将几个 XML 文件按日期排序并使用 XSLT 将其合并为一个文件

Sorting several XML files along dates and merging it into one with XSLT

我有几个单独的 XML 文件,其中包含 TEI 中的历史字母。现在我想将它们合并为一个文件,以日期为标准。

A1.xml

<?xml version="1.0" encoding="UTF-8"?>
<TEI xml:id="1">
<teiHeader>
    <title>Letter 1</title>
    <date when="19990202" n="0"></date>
</teiHeader>
<text>
        <p>Content of letter 1</p>
</text>
</TEI>

和第二个文件,A2.xml:

<?xml version="1.0" encoding="UTF-8"?>
    <TEI xml:id="2">
    <teiHeader>
        <title>Letter 1</title>
        <date when="20010202" n="0"></date>
    </teiHeader>
    <text>
            <p>Content of letter 2</p>
    </text>
    </TEI>

还有第三个,A3.xml:

<?xml version="1.0" encoding="UTF-8"?>
    <TEI xml:id="3">
    <teiHeader>
        <title>Letter 3</title>
        <date when="18880101" n="0"></date>
    </teiHeader>
    <text>
            <p>Content of letter 3</p>
    </text>
    </TEI>

文件以连续的文件名 "A001.xml" 到 "A999.xml" 命名,但未按所需顺序命名。所以我的首选输出是单个文件 letters.xml:

<?xml version="1.0" encoding="UTF-8"?>
<CORRESPONDENCE>

<TEI xml:id="3">
        <teiHeader>
            <title>Letter 3</title>
            <date when="18880101" n="0"></date>
        </teiHeader>
        <text>
                <p>Content of letter 3</p>
        </text>
        </TEI>

    <TEI xml:id="1">
    <teiHeader>
        <title>Letter 1</title>
        <date when="19990202" n="0"></date>
    </teiHeader>
    <text>
            <p>Content of letter 1</p>
    </text>
    </TEI>
        <TEI xml:id="2">
        <teiHeader>
            <title>Letter 1</title>
            <date when="20010202" n="0"></date>
        </teiHeader>
        <text>
                <p>Content of letter 2</p>
        </text>
        </TEI>
</CORRESPONDENCE>

尽管我找到了将多个 XML 文件合并为一个文件的方法,但我无法使用排序标准使其正常工作。这甚至可能吗?

Is this even possible?

XSLT 旨在能够使用 XML 完成任何转换任务,并且被认为是图灵完备的,所以是的,确实有可能。

我将采用 XSLT 3.0,因为这是展示该版本新功能的绝佳示例:xsl:merge。并不是说不可能,只是事情没有那么简单。它专为处理外部源而设计,但可以处理任何输入,甚至任何大小(可流式处理)。

XSLT 3.0 xsl:merge 示例

使用上面的示例,以下代码将按该文件模式获取所有 XML 个文件,并创建一个包含每个文档副本的文件,按日期排序。

<!-- xsl:initial-template, new in XSLT 3.0 is like "int main()" in C-style languages -->
<xsl:template name="xsl:initial-template">
    <!-- your other code here -->
    <result>
        <xsl:merge>

            <!-- 
            xsl:merge defines the source for merging. It is quite powerful. Here
            is a simple example with your data.

            With for-each-item you select a sequence of items that need to be merged,
            which goes in two steps, first you select a list of anchor items, then
            you use the select-attribute to select the sequence you want to merge. Here 
            a collection of documents is requested, like in OP's question

            The select statement selects, with focus on each document, the sequence
            of items to be merged. This sequence can be of any length (here it selects all
            historic letters)

            The merge-key defines the key for which items in the merge sequence are sorted,
            an incorrect order will result in an error, unless sort-before-merge 
            is also specified.
            -->
            <xsl:merge-source 
                for-each-item="collection('files/A*.xml')"
                select="/root/historic-letter/tei:TEI"
                sort-before-merge="true">
                <xsl:merge-key 
                    select="tei:teiHeader/tei:data/tei:when"
                    order="descending" 
                    data-type="number" />
            </xsl:merge-source>

            <!-- the merge action is called for each item resulting from the select 
            statement above. Only in this place can you use current-merge-key()
            and the current-merge-group() functions, which work similar to their grouping
            counterparts.
            -->
            <xsl:merge-action>
                <source original-document="{base-uri()}">
                    <xsl:copy-of select="." />
                </source>
            </xsl:merge-action>
        </xsl:merge>
    </result>
</xsl:template>

因为您只是想将 XML 文档与 Saxon 9 和 XSLT 2.0 连接起来,所以非常简单

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs"
  version="2.0">

<xsl:param name="file-suffix" as="xs:string" select="'A*.xml'"/>

<xsl:template match="/" name="main">
  <CORRESPONDENCE>
    <xsl:perform-sort select="collection(concat('.?select=', $file-suffix))/*">
      <xsl:sort select="teiHeader/date/xs:integer(@when)"/>
    </xsl:perform-sort>
  </CORRESPONDENCE>
</xsl:template>

</xsl:stylesheet>

你会 运行 使用命令行选项 -it:main -xsl:stylesheet.xsl 或如果需要使用主输入文档,但要处理的文档将简单地使用 collection 作为显示。

如果输入示例中的元素位于命名空间 http://www.tei-c.org/ns/1.0 中,正如 Abel 评论的那样,那么您需要将代码更改为

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xpath-default-namespace="http://www.tei-c.org/ns/1.0"
  exclude-result-prefixes="xs"
  version="2.0">

<xsl:param name="file-suffix" as="xs:string" select="'A*.xml'"/>

<xsl:template match="/" name="main">
  <CORRESPONDENCE>
    <xsl:perform-sort select="collection(concat('.?select=', $file-suffix))/*">
      <xsl:sort select="teiHeader/date/xs:integer(@when)"/>
    </xsl:perform-sort>
  </CORRESPONDENCE>
</xsl:template>

</xsl:stylesheet>