标记 ID 列表并使用密钥查找它们

tokenize a list of IDs and look them up with a key

我有一个 people.xml 中的人员列表,其中包含属性 @trait@rel 中对家庭关系的引用。因此条目递归到列表,其中 @rel 包含 @xml:id.

<person xml:id="person_a">
 <firstname>John</firstname>
 <lastname>Foo</lastname>
 <trait type="spouse_of" rel="#person_b">
 <trait type="parent_of" rel="#person_c #person_d">
<person>
<person xml:id="person_b">
 <firstname>Sarah</firstname>
 <lastname>Foo</lastname>
 <trait type="spouse_of" rel="#person_a">
 <trait type="parent_of" rel="#person_c #person_d">
<person>
<person xml:id="person_c">
 <firstname>Henry</firstname>
 <lastname>Foo</lastname>
 <trait type="child_of" rel="#person_a #person_b">
 <trait type="sibling_of" rel="#person_d">
<person>
<person xml:id="person_d">
 <firstname>Tom</firstname>
 <lastname>Foo</lastname>
 <trait type="child_of" rel="#person_a #person_b">
 <trait type=sibling_of" rel="#person_c">
<person>
....

使用 XSL 3.0/Saxon,我尝试将家庭关系输出为以下格式:

<perslist>
<person>
 <name>John Foo</name>
 <relation>spouse of Sarah Foo</relation>
 <relation>parent of Henry Foo, Tom Foo</relation>
</person>
<person>
 <name>Sarah Foo</name>
 <relation>spouse of John Foo</relation>
 <relation>parent of Henry Foo, Tom Foo</relation>
</person>
<person>
 <name>Henry Foo</name>
 <relation>child of John Foo, Sarah Foo</relation>
 <relation>sibling of Tom Foo</relation>
</person>
<person>
 <name>Tom Foo</name>
 <relation>child of John Foo, Sarah Foo</relation>
 <relation>sibling of Henry Foo</relation>
</person>
...
</perslist>

其中大部分已完成并起作用,但我在使用 @rel 时遇到了问题,因为它可以包含多个值。

我正在使用一个键来查找 xml:ids。我正在尝试使用 tokenize() 拆分 @rel 中包含的 ID,但我没有取得任何成功。

<xsl:stylesheet  xmlns:xsl="http://www.w3.org/1999/XSL/Transform version="3.0">


<xsl:key name="ids" match="person" use="@xml:id"/>

....

<xsl:template match="trait">
    <xsl:variable name="trt" select="."/>
    <xsl:choose>
        <xsl:when test=".[@type='spouse_of']">
            <relation>spouse of 
                <xsl:for-each select="tokenize($trt/@rel, ' ')">
                    <xsl:value-of select="key('ids',substring-after(.,'#'))/firstname, key('ids',substring-after(.,'#'))/lastname" separator=", "/>
                </xsl:for-each>
            </relation>
        </xsl:when>
       <xsl:when test=".[@type='parent_of']">
            <relation>parent of 
                <xsl:for-each select="tokenize($trt/@rel, ' ')">
                    <xsl:value-of select="key('ids',substring-after(.,'#'))/firstname, key('ids',substring-after(.,'#'))/lastname"  separator=", ">
                </xsl:for-each>
            </relation>
        </xsl:when>
       <xsl:when test=".[@type='child_of']">
            <relation>child of 
                <xsl:for-each select="tokenize($trt/@rel, ' ')">
                    <xsl:value-of select="key('ids',substring-after(.,'#'))/firstname, key('ids',substring-after(.,'#'))/lastname"  separator=", ">
                </xsl:for-each>
            </relation>
        </xsl:when>
</xsl:template>

</xsl:stylesheet>

特别是撒克逊人告诉我 "Cannot call the key() function when the context item is not a node"

感谢您的任何建议。

铌。更正了 xml 和 xsl 错误

上下文项已在 <xsl:for-each> 中更改。

当你迭代由tokenize()生成的标记列表时,那么每次迭代期间的上下文项将不是一个节点,而是一个xs:string.

key() 期望上下文项是一个节点。这是因为 <xsl:key> 总是适用于 all 个打开的文档,上下文项决定从哪个文档匹配节点中选择。如果您没有在 key() 的第三个参数中给出明确的上下文项,则假定上下文项的文档元素。在这种特殊情况下 . 不是节点,它不属于任何文档,因此 key() 很困惑。

这可以通过显式传递有效的上下文项来解决。将文档元素(正确文档的!)存储在顶级变量中,比方说 $doc,并在对 key() 的调用中使用它效果很好。 任何包含所需匹配项的 节点都可以工作。

话虽这么说,但您复制粘贴编程的方式太多了。怎么样:

<xsl:key name="person" match="person" use="@xml:id"/>
<xsl:variable name="doc" select="/*" />

<xsl:template match="trait">
  <xsl:variable name="self" select="." />
  <xsl:for-each select="tokenize(normalize-space(@rel), ' ')">
    <relation>
      <xsl:choose>
        <xsl:when test="$self/@type='spouse_of'">spouse of </xsl:when>
        <xsl:when test="$self/@type='parent_of'">parent of </xsl:when>
        <xsl:when test="$self/@type='child_of'">child of </xsl:when>
        <!-- there probably should be an <xsl:otherwise> here -->
      </xsl:choose>
      <xsl:variable name="p" select="key('person', substring-after(., '#'), $doc)" />
      <xsl:value-of select="$p/lastname, $p/firstname" separator=", " />
    </relation>
  </xsl:for-each>
</xsl:template>

您可以通过更广泛地使用模板来保存行、临时变量并使方法更加模块化(考虑国际化)。

<xsl:key name="personByRef" match="person" use="concat('#', @xml:id)" />
<xsl:variable name="doc" select="/*" />

<xsl:template match="trait">
  <xsl:variable name="self" select="." />
  <xsl:for-each select="tokenize(normalize-space(@rel), ' ')">
    <relation>
      <xsl:apply-templates select="$self/@type" mode="label" />
      <xsl:apply-templates select="key('personByRef', ., $doc)" mode="fullname" />
    </relation>
  </xsl:for-each>
</xsl:template>

<xsl:template match="trait/@type[.='spouse_of']" mode="label">spouse of </xsl:template>
<xsl:template match="trait/@type[.='parent_of']" mode="label">parent of </xsl:template>
<xsl:template match="trait/@type[.='child_of']" mode="label">child of </xsl:template>

<xsl:template match="person" mode="fullname">
  <xsl:value-of select="lastname,firstname" separator=", " />
</xsl:template>

这里整个 "label" 模板块可以从特定语言的文件中导入,而无需触及您的逻辑。

也许您也想在其他地方输出全名 - 拥有一个专用模板也很有用。