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 在顯示許多緊密相關的日期事件時存在問題,因此並非所有事件都可能出現在時間軸上。