XSLT 生成元素基数

XSLT to generate element cardinalities

我想发现 XML 数据集的结构,但我没有 XML 模式。作为此发现的一部分,我想计算数据集元素的最小和最大基数(minOccursmaxOccurs)。我尝试了各种工具从 XML 文档生成 XML 模式,但它们不会生成 minOccursmaxOccurs。但是,我怀疑使用 XSLT (2.0+) 这样做是可行的。

更具体地说,假设我有以下 XML 文档:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <a/>
    <b>
        <c/>
    </b>
    <b/>
</root>

我希望能够以如下形式计算基数:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <a minOccurs="1" maxOccurs="1"/>
    <b minOccurs="2" maxOccurs="2">
        <c minOccurs="0" maxOccurs="1"/>
    </b>
</root>

root 的子节点将始终具有相同的最大和最小基数,因此该部分可以这样计算:

<xsl:template match="/*">
    <xsl:element name="{name()}">
        <xsl:for-each-group select="*" group-by="name()">
            <xsl:sort select="current-grouping-key()"/>
            <xsl:element name="{current-grouping-key()}">
                <xsl:variable name="cardinality" select="count(current-group())"/>
                <xsl:attribute name="minOccurs" select="$cardinality"/>
                <xsl:attribute name="maxOccurs" select="$cardinality"/>
            </xsl:element>
        </xsl:for-each-group>
    </xsl:element>
</xsl:template>

但是,我不知道如何继续处理孙子的基数。我怀疑这可以抽象成递归 xsl:function.

欢迎就如何进行提出任何建议!

我不能 100% 确定这是否适合您的需要,但我想出了这个 XSLT。它通过按路径名对元素进行分组来工作(例如 "root/a/b")

<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:output indent="yes"/>

    <xsl:key name="parent_path" match="*" use="string-join(ancestor::*/name(), '/')" />
    <xsl:key name="full_path" match="*" use="string-join(ancestor-or-self::*/name(), '/')" />

    <xsl:template match="/*" priority="2">
        <xsl:element name="{name()}">
            <xsl:call-template name="element" />
        </xsl:element>
    </xsl:template>

    <xsl:template match="*" name="element">
        <xsl:variable name="path" select="string-join(ancestor-or-self::*/name(), '/')" />
        <xsl:for-each-group select="key('parent_path', $path)" group-by="name()">
            <xsl:sort select="current-grouping-key()"/>
            <xsl:element name="{current-grouping-key()}">
                <xsl:variable name="counts" select="key('full_path', $path)/count(*[name() = name(current())])" />
                <xsl:variable name="min" select="min($counts)" />
                <xsl:variable name="max" select="max($counts)"/>
                <xsl:attribute name="minOccurs" select="if (not(contains($path, '/'))) then $max else $min"/>
                <xsl:attribute name="maxOccurs" select="$max"/>
                <xsl:apply-templates select="." />
            </xsl:element>
        </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

应用于此XML

<root>
    <a/>
    <b>
        <c/>
        <c/>
    </b>
    <b>
        <c/>
        <d>
            <e />
        </d>
        <g></g>
        <g></g>
        <g></g>
    </b>
    <b>
        <c/>
        <d>
            <e />
            <e />
        </d>
        <g></g>
        <g></g>
    </b>
    <a/>
</root>

输出如下....

<root>
   <a minOccurs="2" maxOccurs="2"/>
   <b minOccurs="3" maxOccurs="3">
      <c minOccurs="1" maxOccurs="2"/>
      <d minOccurs="0" maxOccurs="1">
         <e minOccurs="1" maxOccurs="2"/>
      </d>
      <g minOccurs="0" maxOccurs="3"/>
   </b>
</root>