Compare commits

...
Sign in to create a new pull request.

9 commits
main ... main

Author SHA1 Message Date
Oleg Belyaev
9151b9eb32 handle footnotes correctly, hide internal notes 2025-03-30 20:01:15 +03:00
Oleg Belyaev
0e7fb74acf interpret tei:lb as new paragraph 2025-03-30 17:42:03 +03:00
Oleg Belyaev
ac9d112c13 hash synchronization when changing lang 2025-03-30 16:39:50 +03:00
Oleg Belyaev
377c9d5b8b sort search resutls 2025-03-30 13:12:26 +03:00
Oleg Belyaev
abf143eeab fixed map 2025-03-30 01:25:51 +03:00
Oleg Belyaev
9109c93e6a fixed subentry display 2025-03-30 00:25:25 +03:00
Oleg Belyaev
e635581228 fixed online html generation 2025-03-30 00:21:27 +03:00
Oleg Belyaev
117dead94a fixed new html gen 2025-03-30 00:03:45 +03:00
Oleg Belyaev
cd513bd796 transition to htmx, made truly infinite scroll 2025-03-29 23:57:33 +03:00
26 changed files with 934 additions and 1269 deletions

View file

@ -4,6 +4,9 @@
This repository contains the webapp files to be used with [BaseX](https://basex.org/) running as a webapp, plus some scripts to convert between different XML representations.
This is a rewrite that dispenses with most JS code, apart from some basic logic that is directly encoded in attributes.
This is achieved by using HTMX.
Original code written by Oleg Belyaev in 20242025.
A public version of this database is currently available [here](https://abaev.belyaev.io/dict).

View file

@ -1,133 +0,0 @@
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;
// Link elements that do not have href out of the box
$('.link').on("click", function () {
window.location=$(this)[0].dataset.href+window.location.search+window.location.hash;
});
$('#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

@ -14,7 +14,7 @@ Root
:root {}
html {}
body {}
body {height: 100vh;}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@ -30,8 +30,7 @@ h5 {}
h6 {}
body > header {
position: sticky;
top: 0;
max-height: 10vh;
background-color: #FCF9F5;
margin-left: 0;
margin-right: 0;
@ -39,8 +38,8 @@ body > header {
max-width: 100vw;
/* border-bottom-width: thin;
border-bottom-style: solid;*/
margin-bottom: 10px !important;
padding-bottom: 0px !important;
margin-bottom: 0px !important;
padding-top: 0px !important;
/* padding-right: 1em !important;
padding-left: 1em !important;*/
@ -69,6 +68,10 @@ body > header {
body>main {
max-width:950px
}
article.abv-entry {
display: grid;
grid-template-columns: minmax(10%,15%) minmax(0,5%) minmax(75%,90%);
}
}
@media (min-width:1280px) {
body>footer>nav,
@ -87,7 +90,9 @@ body > header {
body>header>nav,
body>footer>nav {
margin: auto;
margin-right: auto;
margin-left: auto;
max-height: 10vh;
--pico-primary: #8D0B02 !important;
--pico-primary-background: #8D0B02 !important;
--pico-primary-border: #8D0B02 !important;
@ -124,12 +129,10 @@ nav li.abv-menu-re {
filter: gray;
}
main {}
article.abv-entry {
display: grid;
grid-template-columns: minmax(0px,15%) 5% minmax(50%,80%);
align-items: baseline;
main {
height: 85vh;
overflow-y: scroll;
padding-top: 0 !important;
}
section {}
@ -292,6 +295,13 @@ table {}
caption {}
col:first-child {}
thead {}
table#abv-search-results thead {
padding-top: 0;
position: sticky;
top: 0;
}
tbody {}
tfoot {}
tr {}
@ -309,19 +319,17 @@ legend {}
form {}
header form {
margin-bottom: 0 !important;
/*table#abv-search-results {
height: 100%;
}
header form button {
padding-left: 1em !important;
padding-right: 1em !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
table#abv-search-results tbody {
height:100%;
overflow-y: scroll;
}*/
header form select {
max-width:35%;
form#abv-search select {
max-width:15%;
}
label {}
@ -395,6 +403,10 @@ div.index-submit {
float: right;
}
div.notes {
font-size: smaller;
}
optgroup {}
option {}
option:checked {}
@ -413,7 +425,6 @@ button#open-leftbar{
left: 0;
/*height: 1ex;*/
/*line-height: 1ex;*/
/*width: 1em;*/
}
button#close-leftbar{
@ -423,6 +434,23 @@ button#close-leftbar{
display: none;
}
@media (max-width: 1024px) {
button#open-leftbar {
width: 1em;
height: 10ex;
margin: 0 auto;
padding-left: 0;
padding-right: 0;
}
button#close-leftbar {
width: 1em;
height: 10ex;
margin: 0 auto;
padding-left: 0;
padding-right: 0;
}
}
/* This matches forms or form elements that are invalid *and* have been
interacted with. Note that:
1) You may need to add the `interacted` class to forms yourself
@ -466,28 +494,6 @@ dialog > article {
max-height: 85vh !important;
}
dialog#modal_searchResults > article {
padding-top: 0;
padding-bottom: 0;
}
dialog#modal_searchResults > article > table > thead {
position: sticky;
top: 0;
z-index: 1055;
}
dialog#modal_searchResults > article > header {
position: sticky;
top: 0;
z-index: 1055;
}
dialog#modal_searchResults > article > footer {
position: sticky;
bottom: 0;
z-index: 1055;
}
/*dialog#modal_searchResults > article > p > table > tbody {
height:100%;
overflow-y: scroll;
@ -534,30 +540,11 @@ span.abv-text-ul {
span.abv-text-sc {
font-variant-caps: small-caps;
}
span.abv-name {
font-variant-caps: small-caps;
}
/*
This file evolved from Natural Selection:
https://github.com/frontaid/natural-selection
*/
.icons{
text-align: center;
width: 1.5em;
float: left;
padding-right: 0.5vw;
}
.loader {
border: 2px solid #f3f3f3;
border-radius: 50%;
border-top: 2px solid #3498db;
width: 2ex;
height: 2ex;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View file

@ -1,73 +0,0 @@
$( document ).ready(function() {
$('#abv-select-lang').on('input', function() {
let $selected = $(this).children('option:selected');
let url = `./api/languages/${$selected.attr('value')}`;
fetch(url)
.then(res => res.json())
.then(out => {
let $select_word = $('#abv-select-word');
let $select_entry = $('#abv-select-entry');
$select_word.empty();
$select_entry.empty();
$('#btn-index-submit').prop('hidden',true);
for (w of out.words) {
$select_word.append(new Option(w.text,w.id))
}
})
.catch(err => console.log(err));
});
$('#abv-select-word').on('input', function() {
let $selected = $(this).children('option:selected');
let wordid = $selected.attr('value');
let lang = $('#abv-select-lang').children('option:selected').attr('value');
let url = `./api/languages/${lang}/words/${wordid}`;
fetch(url)
.then(res => res.json())
.then(out => {
let $select_entry = $('#abv-select-entry');
$select_entry.empty();
$('#btn-index-submit').prop('hidden',true);
for (e of out.entries) {
url_entry = `./api/entries/${e.id}`;
fetch(url_entry)
.then(res => res.json())
.then(out => {
let entrystring = '';
let glosses = [];
for (r of e.refs) {
if (r.glosses) {
for(g of r.glosses){
glosses.push(`${g}`);
}
}
}
if(glosses.length > 0) {
entrystring = `${glosses.join('; ')}: `
}
opt = $(`<option value="${out.xmlid}" data-abv-xpath="${e.refs[0].path}">${entrystring}<i>${out.form}</i>
${out.glosses.map(g => `${g}`).join(', ')}</option>`);
$select_entry.append(opt)
})
.catch(err => console.log(err));
}
})
.catch(err => console.log(err));
});
$('#abv-select-entry').on("input", function () {
$('#btn-index-submit').removeAttr('hidden');
})
function goToEntry() {
let $selected = $('#abv-select-entry').children('option:selected');
let entry = $selected.attr('value');
let path = $selected.attr('data-abv-xpath');
window.location.replace('./dict/' + entry + `?entry=${entry}&xpath=${path}`);
}
$('#abv-select-entry').on("dblclick", goToEntry);
$('#btn-index-submit').on("click", goToEntry);
});

View file

@ -1,186 +0,0 @@
$( document ).ready(function() {
// GLOBALS;
// Link elements that do not have href out of the box
$('.link').on("click", function () {
window.location=$(this)[0].dataset.href+window.location.search+window.location.hash;
});
$('#abv-search').on("submit", function () {
$('#abv-search-icon').replaceWith('<div class="loader"></div>');
});
// INFINITE SCROLL
// init Infinite Scroll
$('main').infiniteScroll({
path: '.pagination__next',
append: 'div.abv-lex',
//status: '.scroller-status',
//hideNav: '.pagination',
});
// MAP MODAL
// This event binding is done on page load and also whenever new entries are loaded.
bindMapOpen = function () {
let url = `./dict/map-info/${$(this).data('abv-entry')}`;
fetch(url)
.then(res => res.json())
.then(out => {
$('#modal_map').data('map',out);
$('#modal_map')[0].showModal();
})
.catch(err => console.log(err));
};
// Event to open map modal.
$('.abv-map').on("click", bindMapOpen);
$('main').on( 'append.infiniteScroll', function( event, body, path, response ) {
$('.abv-map').on("click", bindMapOpen);
});
// Close map modal
$('.abv-close-map').on("click", function () {
$('#modal_map')[0].close();
});
// Load map data when dialog opens
$('#modal_map').on("toggle", function () {
map = document.getElementById('map_display');
var layout = {
// title: {
// text: 'Canadian cities',
// font: {
// family: 'Droid Serif, serif',
// size: 16
// }
// },
geo: {
scope: 'world',
resolution: 50,
fitbounds: 'locations',
showrivers: true,
rivercolor: '#fff',
showlakes: true,
lakecolor: '#fff',
showland: true,
landcolor: '#EAEAAE',
showcountries: false,
subunitcolor: '#d3d3d3'
}
};
Plotly.newPlot(map, [$(this).data('map')], layout);
});
// HASH NAVIGATION
// If the hash is on the page, scroll a little bit above to account for top nav height
if ( $(decodeURI(document.location.hash)).length > 0) {
window.scrollBy(0,-$('header').height());
}
// If the hash is not on the page, go to the right page by using the ID interface
$( window ).on('hashchange', function() {
const hash = decodeURI(document.location.hash);
if ( hash.startsWith('#entry_') ) {
if ( $(hash).length == 0) {
window.location.replace(document.location.pathname + '/' + document.location.hash.substring(1));
}
else {
window.scrollBy(0,-$('header').height());
}
}
});
// ENTRY LIST SIDE PANEL
// Function to produce HTML for the entry list
function filterEntryList(list, filter, showSubentries) {
let listHtml = "";
for (entry of list) {
let entryID = entry.xmlid;
let entryForm = entry.form;
if (entryForm.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").startsWith(filter)
&& (!entry.subentry || showSubentries)
) {
listHtml += `<li id="link_${entryID}"${entry.subentry ? ' class="abv-menu-re"' : ''}"><a href="./dict/${entryID}">${entryForm}</a></li>`
}
}
listHtml = $(`<ul>${listHtml}</ul>`);
$("#entrylist").append(listHtml);
}
// Fetch entries from the server when page loads
var entries = [];
fetch('./api/entries')
.then(res => res.json())
.then(out => {
entries = out;
filterEntryList(entries, '', false);
})
.then(err => console.log(err));
// Function to scroll the entry list to the selected entry OR to the first entry displayed on the current page if there is no hash in the URL
function scrollView() {
var $container = $('#entrylist')
if (document.location.hash.length > 0) {
var $scrollTo = $(`#link_${$(decodeURI(document.location.hash)).attr('id')}`);
}
else {
var $scrollTo = $(`#link_${$('article:first').attr('id')}`);
}
$container.scrollTop(
$scrollTo.offset().top - $container.offset().top + $container.scrollTop())
}
// Click to open the offcanvas entry list
$( '#open-leftbar' ).on('click', function() {
$("#leftbar").show();
$("main")[0].style.marginLeft = "250px";
$("header nav")[0].style.marginLeft = "250px";
$("footer nav")[0].style.marginLeft = "250px";
$("#open-leftbar").hide();
$("#close-leftbar").show();
scrollView();
});
// Click to close the offcanvas entry list
$( '#close-leftbar' ).on('click', function() {
$("#leftbar").hide();
// $("#leftbar")[0].style.paddingLeft = null;
$("main")[0].style.marginLeft = null;
$("header nav")[0].style.marginLeft = null;
$("footer nav")[0].style.marginLeft = null;
$("#open-leftbar").show();
$("#close-leftbar").hide();
})
// Quick filter functionality
$('#filter-entries').on("keyup", function () {
$("#entrylist > ul").remove();
var value = $(this).val().toLowerCase();
filterEntryList(entries, value, $('#show-re').prop('checked'))
// var value = $(this).val().toLowerCase();
// if (value.length > 0) {
// $("#entrylist > ul > li:not([hidden])").filter(function () {
// return $(this).toggle($(this).children('a').text().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").startsWith(value))
// });
// }
// else {
// $("#entrylist > ul > li").show();
// }
//
// return false;
});
// Event handler to show subentries
$('#show-re').on("change", function () {
$("#entrylist > ul").remove();
filterEntryList(entries, $("#filter-entries").val().toLowerCase(), $(this).prop('checked'));
scrollView();
});
});

1
static/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
static/pico.classless.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,24 @@ module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod';
declare namespace tei = "http://www.tei-c.org/ns/1.0";
declare namespace abv = "http://ossetic-studies.org/ns/abaevdict";
(: HELPER FUNCTIONS :)
declare function abv-m:lookups($include-subentries as xs:boolean := false()) {
if (not($include-subentries)) then
doc('abaevdict_index/lookup.xml')/tei:table/tei:entry
else
doc('abaevdict_index/lookup.xml')/tei:table/*
};
declare function abv-m:collection($lang as xs:string,
$method as xs:string) {
collection(`abaevdict_{$lang}/{$method}`)
};
declare function abv-m:invert-lang($lang as xs:string) as xs:string {
if($lang = 'en') then 'ru' else 'en'
};
declare function abv-m:normalize-str($str as xs:string?)
as xs:string? {
$str => translate('&#xA;','') => normalize-space()

View file

@ -1,96 +0,0 @@
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>)
};

View file

@ -0,0 +1,184 @@
module namespace entries = 'http://ossetic-studies.org/ns/abaevdict-site/entries';
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';
(: MAIN DICTIONARY VIEW COMPONENT :)
(: GET INDIVIDUAL ENTRY :)
declare %rest:path("{$lang}/entries/{$id}")
%rest:query-param("query", "{$query}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function entries:entry($lang as xs:string, $id as xs:string, $query as xs:string?) {
if (count($query) = 0) then
doc(`abaevdict_{$lang}/html/{$id}.html`)
(: abv-m:make-html(doc(`abaevdict_{$lang}/xml/{$id}.xml`),$lang) :)
else
let $xml := doc(`abaevdict_{$lang}/xml/{$id}.xml`)/tei:entry[1]
let $marked := ft:mark(
xquery:eval(
`declare namespace tei = "http://www.tei-c.org/ns/1.0";
declare namespace abv = "http://ossetic-studies.org/ns/abaevdict";
.[{$query}]`, {'': $xml})
)
return abv-m:make-html($marked,$lang)
};
declare %rest:path("/entries/{$id}")
%rest:query-param("query", "{$query}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function entries:entry($id as xs:string, $query as xs:string?) {
entries:entry('en', $id, $query)
};
(: GET ENTRIES CATALOG :)
declare %rest:path("{$lang}/entries")
%rest:query-param("per-chunk", "{$per-chunk}", 5)
%rest:query-param("query", "{$query}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function entries:entries($lang as xs:string,
$per-chunk as xs:integer,
$query as xs:string?) {
let $docs := abv-m:collection($lang,'xml')/tei:entry
let $chosen := if($query) then
xquery:eval(`declare namespace tei = "http://www.tei-c.org/ns/1.0";
declare namespace abv = "http://ossetic-studies.org/ns/abaevdict";
.[{$query}]`,
{'': $docs})/string(@xml:id)
for tumbling window $w in abv-m:lookups()
start at $s when true()
only end at $e when $e - $s eq ($per-chunk - 1)
return
<div class="abv-chunk">
{
for $entry in $w
let $id := $entry/@xml:id
return
<div class="abv-lex"
hx-get="/{$lang}/entries/{$id}"
hx-target="find article"
hx-trigger="intersect once from:closest .abv-chunk"
hx-swap="outerHTML focus-scroll:true">
{if ($id = $chosen) then
attribute hx-vals {
`{'{'}"query": "{$query}"{'}'}`
}}
{
<article id="{$id}"
class="abv-entry">
</article>
}
</div>
}
</div>
};
declare %rest:path("/entries")
%rest:query-param("per-chunk", "{$per-chunk}", 5)
%rest:query-param("query", "{$query}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function entries:entries($per-chunk as xs:integer,
$query as xs:string?) {
entries:entries('en', $per-chunk, $query)
};
(: LIST OF ENTRIES FOR MENU :)
declare %rest:path("{$lang}/entrylist")
%rest:query-param("filter", "{$filter}")
%rest:query-param("subentries", "{$subentries}", false())
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function entries:entrylist($lang as xs:string,
$filter as xs:string?,
$subentries as xs:boolean?) {
<nav id="entrylist">
<ul>
{for $e in abv-m:lookups($subentries)
let $txt := $e/text()
let $id := if (name($e) = 'entry') then $e/@xml:id else $e/@corresp
return
if ($txt contains text {`{$filter}.*`} using wildcards at start) then
<li>
{
if (name($e) = 're') then
attribute class {'abv-menu-re'} else (),
<a href="#{$id}">
{$e/text()}
</a>
}
</li>
}
</ul>
</nav>
};
declare %rest:path("entrylist")
%rest:query-param("filter", "{$filter}")
%rest:query-param("subentries", "{$subentries}", false())
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function entries:entrylist($filter as xs:string?,
$subentries as xs:boolean?) {
entries:entrylist('en',$filter,$subentries)
};
(: QUICK ENTRY LIST SIDEBAR :)
declare function entries:sidebar($lang as xs:string) {
(<button id="close-leftbar"
hx-on-click="document.getElementById('leftbar').style.display = 'none';
document.getElementById('open-leftbar').style.display = 'block';
document.getElementById('close-leftbar').style.display = 'none'">
{html:doc('../../../static/chevron-left.svg')}
</button>,
<button id="open-leftbar"
hx-on-click="document.getElementById('leftbar').style.display = 'block';
document.getElementById('open-leftbar').style.display = 'none';
document.getElementById('close-leftbar').style.display = 'block'">
{html:doc('../../../static/chevron-right.svg')}
</button>,
<aside id="leftbar">
<fieldset>
<input type="text" id="filter-entries" name="filter"
hx-get="./entrylist" hx-target="#entrylist"
hx-swap="outerHTML"
hx-trigger="input changed"
placeholder="{if ($lang = 'ru') then 'Быстрый фильтр' else 'Quick filter…'}"/>
<label>
<input id="show-re"
type="checkbox"
role="switch"
name="subentries" value="1"
hx-get="./entrylist" hx-target="#entrylist"
hx-trigger="change"
hx-swap="outerHTML"/>
{if ($lang = 'ru') then 'Производные' else 'Show subentries'}
</label>
</fieldset>
<nav id="entrylist"
hx-get="./entrylist"
hx-swap="outerHTML"
hx-trigger="load">
</nav>
</aside>)
};

View file

@ -1,49 +0,0 @@
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,149 @@
module namespace index = 'http://ossetic-studies.org/ns/abaevdict-site/index';
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';
(: GET LANGUAGE LIST :)
declare %rest:path("{$lang}/index/languages")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function index:languages($lang as xs:string) {
let $mlangs := doc(`abaevdict_index/langnames.xml`)/csv[1]/record
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>
};
declare %rest:path("/index/languages")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function index:languages() {
index:languages('en')
};
(: FORM LIST :)
declare %rest:path("{$lang}/index/forms")
%rest:query-param("flang", "{$flang}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function index:forms($lang as xs:string, $flang as xs:string) {
let $ments := doc(`abaevdict_index/mentioned_{$lang}.xml`)
/lang-index[1]/lang[@id=$flang]/word
for $w in $ments
let $txt := string($w/@text)
return <option value="{$txt}">{$txt}</option>
};
declare %rest:path("index/forms")
%rest:query-param("flang", "{$flang}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function index:forms($flang as xs:string) {
index:forms('en',$flang)
};
(: RELATED ENTRY LIST :)
declare %rest:path("{$lang}/index/entries")
%rest:query-param("flang", "{$flang}")
%rest:query-param("word", "{$word}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function index:entries($lang as xs:string,
$flang as xs:string,
$word as xs:string) {
let $entries := doc(`abaevdict_index/mentioned_{$lang}.xml`)
/lang-index[1]/lang[@id=$flang]/word[@text=$word]/entry
for $e in $entries
let $id := $e/@id
let $txt := abv-m:entry-form-by-id($id)
let $query := `./@xml:id = '{$id}' and .//text() contains
text '{$word}' using diacritics sensitive`
return <tr>
<td>{string-join(
distinct-values($e/ref/gloss/string(@text)),', ')}</td>
<td>
<a href="./dictionary?query={$query}#{$id}">{$txt}</a>
</td>
<td>
{
string-join(data(doc(`abaevdict_{$lang}/xml/{$id}.xml`)
/tei:entry[1]/tei:sense/(tei:sense|tei:sense/tei:sense|.)
/tei:cit/tei:quote),', ')
}
</td>
</tr>
};
declare %rest:path("index/entries")
%rest:query-param("flang", "{$flang}")
%rest:query-param("word", "{$word}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function index:entries($flang as xs:string,
$word as xs:string) {
index:entries('en',$flang,$word)
};
(: THE MAIN INDEX VIEW :)
declare function index:content($lang as xs:string) {
<html>
<body>
<main>
<div class="index">
<div class="langs">
<label>{if ($lang = 'ru') then 'Языки' else 'Languages'}</label>
<select id="abv-select-lang" size="99999" name="flang"
hx-get='./index/forms' hx-target="#abv-select-word" hx-trigger="input">
{index:languages($lang)}
</select>
</div>
<div class="forms">
<label>{if ($lang = 'ru') then 'Формы' else 'Forms'}</label>
<select id="abv-select-word" size="99999" name="word"
hx-get="./index/entries" hx-target="#abv-select-entry" hx-trigger="input"
hx-include="#abv-select-lang">
</select>
</div>
<div class="entries">
<label>{if ($lang = 'ru') then 'Лексемы' else 'Lexemes'}</label>
<table>
<thead>
<tr>
<th scope="col">{if ($lang = 'ru') then 'Глоссы' else 'Gloss'}</th>
<th scope="col">{if ($lang = 'ru') then 'Лексема' else 'Lemma'}</th>
<th scope="col">{if ($lang = 'ru') then 'Перевод' else 'Translation'}</th>
</tr>
</thead>
<tbody id="abv-select-entry">
</tbody>
</table>
</div>
</div>
</main>
</body>
</html>
};

View file

@ -0,0 +1,71 @@
module namespace map = 'http://ossetic-studies.org/ns/abaevdict-site/map';
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at '../../abv-mod.xqm';
declare %rest:path("{$lang}/map")
%rest:query-param("entry", "{$entry}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function map:map($lang as xs:string, $entry as xs:string) {
let $mapdata := abv-m:make-geomap(
doc(`abaevdict_{$lang}/xml/{$entry}.xml`),
$lang)
return
(<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"
hx-on-click="document.getElementById('modal_map').close()">
{if ($lang = 'ru') then 'Закрыть' else 'Close'}
</button>
</footer>
</article>,
<script defer="1">
{concat(`var mapdata = {json:serialize($mapdata)}`,
"
map = document.getElementById('map-display');
var layout = {
// title: {
// text: 'Canadian cities',
// font: {
// family: 'Droid Serif, serif',
// size: 16
// }
// },
geo: {
scope: 'world',
resolution: 50,
fitbounds: 'locations',
showrivers: true,
rivercolor: '#fff',
showlakes: true,
lakecolor: '#fff',
showland: true,
landcolor: '#EAEAAE',
showcountries: false,
subunitcolor: '#d3d3d3'
}
};
Plotly.newPlot(map, [mapdata], layout);
")}
</script>)
};
declare %rest:path("map")
%rest:query-param("entry", "{$entry}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function map:map($entry as xs:string) {
map:map('en', $entry)
};

View file

@ -1,31 +0,0 @@
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

@ -0,0 +1,134 @@
module namespace search = 'http://ossetic-studies.org/ns/abaevdict-site/search';
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 $search:types :=
(
{
'query': ".//text() contains text &#123;'$'&#125;",
'ru': 'Везде',
'en': 'All'
},
{
'query': ".//tei:form/tei:orth//text() contains text &#123;'$'&#125;",
'ru': 'Формы',
'en': 'Forms'
},
{
'query': ".//tei:cit[@type='translationEquivalent']//text() contains text &#123;'$'&#125;",
'ru': 'Значения',
'en': 'Meanings'
},
{
'query': ".//tei:cit[@type='example']/tei:quote//text() contains text &#123;'$'&#125;",
'ru': 'Примеры',
'en': 'Examples'
},
{
'query': ".//tei:cit[@type='translation']//text() contains text &#123;'$'&#125;",
'ru': 'Переводы',
'en': 'Translations'
},
{
'query': ".//tei:mentioned/(tei:m|tei:w|tei:phr|tei:s)/text() contains text &#123;'$'&#125;",
'ru': 'Цит. формы',
'en': 'Mentioned'
},
{
'query': ".//tei:gloss//text() contains text &#123;'$'&#125;",
'ru': 'Глоссы',
'en': 'Glosses'
},
{
'query': "./tei:etym[1]//text() contains text &#123;'$'&#125;",
'ru': 'Этимологии',
'en': 'Etymology'
},
{
'query': "$",
'ru': 'XQuery',
'en': 'XQuery'
}
);
(: GET SEARCH RESULTS :)
declare function search:content($lang as xs:string,
$pattern as xs:string? := (),
$text as xs:string? := ()) {
<form role="search" id="abv-search"
hx-post=""
hx-select="main"
hx-target="main"
hx-swap="outerHTML"
hx-push-url="true">
<select name="pattern" required="1">
{
for $opt in $search:types
return
<option value="{$opt('query')}">
{
if ($pattern = $opt('query')) then
attribute selected {'1'},
if ($lang = 'ru') then $opt('ru')
else $opt('en')
}
</option>
}
</select>
<input name="text" required="1"
placeholder="{if ($lang = 'ru') then 'Искать' else 'Search'}"
value="{$text}"
aria-label="Search">
{if (exists(session:get('searchQuery'))) then
attribute disabled {"1"} }
</input>
<input type="submit" value="Submit"/>
</form>,
<table hx-boost="true" id="abv-search-results">
<thead>
<tr>
<th scope="col">{if ($lang = 'ru') then 'Лексема' else 'Lemma'}</th>
<th scope="col">{if ($lang = 'ru') then 'Перевод' else 'Translation'}</th>
<th scope="col">{if ($lang = 'ru') then 'Контекст' else 'Context'}</th>
</tr>
</thead>
<tbody>
{if (exists($pattern) and exists($text)) then
let $entries := abv-m:collection($lang,'xml')/tei:entry
let $query := replace($pattern, "\$", $text)
let $hits :=
ft:mark(xquery:eval(`declare namespace tei = "http://www.tei-c.org/ns/1.0";
declare namespace abv = "http://ossetic-studies.org/ns/abaevdict";
.[{$query}]`, {'': $entries}))
for $hit in $hits
order by abv-m:sortKey($hit/tei:form[1]/tei:orth[1]/text())
return
<tr>
<td>
<a href="/{$lang}/dictionary?query={$query}#{$hit/@xml:id}">
{$hit/tei:form[1]/tei:orth[1]}
</a>
</td>
<td>
{
string-join(
data($hit/tei:sense
/(tei:sense|tei:sense/tei:sense|.)/tei:cit/tei:quote),
', '
)
}
</td>
<td>
{
if (count($hit//*[tei:mark]) > 0) then
util:strip-namespaces(($hit//*[tei:mark])[1])/child::node()
}
</td>
</tr>
}
</tbody>
</table>
};

View file

@ -1,210 +0,0 @@
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';
(: ======================================================= :)
(: ==================== API STUFF ======================== :)
(: ======================================================= :)
declare option output:method 'json';
declare option output:json "format=xquery";
declare variable $langs-index := doc('abaevdict_index/langnames.xml');
declare variable $lookup := doc('abaevdict_index/lookup.xml');
(: Get the mentioned index for the right metalanguage :)
declare function api:ment-index($db-lang as xs:string) {
doc(`abaevdict_index/mentioned_{$db-lang}.xml`)
};
(: Short entry info (only forms for now). Hash is used because URLs don't handle
some Unicode characters very well in this system :)
declare function api:entry-info-short($n as node())
as map(xs:string, item()) {
let $id := if (name($n) = 're') then string($n/@corresp) else string($n/@xml:id)
return map{'id': hash($id),
'xmlid': $id,
'form': $n/text(),
'subentry': if (name($n) = 're') then true() else false()
}
};
declare function api:entry-info-long($n as node(), $db-lang as xs:string)
as map(xs:string, item()) {
map:merge(
(
api:entry-info-short($n),
map{'glosses': array:build(data(doc(`abaevdict_{$db-lang}/xml/{$n/@xml:id}.xml`)
/tei:entry[1]/tei:sense/(tei:sense|tei:sense/tei:sense|.)/tei:cit/tei:quote))}
)
)
};
(: Short language info -- for the language list :)
declare function api:lang-info-short($n as node(), $db-lang as xs:string) {
map{
'id': $n/code/text(),
'glottolog': $n/glottolog/text(),
'name': map{'full': if ($db-lang = 'ru') then $n/ru/text() else $n/en/text(),
'abbr': if ($db-lang = 'ru') then $n/ru/text() else $n/en_abbr/text()}
}
};
(: Longer info -- for individual languages :)
declare function api:lang-info-full($n as node(), $db-lang as xs:string) {
map:merge(
(api:lang-info-short($n, $db-lang),
map{'words':
array:build(api:ment-index($db-lang)/lang-index[1]/lang[@id=$n/code]/word,
api:word-info-short#1)
}
)
)
};
declare function api:word-info-short($n as node()) {
let $text := xs:string($n/@text)
return map{
(: I am using a hash function as ID, because the words are uniquely
identified within a language by their text. And the mentioned index is not
persistent enough (at least yet) to use persistent ids :)
'id': string(hash($text)),
'text': $text
}
};
declare function api:word-info-long($n as node()) {
map:merge(
(
api:word-info-short($n),
map{
'entries': array:build($n/entry,
fn {
map{'id': string(hash(./@id)),
'xmlid': string(./@id),
'refs': array:build(./ref,
fn {
map:merge((
map:entry('path', string(./@path)),
if(./gloss) then
{'glosses': array:build(distinct-values(./gloss/@text), string#1)}
else ()
)
)
})
}
}
)
(: 'nodes': array:build(string($n/ref/@node-id)),
'glosses': array:build(string($n/gloss/@text)) :)
}
)
)
};
(: RETURN JSON FOR PLOTLY MAP :)
(: English as default language :)
declare %rest:path("dict/map-info/{$entry}")
%rest:GET
function api:map-info($entry) {
(: In this case we do not redirect, but simply give the JSON via
another function :)
api:map-info('en', $entry)
};
(: This has to be remade to use the /api path. But keep it like this for now :)
declare %rest:path("{$lang}/dict/map-info/{$entry}")
%rest:GET
function api:map-info($lang, $entry) {
let $ments := abv-m:make-geomap(doc(`abaevdict_{$lang}/xml/{$entry}.xml`),
$lang)
return $ments
};
(: API FOR THE MENTIONED INDEX :)
(: Default to English :)
declare %rest:path("/api/{$path=.+}")
%rest:GET
function api:default($path as xs:string) {
web:forward(`/en/api/{$path}`)
};
(: Get languages :)
declare %rest:path("{$db-lang}/api/languages")
%rest:GET
function api:langs($db-lang as xs:string := 'en') {
array:build(
$langs-index/csv[1]/record,
api:lang-info-short(?,$db-lang)
)
};
(: Get one language :)
declare %rest:path("{$db-lang}/api/languages/{$lang}")
%rest:GET
function api:langs-lang($db-lang as xs:string, $lang as xs:string) {
api:lang-info-full(
$langs-index/csv[1]/record[code=$lang], $db-lang
)
};
(: Get words for a language :)
declare %rest:path("{$db-lang}/api/languages/{$lang}/words")
%rest:GET
function api:langs-lang-words($db-lang as xs:string, $lang as xs:string) {
array:build(api:ment-index($db-lang)/lang-index[1]/lang[@id=$lang]/word,
api:word-info-short#1)
};
(: Get info on a particular word.
Db-lang is a placeholder, it does nothing here, used only for consistency :)
declare %rest:path("{$db-lang}/api/languages/{$lang}/words/{$word-id}")
%rest:GET
function api:langs-lang-words-word($db-lang as xs:string,
$lang as xs:string,
$word-id as xs:string) {
api:word-info-long(
api:ment-index($db-lang)/lang-index[1]/lang[@id=$lang]/word[string(hash(@text))=$word-id]
)
};
(: Entry info :)
declare %rest:path("{$db-lang}/api/entries")
%rest:GET
function api:entries($db-lang as xs:string) {
array:build(
$lookup/tei:table[1]/*,
api:entry-info-short#1
)
};
declare %rest:path("{$db-lang}/api/entries/{$entry-id}")
%rest:GET
function api:entries($db-lang as xs:string,
$entry-id as xs:string) {
api:entry-info-long(
$lookup/tei:table[1]/*[string(hash(@xml:id))=$entry-id],
$db-lang)
};
(: Search API :)
declare %rest:path("{$db-lang}/api/search")
%rest:query-param("type","{$type}",'full')
%rest:query-param("query","{$query}")
%rest:GET
function api:search($db-lang as xs:string,
$type as xs:string,
$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

View file

@ -1,123 +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';
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

@ -1,31 +0,0 @@
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))
};

254
xq/site/restx_main.xq Normal file
View file

@ -0,0 +1,254 @@
declare namespace site = '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 home = 'http://ossetic-studies.org/ns/abaevdict-site/home' at './components/home.xqm';
import module namespace entries = 'http://ossetic-studies.org/ns/abaevdict-site/entries' at './components/entries.xqm';
import module namespace index = 'http://ossetic-studies.org/ns/abaevdict-site/index' at './components/index.xqm';
import module namespace search = 'http://ossetic-studies.org/ns/abaevdict-site/search' at './components/search.xqm';
(: =============================================================== :)
(: ============================ FAVICON ========================== :)
(: =============================================================== :)
declare %rest:path("/favicon.ico")
%rest:GET
function site:favicon() {
fetch:binary('../../static/favicon.ico')
};
(: HTML HEAD :)
declare function site:head($title as xs:string) {
<head>
<title>{$title}</title>
<script src="/static/htmx.min.js"></script>
<script src="/static/plotly-3.0.1.min.js" charset="utf-8"></script>
<link rel="stylesheet" href="/static/pico.classless.min.css"/>
<link rel="stylesheet" href="/static/abaev-html.css"/>
</head>
};
(: HEADER :)
declare function site:header($lang as xs:string, $href-other as xs:string) {
<header id="abv-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'}{
attribute hx-on-click {"this.href = this.href + window.location.hash;"},
attribute class {'abv-lang-link'},
if ($lang != 'ru') then
(attribute href {`{$href-other}`}) else (),
'ru'
}, ' / ',
element {if ($lang = 'ru') then 'a' else 'mark'}{
attribute hx-on-click {"this.href = this.href + window.location.hash;"},
attribute class {'abv-lang-link'},
if ($lang = 'ru') then
(attribute href {`{$href-other}`}) else (),
'en'
})
}
</li>
</ul>
<ul>
<li><a href="./home">{
switch($lang)
case 'ru' return 'Главная'
default return 'Home'
}
</a></li>
<li><a href="./dictionary">{switch($lang)
case 'ru' return 'Просмотр'
default return 'Browse'
}
</a></li>
<li><a href="./search">{switch($lang)
case 'ru' return 'Поиск'
default return 'Search'
}
</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>
};
(: PATHS :)
(: ZERO PATH :)
declare %rest:path("{$lang}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:null($lang as xs:string) {
site:home($lang)
};
declare %rest:path("/")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:null() {
site:null('en')
};
(: HOME PAGE :)
declare %rest:path("{$lang}/home")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:home($lang as xs:string) {
<html>
{
site:head(if ($lang = 'ru') then 'ИЭСОЯ В.И. Абаева — Главная'
else 'Abaevdict — Main'),
<body>
{site:header($lang,`/{abv-m:invert-lang($lang)}/home`)}
<main>
{home:content($lang)}
</main>
</body>
}
</html>
};
declare %rest:path("/home")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:home() {
site:home('en')
};
(: DICTIONARY INTERFACE :)
declare %rest:path("{$lang}/dictionary")
%rest:query-param("query", "{$query}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:dictionary($lang as xs:string, $query as xs:string?) {
<html>
{
site:head(if($lang = 'ru') then 'ИЭСОЯ В.И. Абаева — Просмотр'
else 'Abaevdict — Browse'),
<body>
{site:header($lang,`/{abv-m:invert-lang($lang)}/dictionary`)}
{entries:sidebar($lang)}
<dialog id="modal_map"/>
<main id="abv-dict">
{entries:entries($lang,20,$query)}
</main>
</body>
}
</html>
};
declare %rest:path("dictionary")
%rest:query-param("query", "{$query}")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:dictionary($query as xs:string?) {
site:dictionary('en', $query)
};
(: INDEX INTERFACE :)
declare %rest:path("{$lang}/index")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:index($lang as xs:string) {
<html>
{
site:head(if ($lang = 'ru') then 'ИЭСОЯ В.И. Абаева — Указатель'
else 'Abaevdict — Index'),
<body>
{site:header($lang,`/{abv-m:invert-lang($lang)}/index`)}
<main>
{index:content($lang)}
</main>
</body>
}
</html>
};
declare %rest:path("index")
%rest:GET
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:index() {
site:index('en')
};
(: SEARCH INTERFACE :)
declare %rest:path("{$lang}/search")
%rest:form-param("pattern", "{$pattern}")
%rest:form-param("text", "{$text}")
%rest:GET
%rest:POST
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:search($lang as xs:string,
$pattern as xs:string?,
$text as xs:string?) {
<html>
{
site:head(if ($lang = 'ru') then 'ИЭСОЯ В.И. Абаева — Поиск'
else 'Abaevdict — Search'),
<body>
{site:header($lang,`/{abv-m:invert-lang($lang)}/search`)}
<main>
{search:content($lang,$pattern,$text)}
</main>
</body>
}
</html>
};
declare %rest:path("search")
%rest:form-param("pattern", "{$pattern}")
%rest:form-param("text", "{$text}")
%rest:GET
%rest:POST
%rest:produces("text/html")
%output:method("html")
%output:html-version('5')
function site:search($pattern as xs:string?,
$text as xs:string?) {
site:search('en',$pattern,$text)
};
0

View file

@ -1,75 +0,0 @@
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')
};

View file

@ -1,165 +0,0 @@
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'
};

View file

@ -3,10 +3,13 @@ declare namespace abv = "http://ossetic-studies.org/ns/abaevdict";
import module namespace abv-m = 'http://ossetic-studies.org/ns/abaevdict-mod' at './abv-mod.xqm';
let $search := abv-m:search('en','full','friend')
let $frag := $search(1)('fragment')
return $frag
(: return abv-m:mark-element(db:get('abaevdict_en',`xml/{$search(1)('entry')}.xml`), $search(1)('nodes')(1), 'friend') => abv-m:make-html('en') :)
abv-m:make-html(doc('abaevdict_en/xml/entry_abūzyn.xml'),'en')
(: for $node in collection('abaevdict')//tei:ref/@target[.='#ref_Walde—Pok.']
return replace value of node $node with '#ref_Walde-Pok.' :)
(: collection(`abaevdict_en/html`)/article[@id='entry_az'] :)
(: html:doc('abaevdict_en/html/entry_az.html') :)
(: let $doc-tr := $doc transform with {
replace node xquery:eval("/Q{http://www.tei-c.org/ns/1.0}entry[1]/Q{http://www.tei-c.org/ns/1.0}etym[1]/Q{http://www.tei-c.org/ns/1.0}mentioned[2]/Q{http://www.tei-c.org/ns/1.0}mentioned[18]", {'': .})

View file

@ -46,6 +46,23 @@
<xsl:when test="current-grouping-key()">
<h6 class="abv-headword">
<xsl:apply-templates select="current-group()"/>
<br/>
<a href="#{$entry-id}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hash" viewBox="0 0 16 16">
<path d="M8.39 12.648a1 1 0 0 0-.015.18c0 .305.21.508.5.508.266 0 .492-.172.555-.477l.554-2.703h1.204c.421 0 .617-.234.617-.547 0-.312-.188-.53-.617-.53h-.985l.516-2.524h1.265c.43 0 .618-.227.618-.547 0-.313-.188-.524-.618-.524h-1.046l.476-2.304a1 1 0 0 0 .016-.164.51.51 0 0 0-.516-.516.54.54 0 0 0-.539.43l-.523 2.554H7.617l.477-2.304c.008-.04.015-.118.015-.164a.51.51 0 0 0-.523-.516.54.54 0 0 0-.531.43L6.53 5.484H5.414c-.43 0-.617.22-.617.532s.187.539.617.539h.906l-.515 2.523H4.609c-.421 0-.609.219-.609.531s.188.547.61.547h.976l-.516 2.492c-.008.04-.015.125-.015.18 0 .305.21.508.5.508.265 0 .492-.172.554-.477l.555-2.703h2.242zm-1-6.109h2.266l-.515 2.563H6.859l.532-2.563z"/>
</svg>
</a>
<xsl:if test="//tei:mentioned[@xml:lang != 'os' and not(starts-with(@xml:lang,'os-'))]">
<a class="abv-map"
hx-get="./map"
hx-target="#modal_map"
hx-swap="innerHTML"
hx-vals='{"{"}"entry": "{$entry-id}"{"}"}'
hx-on-click='document.getElementById("modal_map").showModal()'
>
<img src="/static/map.png"></img>
</a>
</xsl:if>
</h6>
</xsl:when>
<xsl:otherwise>
@ -70,12 +87,24 @@
group-adjacent="not(self::tei:etym)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<p class="abv-mainpart">
<xsl:apply-templates select="current-group()"/>
</p>
<div class="abv-mainpart">
<p>
<xsl:apply-templates select="current-group()"/>
</p>
</div>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
<div class="abv-etym">
<xsl:for-each-group select="node()"
group-starting-with="tei:lb">
<p>
<xsl:apply-templates select="current-group()"/>
</p>
</xsl:for-each-group>
</div>
<div class="notes">
<xsl:apply-templates select="//tei:note[@type='footnote']" mode="footnote"/>
</div>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
@ -105,7 +134,7 @@
<xsl:template match="tei:form/tei:orth">
<xsl:variable name="elem">
<xsl:choose>
<xsl:when test="ancestor::tei:cit[@type='formGrp']">i</xsl:when>
<xsl:when test="ancestor::tei:cit[@type='formGrp'] or ancestor::tei:re">i</xsl:when>
<xsl:otherwise>b</xsl:otherwise>
</xsl:choose>
</xsl:variable>
@ -243,9 +272,7 @@
<!-- ETYMOLOGY -->
<xsl:template match="tei:etym">
<p class="abv-etym">
<xsl:apply-templates />
</p>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="tei:oRef">
@ -267,7 +294,6 @@
</xsl:template>
<xsl:template match="tei:lb">
<xsl:element name="br"/>
</xsl:template>
<xsl:template match="tei:hi">
@ -312,8 +338,27 @@
</td>
</xsl:template>
<xsl:template match=
"text()">
<!-- Footnotes -->
<xsl:template match="tei:note[@type='footnote']">
<xsl:variable name="no"><xsl:number level="single"/></xsl:variable>
<a id="fn_{/tei:entry/@xml:id}_{$no}" href="#fntxt_{/tei:entry/@xml:id}_{$no}"><sup><xsl:value-of select="$no"/></sup></a>
</xsl:template>
<xsl:template match="tei:note[@type='footnote']" mode='footnote'>
<xsl:variable name="no"><xsl:number level="single"/></xsl:variable>
<p>
<a id="fntxt_{/tei:entry/@xml:id}_{$no}" href="#fn_{/tei:entry/@xml:id}_{$no}"><sup><xsl:value-of select="$no"/></sup></a>
<xsl:apply-templates />
</p>
</xsl:template>
<!-- Remove internal notes -->
<xsl:template match="tei:note[@type='internal']">
</xsl:template>
<!-- get rid of new lines -->
<xsl:template match="text()">
<xsl:value-of select="replace(translate(., '&#xA;', ''), ' +', ' ')"/>
</xsl:template>
</xsl:stylesheet>