reworked file structure; changed search results to be faster

This commit is contained in:
Oleg Belyaev 2025-03-26 14:37:06 +03:00
parent a0fa864475
commit a65037f8ea
15 changed files with 880 additions and 853 deletions

View file

@ -1,3 +1,80 @@
var md5 = (function() {
var MD5 = function (d) {
return M(V(Y(X(d), 8 * d.length)))
}
function M (d) {
for (var _, m = '0123456789abcdef', f = '', r = 0; r < d.length; r++) {
_ = d.charCodeAt(r)
f += m.charAt(_ >>> 4 & 15) + m.charAt(15 & _)
}
return f
}
function X (d) {
for (var _ = Array(d.length >> 2), m = 0; m < _.length; m++) {
_[m] = 0
}
for (m = 0; m < 8 * d.length; m += 8) {
_[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32
}
return _
}
function V (d) {
for (var _ = '', m = 0; m < 32 * d.length; m += 8) _ += String.fromCharCode(d[m >> 5] >>> m % 32 & 255)
return _
}
function Y (d, _) {
d[_ >> 5] |= 128 << _ % 32
d[14 + (_ + 64 >>> 9 << 4)] = _
for (var m = 1732584193, f = -271733879, r = -1732584194, i = 271733878, n = 0; n < d.length; n += 16) {
var h = m
var t = f
var g = r
var e = i
f = md5ii(f = md5ii(f = md5ii(f = md5ii(f = md5hh(f = md5hh(f = md5hh(f = md5hh(f = md5gg(f = md5gg(f = md5gg(f = md5gg(f = md5ff(f = md5ff(f = md5ff(f = md5ff(f, r = md5ff(r, i = md5ff(i, m = md5ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = md5ff(r, i = md5ff(i, m = md5ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = md5ff(r, i = md5ff(i, m = md5ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = md5ff(r, i = md5ff(i, m = md5ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = md5gg(r, i = md5gg(i, m = md5gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = md5gg(r, i = md5gg(i, m = md5gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = md5gg(r, i = md5gg(i, m = md5gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = md5gg(r, i = md5gg(i, m = md5gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = md5hh(r, i = md5hh(i, m = md5hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = md5hh(r, i = md5hh(i, m = md5hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = md5hh(r, i = md5hh(i, m = md5hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = md5hh(r, i = md5hh(i, m = md5hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = md5ii(r, i = md5ii(i, m = md5ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = md5ii(r, i = md5ii(i, m = md5ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = md5ii(r, i = md5ii(i, m = md5ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = md5ii(r, i = md5ii(i, m = md5ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551)
m = safeadd(m, h)
f = safeadd(f, t)
r = safeadd(r, g)
i = safeadd(i, e)
}
return [m, f, r, i]
}
function md5cmn (d, _, m, f, r, i) {
return safeadd(bitrol(safeadd(safeadd(_, d), safeadd(f, i)), r), m)
}
function md5ff (d, _, m, f, r, i, n) {
return md5cmn(_ & m | ~_ & f, d, _, r, i, n)
}
function md5gg (d, _, m, f, r, i, n) {
return md5cmn(_ & f | m & ~f, d, _, r, i, n)
}
function md5hh (d, _, m, f, r, i, n) {
return md5cmn(_ ^ m ^ f, d, _, r, i, n)
}
function md5ii (d, _, m, f, r, i, n) {
return md5cmn(m ^ (_ | ~f), d, _, r, i, n)
}
function safeadd (d, _) {
var m = (65535 & d) + (65535 & _)
return (d >> 16) + (_ >> 16) + (m >> 16) << 16 | 65535 & m
}
function bitrol (d, _) {
return d << _ | d >>> 32 - _
}
function MD5Unicode(buffer){
if (!(buffer instanceof Uint8Array)) {
buffer = new TextEncoder().encode(typeof buffer==='string' ? buffer : JSON.stringify(buffer));
}
var binary = [];
var bytes = new Uint8Array(buffer);
for (var i = 0, il = bytes.byteLength; i < il; i++) {
binary.push(String.fromCharCode(bytes[i]));
}
return MD5(binary.join(''));
}
return MD5Unicode;
})();
$( document ).ready(function() {
// GLOBALS;
@ -9,4 +86,48 @@ $( document ).ready(function() {
$('#abv-search').on("submit", function () {
$('#abv-search-icon').replaceWith('<div class="loader"></div>');
});
// SEARCH RESULTS MODAL
$('#abv-btn-searchResults').on("click", function () {
$('#modal_searchResults')[0].showModal();
});
// Close search modal
$('.abv-close-search').on("click", function () {
$('#modal_searchResults')[0].close();
});
$('#modal_searchResults').on("toggle", async function () {
// let html = "";
async function getResults() {
let url = "./api/search/current";
const response = await fetch(url);
const result = await response.json();
return result;
}
async function getEntryForm(id) {
let url = `./api/entries/${md5(id).toUpperCase()}`;
const response = await fetch(url);
const result = await response.json();
return result.form;
}
searchResults = await getResults();
for (result of searchResults) {
let pos = searchResults.indexOf(result);
// let form = await getEntryForm(result.entry);
let html = `
<tr>
<td>${pos+1}</td>
<td>
<a href="./search/position?p=${pos+1}">${result.entryForm}</a>
</td>
<td>
${result.fragment}
</td>
</tr>
`
$(this).find('tbody').append($(html));
}
// $(this).find('tbody').append($(html));
});
});

View file

@ -216,7 +216,7 @@ a.abv-map img {
height: 2ex;
}
a.link:hover {
a.link:hover, a.abv-map:hover {
cursor: pointer;
}

View file

@ -20,16 +20,6 @@ $( document ).ready(function() {
//hideNav: '.pagination',
});
// SEARCH RESULTS MODAL
$('#abv-btn-searchResults').on("click", function () {
$('#modal_searchResults')[0].showModal();
});
// Close search modal
$('.abv-close-search').on("click", function () {
$('#modal_searchResults')[0].close();
});
// MAP MODAL
// This event binding is done on page load and also whenever new entries are loaded.
bindMapOpen = function () {

View file

@ -324,11 +324,12 @@ declare function abv-m:search($db-lang as xs:string,
}
let $hits := $docs//tei:entry[1][$test(.)]
return array:build(for $hit in $hits
let $entry-id := $hit/@xml:id
let $entry-id as xs:string := $hit/@xml:id
(: group by $entry-id :)
let $mark := ft:mark($hit[$test(.)])
order by abv-m:sortKey($hit/@xml:id)
return {'entry': $entry-id,
'entryForm': abv-m:entry-form-by-id($entry-id),
(: 'nodes': array:build(for $node in $hit return path($node)), :)
'tei': ft:mark($hit[$test(.)]),
'fragment': <span>{util:strip-namespaces(($mark//*[tei:mark])[1])/child::node()}</span>

View file

@ -1,122 +0,0 @@
module namespace page = 'http://ossetic-studies.org/ns/abaevdict-site';
declare namespace tei = "http://www.tei-c.org/ns/1.0";
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at './abv-mod.xqm';
declare variable $page:items as xs:integer := 20;
declare %rest:path("{$lang}/dict/page{$p}")
(: %rest:GET :)
%output:method("html")
%output:html-version('5')
function page:dict($lang, $p) {
let $pagetotal := ceiling(count(collection('abaevdict_en')) div $page:items)
let $sorted := abv-m:sort-collection(collection(`abaevdict_{$lang}`))
return
<html>
<head>
<script src="/static/jquery-3.7.1.min.js">
</script>
<script src="/static/infinite-scroll.pkgd.min.js">
</script>
<script src="/static/plotly-3.0.1.min.js" charset="utf-8"></script>
<script src="/static/abaev.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"/>
<link rel="stylesheet" href="{web:create-url('/static/abaev-html.css')}"/>
<title>Abaev</title>
</head>
<body>
<header>
<nav>
<ul>
<li><strong>Abaevdict</strong></li>
</ul>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Dictionary</a></li>
<li><a href="#">Index</a></li>
<li><a href="#">References</a></li>
</ul>
</nav>
</header>
<button id="open-leftbar">
{html:doc('../static/chevron-right.svg')}
</button>
<button id="close-leftbar">
{html:doc('../static/chevron-left.svg')}
</button>
<aside id="leftbar">
<input type="text" id="filter-entries" placeholder="Quick filter…"/>
<nav id="entrylist">
<ul>
{for $doc in $sorted
return <li id="link_{$doc/tei:entry[1]/@xml:id}">
<a href="by-id/{$doc/tei:entry[1]/@xml:id}">
{$doc/tei:entry[1]/tei:form[1]/tei:orth[1]/text()}
</a>
</li>
}
</ul>
</nav>
</aside>
<main>
{for $doc at $i in $sorted
where $i > ($p - 1) * $page:items and $i <= $p * $page:items
let $html := xslt:transform($doc,
doc('../xsl/abaev2html.xsl'), {'lang': $lang})
return $html}
</main>
<footer>
<nav class="pagination">
<ul>
<li>
{if ($p > 1) then <a href="page{$p - 1}">Previous page</a> else ()}
</li>
</ul>
<!-- <ul><li>Page {$p} of {$pagetotal}</li></ul> -->
<ul>
<li>
{if ($p < $pagetotal) then
<a class="pagination__next" href="page{$p + 1}">Next page</a> else ()}
</li>
</ul>
</nav>
</footer>
<dialog id="modal_map">
<article>
<header><button class="abv-close-map" aria-label="Close" rel="prev"></button>
<p>Map</p>
</header>
<p>
<div id="map_display" style="width:100%;height:80%;"></div>
</p>
<footer><button class="abv-close-map">Close</button></footer>
</article>
</dialog>
</body>
</html>
};
declare %rest:path("{$lang}/dict/by-id/{$id}")
(: %rest:GET :)
%output:method("html")
%output:html-version('5')
function page:by-id($lang, $id) {
let $seq := abv-m:sort-collection(collection(`abaevdict_{$lang}`))
let $doc-index := index-of($seq, $seq[tei:entry[@xml:id=`{$id}`]])
let $pagenum := ceiling($doc-index div $page:items)
return web:redirect(`../page{$pagenum}`, {}, web:decode-url($id))
};
declare %rest:path("{$lang}/dict/map-info/{$entry}")
%rest:produces("application/json")
%rest:GET function page:map-info($lang, $entry) {
let $ments := abv-m:make-geomap(doc(`abaevdict_{$lang}/{$entry}.xml`),
$lang)
return json:serialize($ments, {'format': 'xquery'})
};

View file

@ -1,718 +0,0 @@
module namespace page = 'http://ossetic-studies.org/ns/abaevdict-site';
declare namespace tei = "http://www.tei-c.org/ns/1.0";
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at './abv-mod.xqm';
(: =========================================================== :)
(: ================== GLOBAL VARIABLES ======================= :)
(: =========================================================== :)
(: Number of items per page :)
declare variable $page:items as xs:integer := 20;
(: These variables are declared in module scope because they are needed in
different places, but they are still dynamically evaluated :)
declare variable $page:lookup := doc('abaevdict_index/lookup.xml')/tei:table/tei:entry;
declare variable $page:lookup-all := doc('abaevdict_index/lookup.xml')/tei:table/*;
declare variable $page:total := ceiling(count($page:lookup) div $page:items);
(: This is still called sorted, because I haven't changed the code.
But it's now equal to lookup, because the lookup itself is now sorted. :)
declare variable $page:sorted := $page:lookup;
(: =============================================================== :)
(: ======================= COMMON STUFF ========================== :)
(: =============================================================== :)
declare function page:head($title as xs:string, $script as node()* := ()) {
<head>
<script src="/static/jquery-3.7.1.min.js">
</script>
<script src="/static/abaev-global.js">
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"/>
<link rel="stylesheet" href="{web:create-url('/static/abaev-html.css')}"/>
{$script}
<title>{$title}</title>
</head>
};
(: Navigation header :)
declare function page:header($lang as xs:string, $href-other as xs:string) {
<header>
<nav>
<ul class="abv-brand">
<li><img class="abv-logo" src="/static/Abaev_logo.png" alt="Abaevdict logo"></img>
<strong>{
switch($lang)
case 'ru' return 'ИЭСОЯ'
default return `Abaevdict`
}</strong><i> β</i></li>
<li>{
(element {if ($lang = 'ru') then 'mark' else 'a'}{
if ($lang != 'ru') then
(attribute class {'link'},
attribute data-href {`{$href-other}`}) else (),
'ru'
}, ' / ',
element {if ($lang = 'ru') then 'a' else 'mark'}{
if ($lang = 'ru') then
(attribute class {'link'},
attribute data-href {`{$href-other}`}) else (),
'en'
})
}
</li>
</ul>
<ul>
<li>
<form role="search" action="./search/new" id="abv-search">
{if (session:get('searchQuery')) then
<button onclick="location.href='./search/clear'" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
</svg>
</button>
else
<select name="searchType" required="1">
<option selected="1" value="full">{if ($lang = 'ru')
then 'Везде' else 'All'}</option>
<option value="form">{if ($lang = 'ru')
then 'Формы' else 'Forms'}</option>
<option value="sense">{if ($lang = 'ru')
then 'Значения' else 'Senses'}</option>
<option value="example">{if ($lang = 'ru')
then 'Примеры' else 'Examples'}</option>
<option value="translation">{if ($lang = 'ru')
then 'Переводы' else 'Translations'}</option>
<option value="mentioned">{if ($lang = 'ru')
then 'Цит. формы' else 'Mentioned'}</option>
<option value="gloss">{if ($lang = 'ru')
then 'Глоссы' else 'Glosses'}</option>
<option value="etym">{if ($lang = 'ru')
then 'Этимологии' else 'Etymology'}</option>
</select>
}
<input name="searchQuery"
placeholder="{if ($lang = 'ru') then 'Искать' else 'Search'}"
value="{session:get('searchQuery')}"
aria-label="Search">
{if (exists(session:get('searchQuery'))) then
attribute disabled {"1"} }
</input>
{if (not(session:get('searchQuery'))) then
<button type="submit">
<svg id="abv-search-icon" xmlns="http://www.w3.org/2000/svg"
width="16" height="16"
viewBox="0 0 16 16"
fill="currentColor" class="bi bi-search">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
</svg>
</button>
else
(
if (session:get('searchN') > 1) then
<button type="button" class="link" data-href="./search/prev">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8"/>
</svg>
</button>,
<button type="button" id="abv-btn-searchResults">{session:get('searchN')}/{array:size(session:get('searchData'))}</button>
,
if (session:get('searchN') and session:get('searchN') < array:size(session:get('searchData'))) then
<button type="button" class="link" data-href="./search/next">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8"/>
</svg>
</button>
)
}
</form>
</li>
<li><a href="./home">{
switch($lang)
case 'ru' return 'Главная'
default return 'Home'
}
</a></li>
<li><a href="./dict">{switch($lang)
case 'ru' return 'Словарь'
default return 'Dictionary'
}
</a></li>
<li><a href="./index">{switch($lang)
case 'ru' return 'Указатель'
default return 'Index'
}</a></li>
<li><a href="#">{switch($lang)
case 'ru' return 'Литература'
default return 'References'
}</a></li>
</ul>
</nav>
</header>
};
declare function page:invert-lang($lang as xs:string) as xs:string {
if($lang = 'en') then 'ru' else 'en'
};
(: =============================================================== :)
(: ============================ FAVICON ========================== :)
(: =============================================================== :)
declare %rest:path("/favicon.ico")
%rest:GET
function page:favicon() {
fetch:binary('../static/favicon.ico')
};
(: =============================================================== :)
(: ======================= SEARCH ENTRYPOINT ===================== :)
(: =============================================================== :)
declare function page:entry-for-node($lang as xs:string, $node as node()) {
db:get(`abaevdict_{$lang}`,db:path($node))/tei:entry[1]/string(@xml:id)
};
declare %rest:path("/search/{$path=.+}")
%rest:GET
function page:search-default($path as xs:string) {
web:forward(`/en/search/{$path}`)
};
declare %rest:path("{$lang}/search/new")
%rest:query-param("searchType", "{$searchType}", 'full')
%rest:query-param("searchQuery", "{$searchQuery}")
%output:method("html")
%output:html-version('5')
function page:search($lang, $searchType, $searchQuery) {
let $sd := abv-m:search($lang,$searchType,$searchQuery)
let $r1 := session:set('searchType', $searchType)
let $r2 := session:set('searchQuery', $searchQuery)
let $r3 := session:set('searchN', 1)
let $r4 := session:set('searchData', $sd)
return
if(array:size($sd) > 0) then
page:by-id($lang,
$sd(1)('entry'),
'')
else
web:redirect('../search/clear')
};
declare %rest:path("{$lang}/search/next")
%output:method("html")
%output:html-version('5')
function page:search-next($lang) {
let $n as xs:integer := session:get('searchN')+1
let $r1 := session:set('searchN', $n)
let $sd := session:get('searchData')
return page:by-id($lang, $sd($n)('entry'), ())
};
declare %rest:path("{$lang}/search/prev")
%output:method("html")
%output:html-version('5')
function page:search-prev($lang) {
let $n as xs:integer := session:get('searchN')-1
let $r1 := session:set('searchN', $n)
let $sd := session:get('searchData')
return page:by-id($lang, $sd($n)('entry'), ())
};
declare %rest:path("{$lang}/search/position")
%rest:query-param("p", "{$p}")
%output:method("html")
%output:html-version('5')
function page:search-position($lang, $p as xs:integer) {
let $r1 := session:set('searchN', $p)
let $sd := session:get('searchData')
return page:by-id($lang, $sd($p)('entry'), ())
};
declare %rest:path("{$lang}/search/clear")
%output:method("html")
%output:html-version('5')
function page:clearSearch($lang) {
let $r1 := session:delete('searchType')
let $r2 := session:delete('searchQuery')
let $r3 := session:delete('searchN')
let $r4 := session:delete('searchData')
return web:redirect('../dict')
};
(: =============================================================== :)
(: ======================= HOMEPAGE ============================== :)
(: =============================================================== :)
declare %rest:path("{$lang}")
%output:method("html")
%output:html-version('5')
function page:zero($lang) {
page:home($lang)
};
declare %rest:path("/")
%output:method("html")
%output:html-version('5')
function page:zero() {
page:zero('en')
};
declare %rest:path("{$lang}/home")
%output:method("html")
%output:html-version('5')
function page:home($lang) {
<html>
{page:head(if ($lang='ru') then 'ИЭСОЯ — Главная' else 'Abaevdict — Home',())}
<body>
{page:header($lang,`../{page:invert-lang($lang)}/home`)}
<main>
{
switch($lang)
case "ru" return (
<p>
Вы находитесь на сайте электронной двуязычной версии
<a href="https://ironau.ru/iesoja.html">Историко-этимологического
словаря осетинского языка</a> (ИЭСОЯ) В.&#160;И.&#160;Абаева.
Оцифровка и перевод словаря выполнены группой сотрудников, студентов и аспирантов
<a href="https://tipl.philol.msu.ru/">отделения теоретической и
прикладной лингвистики</a> МГУ имени М.&#160;В.&#160;Ломоносова и
<a href="https://iling-ran.ru/web/ru/departments/indo-european/iranian">
сектора иранских языков</a> Института языкознания РАН.
</p>,
<p>
Электронная версия словаря и перевод III и IV томов выполнены за счёт
гранта Российского научного фонда №
<a href="https://rscf.ru/project/22-28-01639/">22-28-01639</a>.
</p>,
<p>
Мы выражаем особую благодарность Амирхану Михайловичу Торчинову, который
выступил инициатором этого проекта и продолжает оказывать нам неоценимую
поддержку в нашей работе. Мы также благодарны московской осетинской общине за
поддержку перевода III томов словаря и помощь в печатном издании их английской
версии, в особенности Александру Тотоонову, Валерию Дзгоеву, Олегу Пухову,
Владиславу Хаблиеву, Руслану Бестолову, Виталию Даурову, Игорю Дзуцеву, Марине
Каболовой, Владимиру Туганову, Зоинбеку Абаеву, Виктору Джиоеву, Владимиру
Бароеву и Борису Базаеву.
</p>,
<p>
Электронная версия словаря основана на адаптации системы XML-разметки
<a href="https://tei-c.org/">TEI P5</a>. Сервер базы данных использует систему
<a href="https://basex.org">BaseX</a>. Модель разметки словаря и реализация её
программного отображения разработаны О.&#160;И.&#160;Беляевым. Все исходные
данные доступны в <a href="https://code.cucurri.ru/abaevdict">репозиториях</a>
проекта.
</p>,
<p>
Дизайн и эмблема сайта выполнены А.&#160;А.&#160;Осиповой.
</p>,
<p>
В предыдущей версии электронного словаря использовалась реляционная
<a href="https://ossetic.iranic.space">модель</a> представления данных,
разработанная Ю.&#160;Ю.&#160;Макаровым (ИЯз РАН, Кембриджский университет)
на платформе OnLex.
</p>,
<h5>Как ссылаться</h5>,
<blockquote>Belyaev, Oleg, Irina Khomchenkova, Julia Sinitsyna, Vadim Dyachkov. Digitizing print dictionaries using TEI: The Abaev Dictionary Project // <i>IWCLUL 2021: The Seventh International Workshop on Computational Linguistics of Uralic Languages. Proceedings of the Workshop</i>. Stroudsburg, USA: Association for Computational Linguistics, 2021. P. 1219.
</blockquote>,
<h5>Участники проекта</h5>,
<table>
<tbody>
<tr>
<th scope="row">Научный руководитель</th>
<td>О.&#160;И.&#160;Беляев</td>
<td>(МГУ имени
М.&#160;В.&#160;Ломоносова, ИЯз РАН)</td>
</tr>
<tr>
<th scope="row">Основные участники</th>
<td>Ю.&#160;В.&#160;Синицына</td>
<td>(МГУ имени
М.&#160;В.&#160;Ломоносова)</td>
</tr>
<tr>
<td></td>
<td>И.&#160;А.&#160;Хомченкова</td>
<td>(МГУ имени М.&#160;В.&#160;Ломоносова, ИРЯ РАН)</td>
</tr>
<tr>
<th>Оцифровка и перевод</th>
<td>В.&#160;В.&#160;Дьячков</td>
<td>(ИЯз РАН)</td>
</tr>
<tr>
<td></td>
<td>А.&#160;А.&#160;Осипова</td>
<td>(МГУ имени М.&#160;В.&#160;Ломоносова)</td>
</tr>
<tr>
<td></td>
<td>А.&#160;О.&#160;Бадеев</td>
<td>(ИЯз РАН, НИУ ВШЭ)</td>
</tr>
<tr>
<td></td>
<td>Д.&#160;А.&#160;Алексеев</td>
<td>(МГУ имени М.&#160;В.&#160;Ломоносова)</td>
</tr>
</tbody>
</table>)
default return
(
<p>
This is the website of the electronic version of V.&#160;I.&#160;Abaev&#39;s
<a href="https://ironau.ru/iesoja.html">Historical-Etymological Dictionary
of Ossetic</a> (HEDO). Its digitization and English translation have been
carried out by staff members and students of the
<a href="https://tipl.philol.msu.ru/">department of theoretical and applied
linguistics</a>, Lomonosov Moscow State University, and the <a
href="https://iling-ran.ru/web/ru/departments/indo-european/iranian">
Department of Iranian languages</a>, Institute of Linguistics RAS.
</p>,
<p>
The electronic version of the dictionary and the translation of vols. 3 and 4
have been supported by the Russian Science Foundation, project no.
<a href="https://rscf.ru/project/22-28-01639/">22-28-01639</a>.
</p>,
<p>
We are deeply grateful to Amirkhan Torchinov, who has been the initiator of this
project and is still providing us with invaluable support in our work. We are
also grateful to the Ossetian community of Moscow for their support of the
translation of vols. 1 and 2 and the help in publishing its preliminary English
version; in particular, to Alexander Totoonov, Valery Dzgoev, Oleg Pukhov,
Vladislav Khabliev, Ruslan Bestolov, Vitaly Daurov, Igor Dzutsev, Marina
Kabolova, Vladimir Tuganov, Zoinbek Abaev, Viktor Dzhioev, Vladimir
Baroev and Boris Bazaev.
</p>,
<p>
The electronic edition of the dictionary is based on an adaptation of the
XML-based <a href="https://tei-c.org/">TEI P5</a> markup system. The database
server runs on <a href="https://basex.org">BaseX</a>. The markup model and its
software realization have been developed by Oleg Belyaev. All sources are
available in our <a href="https://code.cucurri.ru/abaevdict">repositories</a>.
</p>,
<p>
Site design and logo are by Anna Osipova.
</p>,
<p>
The previous version of the electronic dictionary used a relational
data <a href="https://ossetic.iranic.space">model</a> developed by Yury Makarov
(IL RAS, Cambridge University) using the OnLex platform.
</p>,
<h5>How to cite</h5>,
<blockquote>Belyaev, Oleg, Irina Khomchenkova, Julia Sinitsyna, Vadim Dyachkov. Digitizing print dictionaries using TEI: The Abaev Dictionary Project // <i>IWCLUL 2021: The Seventh International Workshop on Computational Linguistics of Uralic Languages. Proceedings of the Workshop</i>. Stroudsburg, USA: Association for Computational Linguistics, 2021. P. 1219.
</blockquote>,
<h5>Project participants</h5>,
<table>
<tbody>
<tr>
<th scope="row">Project leader</th>
<td>Oleg Belyaev</td>
<td>(Lomonosov MSU, IL RAS)</td>
</tr>
<tr>
<th scope="row">Core team</th>
<td>Irina Khomchenkova</td>
<td>(Lomonosov MSU, Vinogradov RLI RAS)</td>
</tr>
<tr>
<td></td>
<td>Julia Sinitsyna</td>
<td>(Lomonosov MSU)</td>
</tr>
<tr>
<th>Digitization and translation</th>
<td>Vadim Dyachkov</td>
<td>(IL RAS, LLACAN CNRS)</td>
</tr>
<tr>
<td></td>
<td>Anna Osipova</td>
<td>(Lomonosov MSU)</td>
</tr>
<tr>
<td></td>
<td>Artyom Badeev</td>
<td>(IL RAS, HSE University)</td>
</tr>
<tr>
<td></td>
<td>Danil Alekseev</td>
<td>(Lomonosov MSU)</td>
</tr>
</tbody>
</table>)
}
</main>
</body>
</html>
};
declare %rest:path("home")
%output:method("html")
%output:html-version('5')
function page:home() {
page:home('en')
};
(: =============================================================== :)
(: ======================= THE DICTIONARY ======================== :)
(: =============================================================== :)
(: If no language defined, default to English :)
declare %rest:path("dict")
%output:method("html")
%output:html-version('5')
function page:dict() {
web:forward('/en/dict')
};
(: The main dictionary view :)
declare %rest:path("{$lang}/dict")
%rest:query-param("page","{$p}", 1)
%rest:query-param("xpath","{$xpath}", '')
%rest:query-param("entry","{$entry}", '')
%output:method("html")
%output:html-version('5')
function page:dict($lang, $p, $xpath, $entry) {
<html>
{page:head('HEDO Dictionary',
(<script src="/static/infinite-scroll.pkgd.min.js">
</script>,
<script src="/static/plotly-3.0.1.min.js" charset="utf-8"></script>,
<script src="/static/abaev.js"></script>))}
<body>
{page:header($lang,`../{page:invert-lang($lang)}/dict`)}
<!-- Sidebar with entry list -->
<button id="open-leftbar">
{html:doc('../static/chevron-right.svg')}
</button>
<button id="close-leftbar">
{html:doc('../static/chevron-left.svg')}
</button>
<aside id="leftbar">
<fieldset>
<input type="text" id="filter-entries"
placeholder="{if ($lang = 'ru') then 'Быстрый фильтр' else 'Quick filter…'}"/>
<label>
<input id="show-re" type="checkbox" role="switch"></input>
{if ($lang = 'ru') then 'Производные' else 'Show subentries'}
</label>
</fieldset>
<nav id="entrylist"></nav> <!-- To be filled dynamically in js -->
</aside>
<!-- The dictionary itself. The entries are displayed as articles retrieved
from the database in HTML. On-the-fly generation is only when required -->
<main>
{for $doc at $i in $page:sorted
where $i > ($p - 1) * $page:items and $i <= $p * $page:items
let $html := if ($xpath != '' and $entry = $doc/@xml:id)
then abv-m:mark-element(
doc(`abaevdict_{$lang}/xml/{$doc/@xml:id}.xml`),
$xpath) => abv-m:make-html($lang)
else
let $sd := session:get('searchData')
let $sn := session:get('searchN')
return if (exists($sd) and $sd($sn)('entry') = $doc/@xml:id)
then abv-m:make-html($sd($sn)('tei'), $lang)
else doc(`abaevdict_{$lang}/html/{$doc/@xml:id}.html`)
return <div class="abv-lex">{(
(: Block with icons to the left of entry (floating) :)
<div class="icons">
{
<a href="dict/{$doc/@xml:id}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-link-45deg" viewBox="0 0 16 16">
<path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/>
<path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/>
</svg>
</a>,
if (doc('abaevdict_index/mentioned_en.xml')/lang-index
/lang[@id != 'os' and
not(starts-with(@id,'os-'))]/word/entry[@id=string($doc/@xml:id)])
then <a class="abv-map" data-abv-entry="{$doc/@xml:id}">
<img src="/static/map.png"></img>
</a>}</div>,
$html)}</div>
}
</main>
<!-- Footer for page navigation -->
<footer>
<nav class="pagination">
<ul>
<li>
{if ($p > 1) then <a href="?page={$p - 1}">Previous page</a> else ()}
</li>
</ul>
<!-- <ul><li>Page {$p} of {$page:total}</li></ul> -->
<ul class="abv-nextpage">
<li>
{if ($p < $page:total) then
<a class="pagination__next" href="?page={$p + 1}">Next page</a> else ()}
</li>
</ul>
</nav>
</footer>
<!-- Modal for map display -->
<dialog id="modal_map">
<article>
<header>
<button class="abv-close-map" aria-label="Close" rel="prev">
</button>
<p>{if ($lang = 'ru') then 'Карта' else 'Map'}</p>
</header>
<p>
<div id="map_display" style="width:100%;height:80%;"></div>
</p>
<footer><button class="abv-close-map">{if ($lang = 'ru') then 'Закрыть' else 'Close'}</button></footer>
</article>
</dialog>
<!-- Modal for search results -->
{if (exists(session:get('searchData'))) then
<dialog id="modal_searchResults">
<article>
<header>
<button class="abv-close-search" aria-label="Close" rel="prev"/>
<p>{if ($lang = 'ru') then 'Результаты поиска'
else 'Search results'}</p>
</header>
<table>
<thead>
<tr>
<td>{if ($lang = 'ru') then '№' else 'No.'}</td>
<td>{if ($lang = 'ru') then 'Лемма' else 'Lemma'}</td>
<td>{if ($lang = 'ru') then 'Фрагмент' else 'Fragment'}</td>
</tr>
</thead>
<tbody>
{
for $res in session:get('searchData')?*
count $pos
return <tr>
<td>{$pos}</td>
<td>
<a href="./search/position?p={$pos}">
{
abv-m:entry-form-by-id($res('entry'))
}
</a>
</td>
<td>
{
$res('fragment')
}
</td>
</tr>
}
</tbody>
</table>
<footer>
<button class="abv-close-search">{if ($lang = 'ru') then 'Закрыть' else 'Close'}</button>
</footer>
</article>
</dialog>}
</body>
</html>
};
(: == ENTRY BY ID == :)
(: English as default language :)
declare %rest:path("dict/{$id}")
%rest:query-param("xpath", "{$xpath}", '')
%output:method("html")
%output:html-version('5')
function page:by-id($id, $xpath) {
page:by-id('en', $id, $xpath)
};
declare %rest:path("{$lang}/dict/{$id}")
%rest:query-param("xpath", "{$xpath}", '')
%output:method("html")
%output:html-version('5')
function page:by-id($lang, $id, $xpath) {
(: let $doc-index := index-of($page:sorted, $page:sorted[@xml:id=`{$id}`]) :)
let $doc-index := index-where($page:sorted, fn { ./@xml:id=$id })
let $pagenum := ceiling($doc-index div $page:items)
return web:redirect('../dict',
map:merge(({'page': $pagenum,
'entry': web:decode-url($id)},
if ($xpath != '') then {'xpath': $xpath})),
web:decode-url($id))
};
(: =============================================================== :)
(: ======================= THE INDEX ============================= :)
(: =============================================================== :)
(: Mentioned index :)
declare %rest:path("{$lang}/index")
%output:method("html")
%output:html-version('5')
function page:index($lang) {
let $mlangs := doc(`abaevdict_index/langnames.xml`)/csv[1]/record
return
<html>
{page:head('HEDO Index', <script src="/static/abaev-index.js"></script>)}
<body>
{page:header($lang, `../{page:invert-lang($lang)}/index`)}
<main>
<div class="index">
<div class="langs">
<label>{if ($lang = 'ru') then 'Языки' else 'Languages'}</label>
<select id="abv-select-lang" size="99999">
{
for $mlang in $mlangs
let $mlang-id := $mlang/code/text()
let $mlang-name := if ($lang = 'ru') then $mlang/ru/text() else $mlang/en/text()
where $mlang-name != ''
order by $mlang-name
return
<option value="{$mlang-id}">
{
$mlang-name
}
</option>
}
</select>
</div>
<div class="forms">
<Label>{if ($lang = 'ru') then 'Формы' else 'Forms'}</Label>
<select id="abv-select-word" size="99999">
</select>
</div>
<div class="entries">
<Label>{if ($lang = 'ru') then 'Лексемы' else 'Lexemes'}</Label>
<select id="abv-select-entry" size="99999">
</select>
</div>
</div>
<div class="index-submit">
<button id="btn-index-submit" hidden="1">{if ($lang = 'ru') then 'К форме' else 'Go to form'}</button>
</div>
</main>
</body>
</html>
};
(: Default to English :)
declare %rest:path("index")
%output:method("html")
%output:html-version('5')
function page:index() {
page:index('en')
};

View file

@ -0,0 +1,96 @@
module namespace dict = 'http://ossetic-studies.org/ns/abaevdict-site/dictionary';
import module namespace shared = 'http://ossetic-studies.org/ns/abaevdict-site/shared' at '../shared/shared.xq';
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at '../../abv-mod.xqm';
(: Sidebar with entry list. Requires abaev.js to be loaded. :)
declare function dict:sidebar($lang as xs:string) {
(
<button id="open-leftbar">
{html:doc('../../../static/chevron-right.svg')}
</button>,
<button id="close-leftbar">
{html:doc('../../../static/chevron-left.svg')}
</button>,
<aside id="leftbar">
<fieldset>
<input type="text" id="filter-entries"
placeholder="{if ($lang = 'ru') then 'Быстрый фильтр' else 'Quick filter…'}"/>
<label>
<input id="show-re" type="checkbox" role="switch"></input>
{if ($lang = 'ru') then 'Производные' else 'Show subentries'}
</label>
</fieldset>
<nav id="entrylist"></nav> <!-- To be filled dynamically in js -->
</aside>
)
};
declare function dict:main-view($lang as xs:string,
$p as xs:integer,
$xpath as xs:string,
$entry as xs:string) {
(dict:sidebar($lang),
<main>
{for $doc at $i in $shared:sorted
where $i > ($p - 1) * $shared:items-per-page and $i <= $p * $shared:items-per-page
let $html := if ($xpath != '' and $entry = $doc/@xml:id)
then abv-m:mark-element(
doc(`abaevdict_{$lang}/xml/{$doc/@xml:id}.xml`),
$xpath) => abv-m:make-html($lang)
else
let $sd := session:get('searchData')
let $sn := session:get('searchN')
return if (exists($sd) and $sd($sn)('entry') = $doc/@xml:id)
then abv-m:make-html($sd($sn)('tei'), $lang)
else doc(`abaevdict_{$lang}/html/{$doc/@xml:id}.html`)
return <div class="abv-lex">{(
(: Block with icons to the left of entry (floating) :)
<div class="icons">
{
<a href="dict/{$doc/@xml:id}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-link-45deg" viewBox="0 0 16 16">
<path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/>
<path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/>
</svg>
</a>,
if (doc('abaevdict_index/mentioned_en.xml')/lang-index
/lang[@id != 'os' and
not(starts-with(@id,'os-'))]/word/entry[@id=string($doc/@xml:id)])
then <a class="abv-map" data-abv-entry="{$doc/@xml:id}">
<img src="/static/map.png"></img>
</a>}</div>,
$html)}</div>
}
</main>,
<footer>
<nav class="pagination">
<ul>
<li>
{if ($p > 1) then <a href="?page={$p - 1}">Previous page</a> else ()}
</li>
</ul>
<!-- <ul><li>Page {$p} of {$page:total}</li></ul> -->
<ul class="abv-nextpage">
<li>
{if ($p < $shared:total) then
<a class="pagination__next" href="?page={$p + 1}">Next page</a> else ()}
</li>
</ul>
</nav>
</footer>,
<dialog id="modal_map">
<article>
<header>
<button class="abv-close-map" aria-label="Close" rel="prev">
</button>
<p>{if ($lang = 'ru') then 'Карта' else 'Map'}</p>
</header>
<p>
<div id="map_display" style="width:100%;height:80%;"></div>
</p>
<footer><button class="abv-close-map">{if ($lang = 'ru') then 'Закрыть' else 'Close'}</button></footer>
</article>
</dialog>)
};

178
xq/site/components/home.xq Normal file
View file

@ -0,0 +1,178 @@
module namespace home = 'http://ossetic-studies.org/ns/abaevdict-site/home';
declare function home:content($lang as xs:string) {
switch($lang)
case "ru" return (
<p>
Вы находитесь на сайте электронной двуязычной версии
<a href="https://ironau.ru/iesoja.html">Историко-этимологического
словаря осетинского языка</a> (ИЭСОЯ) В.&#160;И.&#160;Абаева.
Оцифровка и перевод словаря выполнены группой сотрудников, студентов и аспирантов
<a href="https://tipl.philol.msu.ru/">отделения теоретической и
прикладной лингвистики</a> МГУ имени М.&#160;В.&#160;Ломоносова и
<a href="https://iling-ran.ru/web/ru/departments/indo-european/iranian">
сектора иранских языков</a> Института языкознания РАН.
</p>,
<p>
Электронная версия словаря и перевод III и IV томов выполнены за счёт
гранта Российского научного фонда №
<a href="https://rscf.ru/project/22-28-01639/">22-28-01639</a>.
</p>,
<p>
Мы выражаем особую благодарность Амирхану Михайловичу Торчинову, который
выступил инициатором этого проекта и продолжает оказывать нам неоценимую
поддержку в нашей работе. Мы также благодарны московской осетинской общине за
поддержку перевода III томов словаря и помощь в печатном издании их английской
версии, в особенности Александру Тотоонову, Валерию Дзгоеву, Олегу Пухову,
Владиславу Хаблиеву, Руслану Бестолову, Виталию Даурову, Игорю Дзуцеву, Марине
Каболовой, Владимиру Туганову, Зоинбеку Абаеву, Виктору Джиоеву, Владимиру
Бароеву и Борису Базаеву.
</p>,
<p>
Электронная версия словаря основана на адаптации системы XML-разметки
<a href="https://tei-c.org/">TEI P5</a>. Сервер базы данных использует систему
<a href="https://basex.org">BaseX</a>. Модель разметки словаря и реализация её
программного отображения разработаны О.&#160;И.&#160;Беляевым. Все исходные
данные доступны в <a href="https://code.cucurri.ru/abaevdict">репозиториях</a>
проекта.
</p>,
<p>
Дизайн и эмблема сайта выполнены А.&#160;А.&#160;Осиповой.
</p>,
<p>
В предыдущей версии электронного словаря использовалась реляционная
<a href="https://ossetic.iranic.space">модель</a> представления данных,
разработанная Ю.&#160;Ю.&#160;Макаровым (ИЯз РАН, Кембриджский университет)
на платформе OnLex.
</p>,
<h5>Как ссылаться</h5>,
<blockquote>Belyaev, Oleg, Irina Khomchenkova, Julia Sinitsyna, Vadim Dyachkov. Digitizing print dictionaries using TEI: The Abaev Dictionary Project // <i>IWCLUL 2021: The Seventh International Workshop on Computational Linguistics of Uralic Languages. Proceedings of the Workshop</i>. Stroudsburg, USA: Association for Computational Linguistics, 2021. P. 1219.
</blockquote>,
<h5>Участники проекта</h5>,
<table>
<tbody>
<tr>
<th scope="row">Научный руководитель</th>
<td>О.&#160;И.&#160;Беляев</td>
<td>(МГУ имени
М.&#160;В.&#160;Ломоносова, ИЯз РАН)</td>
</tr>
<tr>
<th scope="row">Основные участники</th>
<td>Ю.&#160;В.&#160;Синицына</td>
<td>(МГУ имени
М.&#160;В.&#160;Ломоносова)</td>
</tr>
<tr>
<td></td>
<td>И.&#160;А.&#160;Хомченкова</td>
<td>(МГУ имени М.&#160;В.&#160;Ломоносова, ИРЯ РАН)</td>
</tr>
<tr>
<th>Оцифровка и перевод</th>
<td>В.&#160;В.&#160;Дьячков</td>
<td>(ИЯз РАН)</td>
</tr>
<tr>
<td></td>
<td>А.&#160;А.&#160;Осипова</td>
<td>(МГУ имени М.&#160;В.&#160;Ломоносова)</td>
</tr>
<tr>
<td></td>
<td>А.&#160;О.&#160;Бадеев</td>
<td>(ИЯз РАН, НИУ ВШЭ)</td>
</tr>
<tr>
<td></td>
<td>Д.&#160;А.&#160;Алексеев</td>
<td>(МГУ имени М.&#160;В.&#160;Ломоносова)</td>
</tr>
</tbody>
</table>)
default return
(
<p>
This is the website of the electronic version of V.&#160;I.&#160;Abaev&#39;s
<a href="https://ironau.ru/iesoja.html">Historical-Etymological Dictionary
of Ossetic</a> (HEDO). Its digitization and English translation have been
carried out by staff members and students of the
<a href="https://tipl.philol.msu.ru/">department of theoretical and applied
linguistics</a>, Lomonosov Moscow State University, and the <a
href="https://iling-ran.ru/web/ru/departments/indo-european/iranian">
Department of Iranian languages</a>, Institute of Linguistics RAS.
</p>,
<p>
The electronic version of the dictionary and the translation of vols. 3 and 4
have been supported by the Russian Science Foundation, project no.
<a href="https://rscf.ru/project/22-28-01639/">22-28-01639</a>.
</p>,
<p>
We are deeply grateful to Amirkhan Torchinov, who has been the initiator of this
project and is still providing us with invaluable support in our work. We are
also grateful to the Ossetian community of Moscow for their support of the
translation of vols. 1 and 2 and the help in publishing its preliminary English
version; in particular, to Alexander Totoonov, Valery Dzgoev, Oleg Pukhov,
Vladislav Khabliev, Ruslan Bestolov, Vitaly Daurov, Igor Dzutsev, Marina
Kabolova, Vladimir Tuganov, Zoinbek Abaev, Viktor Dzhioev, Vladimir
Baroev and Boris Bazaev.
</p>,
<p>
The electronic edition of the dictionary is based on an adaptation of the
XML-based <a href="https://tei-c.org/">TEI P5</a> markup system. The database
server runs on <a href="https://basex.org">BaseX</a>. The markup model and its
software realization have been developed by Oleg Belyaev. All sources are
available in our <a href="https://code.cucurri.ru/abaevdict">repositories</a>.
</p>,
<p>
Site design and logo are by Anna Osipova.
</p>,
<p>
The previous version of the electronic dictionary used a relational
data <a href="https://ossetic.iranic.space">model</a> developed by Yury Makarov
(IL RAS, Cambridge University) using the OnLex platform.
</p>,
<h5>How to cite</h5>,
<blockquote>Belyaev, Oleg, Irina Khomchenkova, Julia Sinitsyna, Vadim Dyachkov. Digitizing print dictionaries using TEI: The Abaev Dictionary Project // <i>IWCLUL 2021: The Seventh International Workshop on Computational Linguistics of Uralic Languages. Proceedings of the Workshop</i>. Stroudsburg, USA: Association for Computational Linguistics, 2021. P. 1219.
</blockquote>,
<h5>Project participants</h5>,
<table>
<tbody>
<tr>
<th scope="row">Project leader</th>
<td>Oleg Belyaev</td>
<td>(Lomonosov MSU, IL RAS)</td>
</tr>
<tr>
<th scope="row">Core team</th>
<td>Irina Khomchenkova</td>
<td>(Lomonosov MSU, Vinogradov RLI RAS)</td>
</tr>
<tr>
<td></td>
<td>Julia Sinitsyna</td>
<td>(Lomonosov MSU)</td>
</tr>
<tr>
<th>Digitization and translation</th>
<td>Vadim Dyachkov</td>
<td>(IL RAS, LLACAN CNRS)</td>
</tr>
<tr>
<td></td>
<td>Anna Osipova</td>
<td>(Lomonosov MSU)</td>
</tr>
<tr>
<td></td>
<td>Artyom Badeev</td>
<td>(IL RAS, HSE University)</td>
</tr>
<tr>
<td></td>
<td>Danil Alekseev</td>
<td>(Lomonosov MSU)</td>
</tr>
</tbody>
</table>)
};

View file

@ -0,0 +1,49 @@
module namespace index = 'http://ossetic-studies.org/ns/abaevdict-site/index';
import module namespace shared = 'http://ossetic-studies.org/ns/abaevdict-site/shared' at '../shared/shared.xq';
declare function index:content($lang as xs:string) {
let $mlangs := doc(`abaevdict_index/langnames.xml`)/csv[1]/record
return
<html>
{shared:head('HEDO Index', <script src="/static/abaev-index.js"></script>)}
<body>
{shared:header($lang, `../{shared:invert-lang($lang)}/index`)}
<main>
<div class="index">
<div class="langs">
<label>{if ($lang = 'ru') then 'Языки' else 'Languages'}</label>
<select id="abv-select-lang" size="99999">
{
for $mlang in $mlangs
let $mlang-id := $mlang/code/text()
let $mlang-name := if ($lang = 'ru') then $mlang/ru/text() else $mlang/en/text()
where $mlang-name != ''
order by $mlang-name
return
<option value="{$mlang-id}">
{
$mlang-name
}
</option>
}
</select>
</div>
<div class="forms">
<Label>{if ($lang = 'ru') then 'Формы' else 'Forms'}</Label>
<select id="abv-select-word" size="99999">
</select>
</div>
<div class="entries">
<Label>{if ($lang = 'ru') then 'Лексемы' else 'Lexemes'}</Label>
<select id="abv-select-entry" size="99999">
</select>
</div>
</div>
<div class="index-submit">
<button id="btn-index-submit" hidden="1">{if ($lang = 'ru') then 'К форме' else 'Go to form'}</button>
</div>
</main>
</body>
</html>
};

View file

@ -0,0 +1,31 @@
module namespace search = 'http://ossetic-studies.org/ns/abaevdict-site/search-modal';
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at '../../abv-mod.xqm';
(: Modal that appears on all pages, with search results :)
declare function search:content($lang as xs:string) {
if (exists(session:get('searchData'))) then
<dialog id="modal_searchResults">
<article>
<header>
<button class="abv-close-search" aria-label="Close" rel="prev"/>
<p>{if ($lang = 'ru') then 'Результаты поиска'
else 'Search results'}</p>
</header>
<table>
<thead>
<tr>
<td>{if ($lang = 'ru') then '№' else 'No.'}</td>
<td>{if ($lang = 'ru') then 'Лемма' else 'Lemma'}</td>
<td>{if ($lang = 'ru') then 'Фрагмент' else 'Fragment'}</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<footer>
<button class="abv-close-search">{if ($lang = 'ru') then 'Закрыть' else 'Close'}</button>
</footer>
</article>
</dialog>
};

View file

@ -2,7 +2,7 @@ declare namespace api = 'http://ossetic-studies.org/ns/abaevdict-api';
declare namespace tei = "http://www.tei-c.org/ns/1.0";
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at './abv-mod.xqm';
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at '../abv-mod.xqm';
(: ======================================================= :)
(: ==================== API STUFF ======================== :)
@ -199,5 +199,12 @@ declare %rest:path("{$db-lang}/api/search")
$query as xs:string) {
abv-m:search($db-lang,$type,$query)
};
(: Get current search results :)
declare %rest:path("{$db-lang}/api/search/current")
%rest:GET
function api:search-current($db-lang as xs:string) {
session:get('searchData')
};
0

123
xq/site/restx_dict.xq Normal file
View file

@ -0,0 +1,123 @@
module namespace page = 'http://ossetic-studies.org/ns/abaevdict-site';
declare namespace tei = "http://www.tei-c.org/ns/1.0";
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at '../abv-mod.xqm';
import module namespace shared = 'http://ossetic-studies.org/ns/abaevdict-site/shared' at './shared/shared.xq';
(: COMPONENTS :)
(: To make the codebase more manageable, components are split into separate
files included as modules. :)
import module namespace home = 'http://ossetic-studies.org/ns/abaevdict-site/home' at './components/home.xq';
import module namespace dict = 'http://ossetic-studies.org/ns/abaevdict-site/dictionary' at './components/dictionary.xq';
import module namespace index = 'http://ossetic-studies.org/ns/abaevdict-site/index' at './components/index.xq';
(: =============================================================== :)
(: ============================ FAVICON ========================== :)
(: =============================================================== :)
declare %rest:path("/favicon.ico")
%rest:GET
function page:favicon() {
fetch:binary('../../static/favicon.ico')
};
(: =============================================================== :)
(: ======================= HOMEPAGE ============================== :)
(: =============================================================== :)
declare %rest:path("{$lang}")
%output:method("html")
%output:html-version('5')
function page:zero($lang) {
page:home($lang)
};
declare %rest:path("/")
%output:method("html")
%output:html-version('5')
function page:zero() {
page:zero('en')
};
declare %rest:path("{$lang}/home")
%output:method("html")
%output:html-version('5')
function page:home($lang) {
<html>
{shared:head(if ($lang='ru') then 'ИЭСОЯ — Главная' else 'Abaevdict — Home',())}
<body>
{shared:header($lang,`../{shared:invert-lang($lang)}/home`)}
<main>
{
home:content($lang)
}
</main>
</body>
</html>
};
declare %rest:path("home")
%output:method("html")
%output:html-version('5')
function page:home() {
page:home('en')
};
(: =============================================================== :)
(: ======================= THE DICTIONARY ======================== :)
(: =============================================================== :)
(: If no language defined, default to English :)
declare %rest:path("dict")
%output:method("html")
%output:html-version('5')
function page:dict() {
web:forward('/en/dict')
};
(: The main dictionary view :)
declare %rest:path("{$lang}/dict")
%rest:query-param("page","{$p}", 1)
%rest:query-param("xpath","{$xpath}", '')
%rest:query-param("entry","{$entry}", '')
%output:method("html")
%output:html-version('5')
function page:dict($lang, $p, $xpath, $entry) {
<html>
{shared:head('HEDO Dictionary',
(<script src="/static/infinite-scroll.pkgd.min.js">
</script>,
<script src="/static/plotly-3.0.1.min.js" charset="utf-8"></script>,
<script src="/static/abaev.js"></script>))}
<body>
{(
shared:header($lang,`../{shared:invert-lang($lang)}/dict`),
dict:main-view($lang, $p, $xpath, $entry)
)}
</body>
</html>
};
(: =============================================================== :)
(: ======================= THE INDEX ============================= :)
(: =============================================================== :)
(: Mentioned index :)
declare %rest:path("{$lang}/index")
%output:method("html")
%output:html-version('5')
function page:index($lang) {
index:content($lang)
};
(: Default to English :)
declare %rest:path("index")
%output:method("html")
%output:html-version('5')
function page:index() {
page:index('en')
};

View file

@ -0,0 +1,31 @@
module namespace entry = 'http://ossetic-studies.org/ns/abaevdict-site/entrypoints';
import module namespace shared = 'http://ossetic-studies.org/ns/abaevdict-site/shared' at './shared/shared.xq';
(: This module includes various ways of accessing dictionary resources by
their identifiers. Right now, this only applies to entries, but may apply to
other things as well :)
(: == ENTRY BY ID == :)
(: English as default language :)
declare %rest:path("dict/{$id}")
%rest:query-param("xpath", "{$xpath}", '')
%output:method("html")
%output:html-version('5')
function entry:by-id($id, $xpath) {
entry:by-id('en', $id, $xpath)
};
declare %rest:path("{$lang}/dict/{$id}")
%rest:query-param("xpath", "{$xpath}", '')
%output:method("html")
%output:html-version('5')
function entry:by-id($lang, $id, $xpath) {
let $doc-index := index-where($shared:sorted, fn { ./@xml:id=$id })
let $pagenum := ceiling($doc-index div $shared:items-per-page)
return web:redirect('../dict',
map:merge(({'page': $pagenum,
'entry': web:decode-url($id)},
if ($xpath != '') then {'xpath': $xpath})),
web:decode-url($id))
};

75
xq/site/restx_search.xq Normal file
View file

@ -0,0 +1,75 @@
module namespace search = 'http://ossetic-studies.org/ns/abaevdict-site/search';
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at '../abv-mod.xqm';
import module namespace entry = 'http://ossetic-studies.org/ns/abaevdict-site/entrypoints' at './restx_entrypoints.xq';
(: =============================================================== :)
(: ======================= SEARCH ENTRYPOINT ===================== :)
(: =============================================================== :)
declare %rest:path("/search/{$path=.+}")
%rest:GET
function search:search-default($path as xs:string) {
web:forward(`/en/search/{$path}`)
};
declare %rest:path("{$lang}/search/new")
%rest:query-param("searchType", "{$searchType}", 'full')
%rest:query-param("searchQuery", "{$searchQuery}")
%output:method("html")
%output:html-version('5')
function search:search($lang, $searchType, $searchQuery) {
let $sd := abv-m:search($lang,$searchType,$searchQuery)
let $r1 := session:set('searchType', $searchType)
let $r2 := session:set('searchQuery', $searchQuery)
let $r3 := session:set('searchN', 1)
let $r4 := session:set('searchData', $sd)
return
if(array:size($sd) > 0) then
entry:by-id($lang,
$sd(1)('entry'),
'')
else
web:redirect('../search/clear')
};
declare %rest:path("{$lang}/search/next")
%output:method("html")
%output:html-version('5')
function search:search-next($lang) {
let $n as xs:integer := session:get('searchN')+1
let $r1 := session:set('searchN', $n)
let $sd := session:get('searchData')
return entry:by-id($lang, $sd($n)('entry'), ())
};
declare %rest:path("{$lang}/search/prev")
%output:method("html")
%output:html-version('5')
function search:search-prev($lang) {
let $n as xs:integer := session:get('searchN')-1
let $r1 := session:set('searchN', $n)
let $sd := session:get('searchData')
return entry:by-id($lang, $sd($n)('entry'), ())
};
declare %rest:path("{$lang}/search/position")
%rest:query-param("p", "{$p}")
%output:method("html")
%output:html-version('5')
function search:search-position($lang, $p as xs:integer) {
let $r1 := session:set('searchN', $p)
let $sd := session:get('searchData')
return entry:by-id($lang, $sd($p)('entry'), ())
};
declare %rest:path("{$lang}/search/clear")
%output:method("html")
%output:html-version('5')
function search:clearSearch($lang) {
let $r1 := session:delete('searchType')
let $r2 := session:delete('searchQuery')
let $r3 := session:delete('searchN')
let $r4 := session:delete('searchData')
return web:redirect('../dict')
};

165
xq/site/shared/shared.xq Normal file
View file

@ -0,0 +1,165 @@
module namespace shared = 'http://ossetic-studies.org/ns/abaevdict-site/shared';
declare namespace tei = "http://www.tei-c.org/ns/1.0";
import module namespace search = 'http://ossetic-studies.org/ns/abaevdict-site/search-modal' at '../components/search.xq';
(: =========================================================== :)
(: ================== GLOBAL VARIABLES ======================= :)
(: =========================================================== :)
(: Number of items per page :)
declare variable $shared:items-per-page as xs:integer := 20;
(: These variables are declared in module scope because they are needed in
different places, but they are still dynamically evaluated :)
declare variable $shared:lookup := doc('abaevdict_index/lookup.xml')/tei:table/tei:entry;
declare variable $shared:lookup-all := doc('abaevdict_index/lookup.xml')/tei:table/*;
declare variable $shared:total := ceiling(count($shared:lookup) div $shared:items-per-page);
(: This is still called sorted, because I haven't changed the code.
But it's now equal to lookup, because the lookup itself is now sorted. :)
declare variable $shared:sorted := $shared:lookup;
(: =============================================================== :)
(: ======================= COMMON STUFF ========================== :)
(: =============================================================== :)
declare function shared:head($title as xs:string, $script as node()* := ()) {
<head>
<script src="/static/jquery-3.7.1.min.js">
</script>
<script src="/static/abaev-global.js">
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"/>
<link rel="stylesheet" href="{web:create-url('/static/abaev-html.css')}"/>
{$script}
<title>{$title}</title>
</head>
};
(: Navigation header and search dialog :)
declare function shared:header($lang as xs:string, $href-other as xs:string) {
(
<header>
<nav>
<ul class="abv-brand">
<li><img class="abv-logo" src="/static/Abaev_logo.png" alt="Abaevdict logo"></img>
<strong>{
switch($lang)
case 'ru' return 'ИЭСОЯ'
default return `Abaevdict`
}</strong><i> β</i></li>
<li>{
(element {if ($lang = 'ru') then 'mark' else 'a'}{
if ($lang != 'ru') then
(attribute class {'link'},
attribute data-href {`{$href-other}`}) else (),
'ru'
}, ' / ',
element {if ($lang = 'ru') then 'a' else 'mark'}{
if ($lang = 'ru') then
(attribute class {'link'},
attribute data-href {`{$href-other}`}) else (),
'en'
})
}
</li>
</ul>
<ul>
<li>
<form role="search" action="./search/new" id="abv-search">
{if (session:get('searchQuery')) then
<button class="link" data-href="./search/clear" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
</svg>
</button>
else
<select name="searchType" required="1">
<option selected="1" value="full">{if ($lang = 'ru')
then 'Везде' else 'All'}</option>
<option value="form">{if ($lang = 'ru')
then 'Формы' else 'Forms'}</option>
<option value="sense">{if ($lang = 'ru')
then 'Значения' else 'Senses'}</option>
<option value="example">{if ($lang = 'ru')
then 'Примеры' else 'Examples'}</option>
<option value="translation">{if ($lang = 'ru')
then 'Переводы' else 'Translations'}</option>
<option value="mentioned">{if ($lang = 'ru')
then 'Цит. формы' else 'Mentioned'}</option>
<option value="gloss">{if ($lang = 'ru')
then 'Глоссы' else 'Glosses'}</option>
<option value="etym">{if ($lang = 'ru')
then 'Этимологии' else 'Etymology'}</option>
</select>
}
<input name="searchQuery"
placeholder="{if ($lang = 'ru') then 'Искать' else 'Search'}"
value="{session:get('searchQuery')}"
aria-label="Search">
{if (exists(session:get('searchQuery'))) then
attribute disabled {"1"} }
</input>
{if (not(session:get('searchQuery'))) then
<button type="submit">
<svg id="abv-search-icon" xmlns="http://www.w3.org/2000/svg"
width="16" height="16"
viewBox="0 0 16 16"
fill="currentColor" class="bi bi-search">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
</svg>
</button>
else
(
if (session:get('searchN') > 1) then
<button type="button" class="link" data-href="./search/prev">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8"/>
</svg>
</button>,
<button type="button" id="abv-btn-searchResults">{session:get('searchN')}/{array:size(session:get('searchData'))}</button>
,
if (session:get('searchN') and session:get('searchN') < array:size(session:get('searchData'))) then
<button type="button" class="link" data-href="./search/next">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8"/>
</svg>
</button>
)
}
</form>
</li>
<li><a href="./home">{
switch($lang)
case 'ru' return 'Главная'
default return 'Home'
}
</a></li>
<li><a href="./dict">{switch($lang)
case 'ru' return 'Словарь'
default return 'Dictionary'
}
</a></li>
<li><a href="./index">{switch($lang)
case 'ru' return 'Указатель'
default return 'Index'
}</a></li>
<li><a href="#">{switch($lang)
case 'ru' return 'Литература'
default return 'References'
}</a></li>
</ul>
</nav>
</header>,
search:content($lang)
)
};
declare function shared:invert-lang($lang as xs:string) as xs:string {
if($lang = 'en') then 'ru' else 'en'
};