我仍然面临XSLT 2.0密钥处理问题:我应该使用什么类型的密钥定义来仅生成一次元素(并且正确调用apply-template)。
<?xml version="1.0" encoding="UTF-8"?>
<ROOT>
<ELEMENTS>
<COMMON-DATATYPES>
<NUMERICAL ID="MyFloat_20_62045">
<NAME>MyFloat</NAME>
</NUMERICAL>
<RANGEABLE-VALUE ID="PercentType_20_62177">
<NAME>PercentType</NAME>
<BASE-RANGEABLE-REF TYPE="NUMERICAL">/MyFloat_20_62045</BASE-RANGEABLE-REF>
</RANGEABLE-VALUE>
</COMMON-DATATYPES>
<OBJECT ID="001">
<NAME>First</NAME>
<PORTS>
<PORT ID="1">
<NAME>Input</NAME>
<TYPE TREF="NUMERICAL">/MyFloat_20_62045</TYPE>
</PORT>
<PORT ID="2">
<NAME>Output</NAME>
<TYPE TREF="NUMERICAL">/MyFloat_20_62045</TYPE>
</PORT>
</PORTS>
</OBJECT>
<OBJECT ID="002">
<NAME>Second</NAME>
<PORTS>
<PORT ID="11">
<NAME>Input</NAME>
<TYPE TREF="NUMERICAL">/MyFloat_20_62045</TYPE>
</PORT>
<PORT ID="22">
<NAME>Output</NAME>
<TYPE TREF="NUMERICAL">
<NUMERICAL ID="MySecondFloat_20_62055">
<NAME>MySecondFloat</NAME>
</NUMERICAL>
</TYPE>
</PORT>
<PORT ID="33">
<NAME>Output</NAME>
<TYPE TREF="RANGEABLE-VALUE">/PercentType_20_62177</TYPE>
</PORT>
<PORT ID="44">
<NAME>Output</NAME>
<TYPE TREF="RANGEABLE-VALUE">
<RANGEABLE-VALUE ID="MyFirstOwnRangeableValue_20_62065">
<NAME>MyFirstOwnRangeableValue</NAME>
</RANGEABLE-VALUE>
</TYPE>
</PORT>
<PORT ID="55">
<NAME>Output</NAME>
<TYPE TREF="RANGEABLE-VALUE">/PercentType_20_62177</TYPE>
</PORT>
</PORTS>
</OBJECT>
</ELEMENTS>
</ROOT>
<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:key name="all-datatypes" match="NUMERICAL/@ID | RANGEABLE-VALUE/@ID | PORT/TYPE" use="tokenize(., '/')[last()]"/>
<xsl:key name="ref-numericals" match="NUMERICAL" use="@ID"/>
<xsl:key name="ref-rangeablevalues" match="RANGEABLE-VALUE" use="@ID"/>
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="ROOT">
<file>
<xsl:apply-templates select="//OBJECT"/>
</file>
</xsl:template>
<xsl:template match="NUMERICAL" mode="copy" name="copy-numerical">
<numerical id="{@ID}">
<name>
<xsl:value-of select="NAME"/>
</name>
</numerical>
</xsl:template>
<!-- Numerical full -->
<xsl:template match="NUMERICAL[@ID is key('all-datatypes', @ID)[1]]">
<xsl:call-template name="copy-numerical"/>
</xsl:template>
<!-- Numerical reference -->
<xsl:template match="NUMERICAL">
<numericial>
<xsl:attribute name="href">#<xsl:value-of select="@ID"/></xsl:attribute>
</numericial>
</xsl:template>
<!-- Rangeable full -->
<xsl:template mode="copy" match="RANGEABLE-VALUE">
<rangeable-value id="{@ID}">
<name>
<xsl:value-of select="NAME"/>
</name>
<type-ref>
<xsl:attribute name="href">#<xsl:value-of select="tokenize(BASE-RANGEABLE-REF, '/')[last()]"/></xsl:attribute>
</type-ref>
</rangeable-value>
</xsl:template>
<!-- Rangeable reference section still missing -->
<xsl:template match="OBJECT">
<object id="{ID}">
<xsl:attribute name="id">
<xsl:value-of select="@ID"/>
</xsl:attribute>
<ports>
<xsl:for-each select="PORTS/PORT">
<port>
<xsl:attribute name="id" select="@ID"/>
<name>
<xsl:value-of select="NAME"/>
</name>
<type>
<xsl:apply-templates mode="copy" select="key('ref-numericals', tokenize(TYPE, '/')[last()])"/>
<xsl:apply-templates mode="copy" select="key('ref-rangeablevalues', tokenize(TYPE, '/')[last()])"/>
</type>
</port>
</xsl:for-each>
</ports>
</object>
</xsl:template>
</xsl:stylesheet>
<file>
<object id="001">
<ports>
<port id="1">
<name>Input</name>
<type>
<numerical id="MyFloat_20_62045">
<name>MyFloat</name>
</numerical>
</type>
</port>
<port id="2">
<name>Output</name>
<numerical href="#MyFloat_20_62045"/>
</port>
</ports>
</object>
<object id="002">
<ports>
<port id="11">
<name>Input</name>
<numerical href="#MyFloat_20_62045"/>
</port>
<port id="22">
<name>Output</name>
<type>
<numerical id="MySecondFloat_20_62055">
<name>MySecondFloat</name>
</numerical>
</type>
</port>
<port id="33">
<name>Output</name>
<type>
<rangeable-value id="PercentType_20_62177">
<name>PercentType</name>
<type-ref href="#MyFloat_20_62045"/>
</rangeable-value>
</type>
</port>
<port id="44">
<name>Output</name>
<type>
<rangeable-value id="MyFirstOwnRangeableValue_20_62065">
<name>MyFirstOwnRangeableValue</name>
<type-ref href="#MyFloat_20_62045"/>
</rangeable-value>
</type>
</port>
<port id="55">
<name>Output</name>
<rangeable-value href="#PercentType_20_62177"/>
</port>
</ports>
</object>
</file>
所以转换原则应该是:“第一次处理元素时,它是完全生成的,后来如果发生相同的元素,那么应该只有为该类型生成的href。”元素详细信息可能存在于COMMON-DATATYPE或PORT / TYPE部分中。
现在,我的XSLT脚本每次为每个PORT生成完整信息。
是否可以在OBJECT中只处理一次“apply-templates命令”以覆盖所有不同的元素(即使引用的类型发生了变化(NUMERICAL,RANGEABLE-VALUE,STRING,BOOLEAN ......))? / p>
修改:
如果我将一个选定的NUMERICAL与(generate-id() = generate-id(key('all-datatypes', @ID)[1]))
左侧:NUMERICAL[generate-id()]
返回的值不同于比较的右手:generate-id(key('all-datatypes', @ID)[1])
。返回的字符串值的开头在两侧都是相同的(?),但在比较的右侧,还有更长的字符串,其中包含其他字符。我是否在所有数据类型中形成了密钥:
<xsl:key name="all-datatypes" match="NUMERICAL/@ID | RANGEABLE-VALUE/@ID | PORT/TYPE" use="tokenize(., '/')[last()]"/>
或者tokenize命令(PORT / TYPE案例需要)会导致这些差异吗?
答案 0 :(得分:0)
由于你最终想要的是由输出树的结构驱动而不是输入,它可能效率较低但我怀疑它会更清晰你分两个阶段进行转换 - 首先构建完整的输出树,没有任何href短路,然后再次处理该树以进行重复数据删除。
<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 method="xml" omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:variable name="phase1">
<file>
<xsl:apply-templates select="//OBJECT" />
</file>
</xsl:variable>
<!-- debugging
<xsl:result-document href="phase1-result.xml">
<xsl:sequence select="$phase1" />
</xsl:result-document> -->
<xsl:apply-templates select="$phase1" mode="dedupe" />
</xsl:template>
<!-- first phase templates - build the output but with all types fully
expanded out e.g.
<port id="33">
<name>Output</name>
<type>
<rangeable-value id="PercentType_20_62177">
<name>PercentType</name>
<type>
<numerical id="MyFloat_20_62045">
<name>MyFloat</name>
</numerical>
</type>
</rangeable-value>
</type>
</port>
-->
<!-- name keys after the TYPE/TREF values so we can select the right key
without using conditionals -->
<xsl:key name="NUMERICAL" match="NUMERICAL" use="@ID"/>
<xsl:key name="RANGEABLE-VALUE" match="RANGEABLE-VALUE" use="@ID"/>
<xsl:template match="OBJECT">
<object id="{@ID}">
<ports>
<xsl:apply-templates select="PORTS/PORT" />
</ports>
</object>
</xsl:template>
<xsl:template match="PORT">
<port id="{@ID}">
<name>
<xsl:value-of select="NAME"/>
</name>
<xsl:apply-templates select="TYPE" />
</port>
</xsl:template>
<xsl:template match="TYPE | BASE-RANGEABLE-REF">
<type>
<!-- This element contains either one child element (type def directly
nested) or a TYPE (B-R-R) or TREF (TYPE) attribute and a text node
with cross-reference to the type. -->
<xsl:apply-templates select="* | key((@TYPE, @TREF)[1], tokenize(text(), '/')[last()])" />
</type>
</xsl:template>
<xsl:template match="NUMERICAL">
<numerical id="{@ID}">
<name>
<xsl:value-of select="NAME"/>
</name>
</numerical>
</xsl:template>
<xsl:template match="RANGEABLE-VALUE">
<rangeable-value id="{@ID}">
<name>
<xsl:value-of select="NAME"/>
</name>
<xsl:apply-templates select="BASE-RANGEABLE-REF" />
</rangeable-value>
</xsl:template>
<!-- phase 2 - deduplicate, replacing second-and-subsequent fully expanded
types with an href to the first one -->
<xsl:key name="dedup-key" match="numerical | rangeable-value" use="@id" />
<xsl:template mode="dedupe" match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="dedupe" />
</xsl:copy>
</xsl:template>
<xsl:template mode="dedupe"
match="type[*[not(. is key('dedup-key', @id)[1])]]">
<!-- logic to handle the different formats in port (<numeric href="#..."/>)
and in rangeable-value (<type-ref href="#..."/>) -->
<xsl:element name="{if (parent::rangeable-value) then 'type-ref'
else node-name(*[1])}">
<xsl:attribute name="href" select="concat('#', */@id)" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
这比我预期的要复杂得多,主要是因为您为交叉引用指定了一些不一致的格式(在port
下使用<numerical href="#..." />
或<rangeable-value href="#..." />
而在rangeable-value
下你使用<type-ref>
),但我仍然认为这使得意图比原始方法更清晰。