XSLT 生成元素基数
XSLT to generate element cardinalities
我想发现 XML 数据集的结构,但我没有 XML 模式。作为此发现的一部分,我想计算数据集元素的最小和最大基数(minOccurs
和 maxOccurs
)。我尝试了各种工具从 XML 文档生成 XML 模式,但它们不会生成 minOccurs
和 maxOccurs
。但是,我怀疑使用 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>
我想发现 XML 数据集的结构,但我没有 XML 模式。作为此发现的一部分,我想计算数据集元素的最小和最大基数(minOccurs
和 maxOccurs
)。我尝试了各种工具从 XML 文档生成 XML 模式,但它们不会生成 minOccurs
和 maxOccurs
。但是,我怀疑使用 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>