XQuery/TEI 文档时间轴
您想要创建一个包含单个 TEI 文档的日期时间轴。
TEI 文档可能在文档的任何部分包含日期元素 - 在元数据中、在文档出版详细信息中、在前言和后记中,以及在正文中。假设我们想要一个显示正文中日期的时间轴。
我们将使用 Simile Timeline Javascript API 在 HTML 页面中创建一个可浏览的时间轴。
TEI 文档以以下格式存储日期元素中的日期
<date when="1861-03-16">March 16</date>
或
<date when="1861">1861</date>
我们将编写一个 XQuery 脚本,它将提取 TEI 文档正文中的所有日期元素,并生成一个 Simile 时间轴。
日期在 TEI 文档的各个部分都有使用,但我们最有可能对正文中的日期感兴趣。
let $dates := doc($tei-document)//tei:body//tei:date
例如
<date when="1642-01">January 1642</date>
<date when="1616">1616</date>
<date when="1642">1642</date>
<date when="1642-08-13">13 August 1642</date>
<date when="1643-05">May</date>
<date when="1643-07">July 1643</date>
然后,我们可以将此日期元素序列转换为 Simile 所需的格式。
<data>{
for $date in $dates
return
<event start='{$date/@when}' >
{$date/text()}
</event>
}</data>
请注意,上面的查询中包含两个路径表达式。第一个表达式 $date/@when 提取日期元素的 when 属性。第二个路径表达式 $date/text() 提取日期元素的正文文本,即开始和结束日期标记之间的文本
<date when="1642-08-13">13 August 1642</date>
xquery version "1.0";
declare namespace tei = "http://www.tei-c.org/ns/1.0";
(: get the file name from the URL parameter :)
let $file := request:get-parameter('file', '')
(: this is where we will get our TEI documents :)
let $data-collection := '/db/Wiki/TEI/docs'
(: open the document :)
let $tei-document := concat($data-collection, '/', $file)
(: get all dates in the body of the document :)
let $dates := doc($tei-document)//tei:body//tei:date
return
<data>{
for $date in $dates
return
<event start='{$date/@when}'>
{$date/text()}
</event>
}</data>
例如,以下是 TEI 文档“新西兰的发现”中包含的日期,该文档由 J. C. Beaglehole 撰写,由 新西兰电子文本中心 制作
- TEI 日期通常是 XML 日期,Simile 时间轴 API 可以识别这些日期。但是,TEI 支持对相对日期的编码,例如
<date when="--01-01">New Years Day</date>
因此,日期确实需要使用合适的 RegExp 进行过滤。一种选择是使用“castable” XQuery 函数检查日期格式。
我们可以通过在时间轴气泡中为日期提供一些上下文来增强时间轴。一种方法是包含一些前后的文本。
每个日期节点都是父节点的一部分,例如
<date when="1777-02-12">12 February 1777</date>
是以下节点的子节点
<p>Cook left Queen Charlotte's Sound for the fourth time on <date when="1774-11-10">10 November</date>.
He returned for a fifth visit on <date when="1777-02-12">12 February 1777</date> and remained a fortnight; but this
last voyage contributed nothing to the discovery of New Zealand. The discoverer
was bound for the northern hemisphere, and for his death.</p>
我们需要访问目标日期两侧的元素和文本节点的混合。例如,在该节点之前是文本节点(“库克离开..”)、日期节点和另一个文本节点(“他返回..”)。在目标日期之后是文本节点(“并停留...”)。我们可以使用 preceding-sibling 和 following-sibling 轴选择这些节点
let $nodesbefore := $date/preceding-sibling::node()
let $nodesafter := $date/following-sibling::node()
构建上下文字符串的一种粗略方法是连接节点字符串并提取合适的子字符串。文本之后的
let $after := string-join($nodesafter, ' ')
let $afterString := substring($after,1,100)
和文本之前的
let $before := string-join($nodesbefore,' ')
let $beforeString := substring($before,string-length($before)- 101,100)
然后,我们可以创建一个具有目标日期(以粗体显示)的 XML 片段
let $context :=
<div>
{concat('...', $beforeString,' ')}
<b>{$date/text()}</b>
{concat($afterString,' ...')}
</div>
最后,需要序列化该元素并将其添加到事件中
return
<event start='{$when}' title='{$when}' >
{util:serialize($context,("method=xhtml","media-type=text/html"))}
</event>
上下文是从父节点中提取的,不考虑单词或句子边界。以单词边界为单位拆分会更好。
let $nodesafter := $date/following-sibling::node()
(: join the nodes, then split on space :)
let $after := tokenize(string-join($nodesafter, ' '),' ')
(: get the first $scope words :)
let $afterwords := subsequence($after,1,$scope)
(: join the subsequence of words, and suffix with ellipsis if the paragraph text has been truncated :)
let $afterString :=
concat (' ',string-join($afterwords,' '),if (count($after) > $scope) then '... ' else '')
同样,目标日期之前的文本
let $nodesbefore := $date/preceding-sibling::node()
let $before := tokenize(string-join($nodesbefore,' '),' ')
let $beforewords := subsequence($before,count($before) - $scope + 1,$scope)
let $beforeString :=
concat (if (count($before) > $scope) then '... ' else '',string-join($beforewords,' '),' ')
以句子边界为单位拆分会更好。我们可以使用模式“\. ”作为标记。这可能并不完全准确,但误报只会缩短上下文。省略号现在不再需要。$scope 现在是两侧的句子数量。
let $nodesafter := $date/following-sibling::node()
(: join the nodes, then split on the pattern fullstop space :)
let $after := tokenize(string-join($nodesafter, ' '),'\. ')
(: get the first $scope sentences :)
let $afterSentences := subsequence($after,1,$scope)
(: join the subsequence of sentences :)
let $afterString :=
concat (' ',string-join($afterSentences,'. '))
beforeString 的情况类似。
let $nodesbefore := $date/preceding-sibling::node()
let $before := tokenize(string-join($nodesbefore,' '),'\. ')
let $beforeSentences := subsequence($before,count($before) - $scope + 1,$scope)
let $beforeString :=
concat (string-join($beforeSentences,'. '),'. ')
此外,每个事件都可以链接到文档的全文。(待办事项)
由于事件流由源文档参数化,因此包含时间轴的 HTML 页面也需要参数化,因此我们将使用另一个 XQuery 脚本生成它。
时间轴布局的定义使用 SIMILE 时间轴 Javascript API。要定义基本频段
function onLoad(file,start) {
var theme = Timeline.ClassicTheme.create();
theme.event.label.width = 400; // px
theme.event.bubble.width = 300;
theme.event.bubble.height = 300;
var eventSource = new Timeline.DefaultEventSource();
var bandInfo = [
Timeline.createBandInfo({
eventSource: eventSource,
theme: theme,
trackGap: 0.2,
trackHeight: 1,
date: start,
width: "90%",
intervalUnit: Timeline.DateTime.YEAR,
intervalPixels: 45
}),
Timeline.createBandInfo({
date: start,
width: "10%",
intervalUnit: Timeline.DateTime.DECADE,
intervalPixels: 50
})
];
bandInfo[1].syncWith = 0;
bandInfo[1].highlight = true;
Timeline.create(document.getElementById("my-timeline"), bandInfo);
Timeline.loadXML("dates.xq?file="+file, function(xml, url) { eventSource.loadXML(xml, url); });
}
请注意,频段设置为 YEAR 和 DECADE,这些频段适用于历史文本。该函数有两个参数:源文件和开始年份。
事件由对上一节中转换脚本的调用生成。
Timeline.loadXML("dates.xq?file="+file, function(xml, url) { eventSource.loadXML(xml, url); });
开始日期是日期序列中的最早日期。我们可以使用 order by 子句对日期进行排序,然后选择序列中的第一个项目来查找它。
let $orderedDates :=
for $date in $doc//tei:body//tei:date/@when
order by $date
return $date
let $start := $orderedDates[1]
我们可以检索文档标题和作者
xquery version "1.0";
declare namespace tei = "http://www.tei-c.org/ns/1.0";
declare option exist:serialize "method=xhtml media-type=text/html";
let $file:= request:get-parameter('file','')
let $data-collection := '/db/Wiki/TEI/docs'
let $tei-document := concat($data-collection, '/', $file)
let $doc := doc($tei-document)
(: get the title and author from the titleStmt element :)
let $header := $doc//tei:titleStmt
(: there may be several titles, differentiated by the type property - just take the first :)
let $doc-title := string(($header/tei:title)[1])
let $doc-author := string(($header/tei:author/tei:name)[1])
(: get the start date :)
let $orderedDates :=
for $date in $doc//tei:body//tei:date/@when
order by $date
return $date
let $start := $orderedDates[1]
return
<html>
<head>
<title>TimeLine: {$doc-title}</title>
<script src="http://simile.mit.edu/timeline/api/timeline-api.js" type="text/javascript"></script>
<script type="text/javascript">
<![CDATA[
function onLoad(file,start) {
var theme = Timeline.ClassicTheme.create();
theme.event.label.width = 400; // px
theme.event.bubble.width = 300;
theme.event.bubble.height = 300;
var eventSource = new Timeline.DefaultEventSource();
var bandInfo = [
Timeline.createBandInfo({
eventSource: eventSource,
theme: theme,
trackGap: 0.2,
trackHeight: 1,
date: start,
width: "90%",
intervalUnit: Timeline.DateTime.YEAR,
intervalPixels: 45
}),
Timeline.createBandInfo({
date: start,
width: "10%",
intervalUnit: Timeline.DateTime.DECADE,
intervalPixels: 50
})
];
bandInfo[1].syncWith = 0;
bandInfo[1].highlight = true;
Timeline.create(document.getElementById("my-timeline"), bandInfo);
Timeline.loadXML("dates.xq?file="+file, function(xml, url) { eventSource.loadXML(xml, url); });
}
]]>
</script>
</head>
<body onload="onLoad('{$file}','{$start}');">
<h1>Timeline of <em>{$title}</em> by {$author}</h1>
<div id="my-timeline" style="height: 700px; border: 1px solid #aaa"></div>
</body>
</html>
- Simile Timeline 在显示许多紧密相关的日期事件时存在问题,因此并非所有事件都可能出现在时间轴上。