使用 XSLT 2.0 按里程碑元素连接文本节点
Concatenate text nodes by milestone element using XSLT 2.0
我的 XML 文件有像 <handShift new="#DP1053/>
这样的 TEI 里程碑元素;在 <text>
内容的开头总是有一个,但在那之后有时会有一些此类标签,其中两个或三个不同的属性值之一散布在 <text>
元素中以指示特定的抄写员在最后一次接听。属性 @new
的值指向 TEI header 中的 @xml:id
定义,注册为 <handNote/>
元素的属性。
我在 XSLT 2.0 中的目标是连接每个抄写员编写的文本,以便我可以独立查询每个抄写员的作品。我想知道推荐的解决方案是否需要 group-starting-with
,但我还没有完全了解所涉及的预处理(我将不胜感激)。相反,我自己的直觉是执行
- a
for-each
循环遍历抄写员的手,运行
- 一个
string-join
- 在所有文本节点上
- 其中属性值为
@new
的前一个 <handShift/>
与当前循环迭代中处理的手相匹配 比前一个 [=21] 更接近 =] 属性值不匹配的地方。
我在转换为 HTML 的 XSLT 2.0 样式表中的试用语法如下:
<xsl:for-each select="//tei:handNote[@xml:id != '']">
<xsl:variable name="hand" select="./@xml:id"/>
<p><xsl:value-of select="$hand"/>: <xsl:value-of select="string-join(//tei:text//text()[preceding-sibling::tei:handShift[@new = concat('#',$hand)] >> preceding-sibling::tei:handShift[@new != concat('#',$hand)]])"/></p>
</xsl:for-each>
但是,这仅 returns 文本中最终里程碑之后的文本节点,并且仅在选择匹配该最终里程碑的属性值的 for-each 迭代中。我肯定把 >>
语句弄错了,如果有任何关于这种方法或不同的 grouping-based 方法的建议,我将不胜感激。
我可能应该提一下,一旦我掌握了这种串联,我将不得不将任何 <add hand="DP1054">addition</add>
类型的内容(即与当前工作不匹配的手工修订)添加到等式中,通过排除这种性质的不匹配内容并包括位于不匹配的抄写员职位内的匹配内容,但我不一定预见到必须将其添加到 "correct" 位置的串联中。因此,我应该能够通过两个相当简单的附加步骤来解决这些问题,但初始串联或分组解决方案必须允许排除具有不匹配属性值的节点,以及我可能希望排除的任何其他元素(例如 <expan>
在下面的例子中)。
这是一个模拟 XML 文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE TEI>
<TEI>
<teiHeader>
<fileDesc/>
<sourceDesc>
<msDesc>
<physDesc>
<handDesc>
<handNote xml:id="DP1054"/>
<handNote xml:id="DP1053"/>
</handDesc>
</physDesc>
</msDesc>
</sourceDesc>
</teiHeader>
<text>
<body>
<p><handShift new="#DP1054"/>I'LL REPRESENT THE WORK OF HAND 1054 IN ALLCAPS <handShift new="#DP1053"/>and I'll represent the work of hand 1053 in lowercase <handShift new="#DP1054"/>THE IDEA BEING THAT IN THE END ALL UPPERCASE TEXT SHOULD BE CONCATENATED <handShift new="#DP1053"/>separately from the sentence case content. Of course reality is a little more <add hand="#DP1054">COMPLEX</add>: we have <hi rend="color(green)">other nodes intervening</hi>, <handShift new="#DP1054"/>AND I WONDER WHETHER THESE WILL MESS WITH THE CONCEPT OF <choice>
<abbr>SBLS</abbr>
<expan>S<ex>I</ex>BL<ex>ING</ex>S</expan>
</choice> <handShift new="#DP1053"/> (I will filter out nodes with `tei:expan` ancestors and nonmatching `add` elements; that's not the part I am having difficulty with).</p>
</body>
</text>
</TEI>
我认为 group-starting-with 可以提供帮助,这是一个将结果存储在 XPath 3.1 映射中的示例(好吧,分组给出了一系列映射,map:merge
函数将它们合并为一个在该 id 的 handShift 之后从 id 到节点的单个映射):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.tei-c.org/ns/1.0"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:variable name="note-map-sequence" as="map(xs:string, node()*)*">
<xsl:for-each-group select="//body/p/node()" group-starting-with="handShift">
<xsl:map-entry key="substring(@new, 2)" select="current-group()"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:variable name="note-map" as="map(xs:string, node()*)"
select="map:merge($note-map-sequence, map { 'duplicates' : 'combine' })"/>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates select="//handNote"/>
</body>
</html>
</xsl:template>
<xsl:template match="handNote">
<p>
<xsl:value-of select="@xml:id"/>:
<xsl:apply-templates select="$note-map(@xml:id)"/>
</p>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bFWRApk有在线样本输出
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>.NET XSLT Fiddle Example</title></head>
<body>
<p>DP1054:
I'LL REPRESENT THE WORK OF HAND 1054 IN ALLCAPS THE IDEA BEING THAT IN THE END ALL UPPERCASE TEXT SHOULD BE CONCATENATED AND I WONDER WHETHER THESE WILL MESS WITH THE CONCEPT OF
SBLS
SIBLINGS
</p>
<p>DP1053:
and I'll represent the work of hand 1053 in lowercase separately from the sentence case content. Of course reality is a little more COMPLEX: we have other nodes intervening, (I will filter out nodes with `tei:expan` ancestors and nonmatching `add` elements;
that's not the part I am having difficulty with).
</p>
</body>
</html>
带有 XPath 3.1 的 XSLT 3 从 Saxon 9.8 开始可用,因此大多数使用 Saxon 9 for XSLT 2 的人应该也可以通过使用最新 (9.9) 或以前 (9.8) 版本的 Saxon 来使用 XSLT 3。
当然,地图只是作为分组结果的优雅轻量级容器,使用的 for-each-group
也可以与 XSLT 2 一起使用,只是您需要将分组结果存储在一些中介XML(例如<group id="{current-grouping-key()}">...</group>
)代替。
我的 XML 文件有像 <handShift new="#DP1053/>
这样的 TEI 里程碑元素;在 <text>
内容的开头总是有一个,但在那之后有时会有一些此类标签,其中两个或三个不同的属性值之一散布在 <text>
元素中以指示特定的抄写员在最后一次接听。属性 @new
的值指向 TEI header 中的 @xml:id
定义,注册为 <handNote/>
元素的属性。
我在 XSLT 2.0 中的目标是连接每个抄写员编写的文本,以便我可以独立查询每个抄写员的作品。我想知道推荐的解决方案是否需要 group-starting-with
,但我还没有完全了解所涉及的预处理(我将不胜感激)。相反,我自己的直觉是执行
- a
for-each
循环遍历抄写员的手,运行 - 一个
string-join
- 在所有文本节点上
- 其中属性值为
@new
的前一个<handShift/>
与当前循环迭代中处理的手相匹配 比前一个 [=21] 更接近 =] 属性值不匹配的地方。
我在转换为 HTML 的 XSLT 2.0 样式表中的试用语法如下:
<xsl:for-each select="//tei:handNote[@xml:id != '']">
<xsl:variable name="hand" select="./@xml:id"/>
<p><xsl:value-of select="$hand"/>: <xsl:value-of select="string-join(//tei:text//text()[preceding-sibling::tei:handShift[@new = concat('#',$hand)] >> preceding-sibling::tei:handShift[@new != concat('#',$hand)]])"/></p>
</xsl:for-each>
但是,这仅 returns 文本中最终里程碑之后的文本节点,并且仅在选择匹配该最终里程碑的属性值的 for-each 迭代中。我肯定把 >>
语句弄错了,如果有任何关于这种方法或不同的 grouping-based 方法的建议,我将不胜感激。
我可能应该提一下,一旦我掌握了这种串联,我将不得不将任何 <add hand="DP1054">addition</add>
类型的内容(即与当前工作不匹配的手工修订)添加到等式中,通过排除这种性质的不匹配内容并包括位于不匹配的抄写员职位内的匹配内容,但我不一定预见到必须将其添加到 "correct" 位置的串联中。因此,我应该能够通过两个相当简单的附加步骤来解决这些问题,但初始串联或分组解决方案必须允许排除具有不匹配属性值的节点,以及我可能希望排除的任何其他元素(例如 <expan>
在下面的例子中)。
这是一个模拟 XML 文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE TEI>
<TEI>
<teiHeader>
<fileDesc/>
<sourceDesc>
<msDesc>
<physDesc>
<handDesc>
<handNote xml:id="DP1054"/>
<handNote xml:id="DP1053"/>
</handDesc>
</physDesc>
</msDesc>
</sourceDesc>
</teiHeader>
<text>
<body>
<p><handShift new="#DP1054"/>I'LL REPRESENT THE WORK OF HAND 1054 IN ALLCAPS <handShift new="#DP1053"/>and I'll represent the work of hand 1053 in lowercase <handShift new="#DP1054"/>THE IDEA BEING THAT IN THE END ALL UPPERCASE TEXT SHOULD BE CONCATENATED <handShift new="#DP1053"/>separately from the sentence case content. Of course reality is a little more <add hand="#DP1054">COMPLEX</add>: we have <hi rend="color(green)">other nodes intervening</hi>, <handShift new="#DP1054"/>AND I WONDER WHETHER THESE WILL MESS WITH THE CONCEPT OF <choice>
<abbr>SBLS</abbr>
<expan>S<ex>I</ex>BL<ex>ING</ex>S</expan>
</choice> <handShift new="#DP1053"/> (I will filter out nodes with `tei:expan` ancestors and nonmatching `add` elements; that's not the part I am having difficulty with).</p>
</body>
</text>
</TEI>
我认为 group-starting-with 可以提供帮助,这是一个将结果存储在 XPath 3.1 映射中的示例(好吧,分组给出了一系列映射,map:merge
函数将它们合并为一个在该 id 的 handShift 之后从 id 到节点的单个映射):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.tei-c.org/ns/1.0"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:variable name="note-map-sequence" as="map(xs:string, node()*)*">
<xsl:for-each-group select="//body/p/node()" group-starting-with="handShift">
<xsl:map-entry key="substring(@new, 2)" select="current-group()"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:variable name="note-map" as="map(xs:string, node()*)"
select="map:merge($note-map-sequence, map { 'duplicates' : 'combine' })"/>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates select="//handNote"/>
</body>
</html>
</xsl:template>
<xsl:template match="handNote">
<p>
<xsl:value-of select="@xml:id"/>:
<xsl:apply-templates select="$note-map(@xml:id)"/>
</p>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bFWRApk有在线样本输出
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>.NET XSLT Fiddle Example</title></head>
<body>
<p>DP1054:
I'LL REPRESENT THE WORK OF HAND 1054 IN ALLCAPS THE IDEA BEING THAT IN THE END ALL UPPERCASE TEXT SHOULD BE CONCATENATED AND I WONDER WHETHER THESE WILL MESS WITH THE CONCEPT OF
SBLS
SIBLINGS
</p>
<p>DP1053:
and I'll represent the work of hand 1053 in lowercase separately from the sentence case content. Of course reality is a little more COMPLEX: we have other nodes intervening, (I will filter out nodes with `tei:expan` ancestors and nonmatching `add` elements;
that's not the part I am having difficulty with).
</p>
</body>
</html>
带有 XPath 3.1 的 XSLT 3 从 Saxon 9.8 开始可用,因此大多数使用 Saxon 9 for XSLT 2 的人应该也可以通过使用最新 (9.9) 或以前 (9.8) 版本的 Saxon 来使用 XSLT 3。
当然,地图只是作为分组结果的优雅轻量级容器,使用的 for-each-group
也可以与 XSLT 2 一起使用,只是您需要将分组结果存储在一些中介XML(例如<group id="{current-grouping-key()}">...</group>
)代替。