标记化ID列表并使用密钥

时间:2018-06-05 14:10:59

标签: xml xslt xslt-3.0

我在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告诉我&#34;当上下文项不是节点时,不能调用key()函数&#34;

感谢您的任何建议。

的Nb。更正了xml和xsl错误

1 个答案:

答案 0 :(得分:2)

<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"模板块,而无需触及您的逻辑。

也许您想要在其他地方输出全名 - 也可以使用一个专用模板。