使用 xsl 合并、分组和排序许多 xml
merging, grouping and sorting many xmls using xsl
我正在尝试将多个 XML 文件(数百个)合并为一个 xml 文件,但通过标签 <accountno>
将它们排序和分组(同时添加一个额外的容器和源文件名标签)。我曾尝试使用 C# 执行此操作,但在研究后似乎 XSLT 可能是最简单的方法?问题是我没有足够的 XSLT 经验来实现这个。
我将尝试使用三个简化的 XML 文件进行演示:
file1.xml
<?xml version="1.0" encoding="UTF-8" ?>
<OrigResponse>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</OrigResponse>
File2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<OrigResponse>
<address>
<name1>Title1001257028</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>000456700</accountno>
</body>
</trans>
</OrigResponse>
File3.xml
<?xml version="1.0" encoding="UTF-8" ?>
<OrigResponse>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</OrigResponse>
因为文件 1 和 file3.xml 是同一个帐号,所以需要将它们合并到一个唯一的容器中,而文件 2 在其自己的容器中。所以对于输出 xml 文件,我正在寻找创建这样的东西:
merged.xml
<?xml version="1.0" encoding="UTF-8" ?>
<OrigResponse>
<mergeinvoice>
<inputfile id="{cntr}">file3.xml</inputfile>
<address>
<name1>Title100125777</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>000456700</accountno>
</body>
</trans>
<inputfile id="{cntr}">file1.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</mergeinvoice>
<mergeinvoice>
<inputfile id="{cntr}">file2.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</OrigResponse>
所以我们在容器中将相同编号(多个和单个)的帐号组合在一起 <mergeinvoice>.
我还需要在父级别插入 <inputfilename>
标签,其中包含每个帐户的源 xml 文件的名称,最后,同一标签中的 'id' 属性包含每个文件的递增计数器(我用变量占位符 {cntr}
显示)。
这是否可以像建议的那样使用 XSLT 轻松实现?我知道这是一个很大的问题,但如果是这样,我希望专家能给我指引正确的方向?
非常感谢期待
安迪
假设 Saxon 9 和 XSLT 2.0 下面的样式表期望使用初始模板 main
(it:main
命令行选项)调用并读取目录中的所有 *.xml
文档并将它们分组:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template name="main">
<OrigResponse>
<xsl:for-each-group select="collection('.?*.xml')/OrigResponse" group-by="trans/body/accountno">
<mergeinvoice>
<xsl:variable name="group-pos" as="xs:integer" select="position()"/>
<xsl:apply-templates select="current-group()">
<xsl:with-param name="group-pos" select="$group-pos"/>
</xsl:apply-templates>
</mergeinvoice>
</xsl:for-each-group>
</OrigResponse>
</xsl:template>
<xsl:template match="OrigResponse">
<xsl:param name="group-pos" as="xs:integer"/>
<inputfile id="f{$group-pos}-{position()}">
<xsl:value-of select="tokenize(document-uri(/), '/')[last()]"/>
</inputfile>
<xsl:copy-of select="node()"/>
</xsl:template>
</xsl:stylesheet>
您的三个样本文件的输出是
<OrigResponse>
<mergeinvoice>
<inputfile id="f1-1">file1.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
<inputfile id="f1-2">file3.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</mergeinvoice>
<mergeinvoice>
<inputfile id="f2-1">file2.xml</inputfile>
<address>
<name1>Title1001257028</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>000456700</accountno>
</body>
</trans>
</mergeinvoice>
</OrigResponse>
因此,我选择了名称 f[group-count]-[count-in-group]
中的 id
值,而不是 1
、2
等形式的顺序计数器.要实现顺序计数器,可能需要首先分组到一个变量中以获得临时树,然后使用 xsl:number
将其推送到模板以计算 inputfile
元素,如以下示例所示:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template name="main">
<OrigResponse>
<xsl:variable name="temp-doc">
<xsl:for-each-group select="collection('.?*.xml')/OrigResponse" group-by="trans/body/accountno">
<mergeinvoice>
<xsl:apply-templates select="current-group()" mode="group"/>
</mergeinvoice>
</xsl:for-each-group>
</xsl:variable>
<xsl:apply-templates select="$temp-doc/node()"/>
</OrigResponse>
</xsl:template>
<xsl:template match="OrigResponse" mode="group">
<inputfile>
<xsl:value-of select="tokenize(document-uri(/), '/')[last()]"/>
</inputfile>
<xsl:copy-of select="node()"/>
</xsl:template>
<xsl:template match="@* | node()" mode="#all">
<xsl:copy>
<xsl:apply-templates select="@* , node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="inputfile">
<xsl:copy>
<xsl:attribute name="id">
<xsl:number level="any"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
那么输出就是
<OrigResponse>
<mergeinvoice>
<inputfile id="1">file1.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
<inputfile id="2">file3.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</mergeinvoice>
<mergeinvoice>
<inputfile id="3">file2.xml</inputfile>
<address>
<name1>Title1001257028</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>000456700</accountno>
</body>
</trans>
</mergeinvoice>
</OrigResponse>
我正在尝试将多个 XML 文件(数百个)合并为一个 xml 文件,但通过标签 <accountno>
将它们排序和分组(同时添加一个额外的容器和源文件名标签)。我曾尝试使用 C# 执行此操作,但在研究后似乎 XSLT 可能是最简单的方法?问题是我没有足够的 XSLT 经验来实现这个。
我将尝试使用三个简化的 XML 文件进行演示:
file1.xml
<?xml version="1.0" encoding="UTF-8" ?>
<OrigResponse>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</OrigResponse>
File2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<OrigResponse>
<address>
<name1>Title1001257028</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>000456700</accountno>
</body>
</trans>
</OrigResponse>
File3.xml
<?xml version="1.0" encoding="UTF-8" ?>
<OrigResponse>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</OrigResponse>
因为文件 1 和 file3.xml 是同一个帐号,所以需要将它们合并到一个唯一的容器中,而文件 2 在其自己的容器中。所以对于输出 xml 文件,我正在寻找创建这样的东西:
merged.xml
<?xml version="1.0" encoding="UTF-8" ?>
<OrigResponse>
<mergeinvoice>
<inputfile id="{cntr}">file3.xml</inputfile>
<address>
<name1>Title100125777</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>000456700</accountno>
</body>
</trans>
<inputfile id="{cntr}">file1.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</mergeinvoice>
<mergeinvoice>
<inputfile id="{cntr}">file2.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</OrigResponse>
所以我们在容器中将相同编号(多个和单个)的帐号组合在一起 <mergeinvoice>.
我还需要在父级别插入 <inputfilename>
标签,其中包含每个帐户的源 xml 文件的名称,最后,同一标签中的 'id' 属性包含每个文件的递增计数器(我用变量占位符 {cntr}
显示)。
这是否可以像建议的那样使用 XSLT 轻松实现?我知道这是一个很大的问题,但如果是这样,我希望专家能给我指引正确的方向?
非常感谢期待
安迪
假设 Saxon 9 和 XSLT 2.0 下面的样式表期望使用初始模板 main
(it:main
命令行选项)调用并读取目录中的所有 *.xml
文档并将它们分组:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template name="main">
<OrigResponse>
<xsl:for-each-group select="collection('.?*.xml')/OrigResponse" group-by="trans/body/accountno">
<mergeinvoice>
<xsl:variable name="group-pos" as="xs:integer" select="position()"/>
<xsl:apply-templates select="current-group()">
<xsl:with-param name="group-pos" select="$group-pos"/>
</xsl:apply-templates>
</mergeinvoice>
</xsl:for-each-group>
</OrigResponse>
</xsl:template>
<xsl:template match="OrigResponse">
<xsl:param name="group-pos" as="xs:integer"/>
<inputfile id="f{$group-pos}-{position()}">
<xsl:value-of select="tokenize(document-uri(/), '/')[last()]"/>
</inputfile>
<xsl:copy-of select="node()"/>
</xsl:template>
</xsl:stylesheet>
您的三个样本文件的输出是
<OrigResponse>
<mergeinvoice>
<inputfile id="f1-1">file1.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
<inputfile id="f1-2">file3.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</mergeinvoice>
<mergeinvoice>
<inputfile id="f2-1">file2.xml</inputfile>
<address>
<name1>Title1001257028</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>000456700</accountno>
</body>
</trans>
</mergeinvoice>
</OrigResponse>
因此,我选择了名称 f[group-count]-[count-in-group]
中的 id
值,而不是 1
、2
等形式的顺序计数器.要实现顺序计数器,可能需要首先分组到一个变量中以获得临时树,然后使用 xsl:number
将其推送到模板以计算 inputfile
元素,如以下示例所示:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template name="main">
<OrigResponse>
<xsl:variable name="temp-doc">
<xsl:for-each-group select="collection('.?*.xml')/OrigResponse" group-by="trans/body/accountno">
<mergeinvoice>
<xsl:apply-templates select="current-group()" mode="group"/>
</mergeinvoice>
</xsl:for-each-group>
</xsl:variable>
<xsl:apply-templates select="$temp-doc/node()"/>
</OrigResponse>
</xsl:template>
<xsl:template match="OrigResponse" mode="group">
<inputfile>
<xsl:value-of select="tokenize(document-uri(/), '/')[last()]"/>
</inputfile>
<xsl:copy-of select="node()"/>
</xsl:template>
<xsl:template match="@* | node()" mode="#all">
<xsl:copy>
<xsl:apply-templates select="@* , node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="inputfile">
<xsl:copy>
<xsl:attribute name="id">
<xsl:number level="any"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
那么输出就是
<OrigResponse>
<mergeinvoice>
<inputfile id="1">file1.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
<inputfile id="2">file3.xml</inputfile>
<address>
<name1>Title1001257027</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>123456789</accountno>
</body>
</trans>
</mergeinvoice>
<mergeinvoice>
<inputfile id="3">file2.xml</inputfile>
<address>
<name1>Title1001257028</name1>
<add1>address 1</add1>
</address>
<trans>
<header>
<h1text>mixed text</h1text>
</header>
<body>
<accountno>000456700</accountno>
</body>
</trans>
</mergeinvoice>
</OrigResponse>