XSLT 2.0密钥问题?处理元素一次又一次参考

时间:2014-01-17 14:09:57

标签: xslt

我仍然面临XSLT 2.0密钥处理问题:我应该使用什么类型的密钥定义来仅生成一次元素(并且正确调用apply-template)。

source xml:

<?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>

我当前的XSLT脚本:

<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案例需要)会导致这些差异吗?

1 个答案:

答案 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>),但我仍然认为这使得意图比原始方法更清晰。