A web application for studying Ancient Greek texts with integrated lexical, syntactic, and morphological analysis tools.
Oxytone provides an interactive reading environment for Ancient Greek texts with:
- Integration with LSJ dictionary (taken from Giuseppe Celano's Unicode version)
- Morphological analysis and highlighting of verb complements
- Flashcard generation for vocabulary learning via Anki
Oxytone includes the full GLAUx corpus, which contains most Ancient Greek texts. However, most of these texts have been annotated automatically through machine learning. See Keersmaekers, Alek (2021) for more details and a breakdown of annotation accuracy. GLAUx itself is based on the hard work of the people behind the Perseus and Perseids Projects, along many others.
oxytone/
webapp/ # BaseX RESTXQ endpoints
repo/ # XQuery modules
seed/ # XQuery seeding scripts
src/ # SvelteKit frontend
static/ # Static assets for SvelteKit
glaux/ # GLAUx corpus files
tei/ # TEI corpus files
lsj/ # LSJ dictionary resources
Two services run side by side in development, tied together by a reverse proxy in production:
- SvelteKit frontend (port 5173 in dev, 3000 in prod) — the reading UI.
- BaseX (port 8080) — serves normalized treebank HTML and LSJ definitions from RESTXQ endpoints, and generates the Anki flashcard CSV.
See docs/architecture.md and docs/data-pipeline.md for details.
Follow the installation instructions on their respective websites:
basex: XML databasepnpm: JavaScript package managerjust: Task runner- Saxon-HE: XSLT processor for BaseX (this can be installed automatically with
just saxon)
just install # pnpm install
just saxon # install Saxon-HE into BaseX (one time)just corpus # download and unzip corpus.zip (tei/, lsj/)corpus.zip ships without the glaux/ directory. Reconstruct it from a local
clone of the GLAUx source repo:
git clone https://github.com/alekkeersmaekers/glaux ../glaux
just glaux-rebuild # rebuilds glaux/ from ../glauxglaux-rebuild reads metadata.txt from the GLAUx clone and copies each source
file into glaux/tlg<author>/tlg<work>/<id>.xml so the layout matches tei/.
Set GLAUX_SRC to point at a clone in a different location:
GLAUX_SRC=/path/to/glaux just glaux-rebuildjust seed # builds lsj, glaux, tei, index, and normalized DBsThe seed recipe runs its steps in order, and that order matters: index reads
the store the lsj step (shortdefs) leaves behind and appends the work metadata to
it. BaseX keeps a single on-disk key/value store, so both the LSJ shortdefs (keyed
by Greek lemma) and the work metadata (keyed by tlg… path) live together in it —
their keys never collide. Re-run the whole seed (or at least lsj then index)
if the store ever looks wrong; running index alone on an empty store drops the
shortdefs.
The frontend reads the BaseX URL from a .env file (gitignored). Create one for
local development:
echo 'PUBLIC_BASEX_URL=http://localhost:8080/' > .envThe trailing slash is required — the frontend's ky client uses this as a
prefixUrl, so it must join cleanly with the relative RESTXQ paths it requests.
In separate terminals:
just basex # BaseX HTTP server on :8080
just svelte # SvelteKit dev server on :5173Then open http://localhost:5173.
Port 8080 already in use? BaseX binds to
0.0.0.0:8080. If another service (e.g. a system Apache Tomcat) holds that port,just basexfails withAddress already in use. Stop the other service first, or change BaseX's port.
The build recipe pulls, builds, and restarts the systemd target. In production a
Caddy reverse proxy routes /basex/* → BaseX and everything else →
SvelteKit — so the production .env uses that proxy prefix
(e.g. PUBLIC_BASEX_URL=/basex/) rather than a direct port.
-
pnpm devcrashes withFailed to parse URL/Invalid URL. The.envfile is missing, soPUBLIC_BASEX_URLis empty. See step 4 above. Remember the trailing slashes. -
Every BaseX route returns
503 Service Unavailable(bare Jetty error page). The RESTXQ web context failed to start. Check thejust basexoutput for aClassNotFoundException— an outdatedwebapp/WEB-INF/web.xmlcan reference servlet/filter classes that no longer exist in the bundled Jetty (e.g.CrossOriginFilter, the WebDAV servlet). Remove the offending entries. -
read/definereturn 500 withxslt:error … TransformerFactoryImpl could not be instantiated. Saxon can't start because itsxmlresolverdependency is missing from~/.basex/lib/custom/. Re-runjust saxon— it installssaxon-he,xmlresolver, andxmlresolver-…-data(the resolver jars live underlib/inside the Saxon zip and are easy to miss). -
indexreturns 500 withInput of lookup must be map or array, or the index page is empty. The shared store is missing the work metadata (or was overwritten with only shortdefs). Re-seed — see the note under step 3.
See CLAUDE.md for more detailed development guidelines.