我有一个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"
模板块,而无需触及您的逻辑。
也许您也想在其他地方输出全名 - 为此使用单个专用模板也很有用。