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



我有一个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:id。 我正在尝试使用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>

特别是 Saxon 告诉我"当上下文项不是节点时,无法调用 key(( 函数">

感谢您的任何建议。

注意更正了 xml 和 XSL 错误

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

当您迭代由tokenize()生成的令牌列表时,则每次迭代期间的上下文项将不是节点,而是xs:string

key()期望上下文项是一个节点。这是因为<xsl:key>始终适用于所有打开的文档,并且上下文项决定从哪些文档匹配节点中进行选择。如果未在第三个参数中为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"模板块,而无需触及您的逻辑。

也许您也想在其他地方输出全名 - 为此使用单个专用模板也很有用。

最新更新