summaryrefslogtreecommitdiff
path: root/app/akk-keilschrift-lib.js
blob: a5871b3c3e6c8e7a6268a5845e0336cf28f79bf4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/* Umschrift → Keilschrift für das altbabylonische Wörterbuch.
   Quelle: normalisiertes Akkadisch in Lateinschrift. Die Silbentrennung
   geschieht mechanisch (V / KV / VK, keine KVK-Zeichen), Logogramme
   stehen in geschweiften Klammern als Unicode-Zeichennamen.
   Jedes unbekannte Zeichen bricht den Lauf ab — stille Fehler wären
   hier die schlimmste Sorte, weil niemand sie lesen könnte. */
const SIGN = {
  a:'A', e:'E', i:'I', u:'U',
  ba:'BA', bi:'BI', bu:'BU', be:'BAD',
  pa:'PA', pi:'BI', pu:'BU', pe:'BAD',
  da:'DA', di:'DI', du:'DU', de:'DI',
  ta:'TA', ti:'TI', tu:'TU', te:'TE',
  'ṭa':'DA', 'ṭi':'DI', 'ṭu':'DU', 'ṭe':'DI',
  ga:'GA', gi:'GI', gu:'GU', ge:'GI',
  ka:'KA', ki:'KI', ku:'KU', ke:'KI',
  qa:'GA', qi:'KI', qu:'KU', qe:'KI',
  za:'ZA', zi:'ZI', zu:'ZU', ze:'ZE2',
  sa:'SA', si:'SI', su:'SU', se:'SI',
  'ṣa':'ZA', 'ṣi':'ZI', 'ṣu':'ZU', 'ṣe':'ZE2',
  'ša':'SHA', 'ši':'IGI', 'šu':'SHU', 'še':'SHE',
  ha:'HA', hi:'HI', hu:'HU', he:'HI',
  la:'LA', li:'LI', lu:'LU', le:'LI',
  ma:'MA', mi:'MI', mu:'MU', me:'ME',
  na:'NA', ni:'NI', nu:'NU', ne:'NE',
  ra:'RA', ri:'RI', ru:'RU', re:'RI',
  wa:'PI', wi:'PI', wu:'PI', we:'PI',
  ab:'AB', ap:'AB', ib:'IB', ip:'IB', ub:'UB', up:'UB', eb:'IB', ep:'IB',
  ad:'AD', at:'AD', 'aṭ':'AD', id:'A2', it:'A2', 'iṭ':'A2',
  ud:'UD', ut:'UD', 'uṭ':'UD', ed:'A2', et:'A2', 'eṭ':'A2',
  ag:'AK', ak:'AK', aq:'AK', ig:'IG', ik:'IG', iq:'IG', eg:'IG', ek:'IG', eq:'IG',
  al:'AL', il:'IL', el:'EL',
  am:'GUD TIMES KUR', im:'IM', um:'UM', em:'IM',
  an:'AN', in:'IN', un:'UN', en:'EN',
  ir:'IR', ur:'UR', er:'IR',
  'aš':'ASH', 'iš':'ISH', 'uš':'USH', 'eš':'ESH2',
  as:'UD KUSHU2', az:'UD KUSHU2', 'aṣ':'UD KUSHU2'
};
/* KVK-Zeichen. Das Altbabylonische schreibt geschlossene Silben gern mit
   einem einzigen Zeichen statt mit KV+VK; wo es eines gibt, wird es
   genommen. Das ist nicht nur kürzer, sondern schliesst auch Lücken der
   VK-Reihe: /šar/ hat ein Zeichen (ŠÁR), /ar/ allein nicht. */
const CVC = {
  tum:'TUM', lum:'LUM', kur:'KUR', nam:'NAM', tar:'TAR', lal:'LAL', sum:'SUM',
  gar:'GAR', gal:'GAL', til:'TIL', din:'DIN', tab:'TAB', zag:'ZAG', sag:'SAG',
  dam:'DAM', dub:'DUB', dug:'DUG', bar:'BAR', bal:'BAL', bur:'BUR', dag:'DAG',
  dar:'DAR', gam:'GAM', gan:'GAN', gur:'GUR', hal:'HAL', kak:'KAK', kal:'KAL',
  kin:'KIN', kul:'KUL', kun:'KUN', lam:'LAM', lul:'LUL', mar:'MAR', mes:'MES',
  nim:'NIM', nun:'NUN', pad:'PAD', pan:'PAN', sar:'SAR', sig:'SIG', sud:'SUD',
  sur:'SUR', tag:'TAG', tir:'TIR', tuk:'TUK', mah:'MAH', gig:'GIG', gul:'GUL',
  gum:'GUM', mug:'MUG', rab:'RAB', pap:'PAP', zig:'ZIG', zum:'ZUM', bad:'BAD',
  kam:'KAM2', tur:'TUR', kid:'KID', lil:'LIL', nin:'NIN', zib:'ZIB',
  'šar':'SHAR2', 'šeš':'SHESH', 'šid':'SHID', 'šim':'SHIM', 'šir':'SHIR',
  'maš':'MASH', 'muš':'MUSH', 'kiš':'KISH',
  /* stimmlos/emphatisch teilen sich das Zeichen, wie ueblich */
  dum:'TUM', qar:'GAR', kam2:'KAM2', 'ṭab':'TAB', 'ṭar':'TAR', qal:'GAL',
  bat:'BAD', pat:'BAD', 'ṭup':'DUB', dur:'BUR', sik:'SIK2'
};
const V = 'aeiu', LONG = {'ā':'a','ē':'e','ī':'i','ū':'u','â':'a','ê':'e','î':'i','û':'u'};

let NAMES = null;
function names(){
  if (NAMES) return NAMES;
  NAMES = new Map();
  /* Node kennt keine Unicode-Namenstabelle; sie wird deshalb einmalig
     aus einer JSON-Datei gelesen, die ein Python-Vorlauf erzeugt hat. */
  const tab = require('./akk-zeichennamen.json');
  for (const [n, cp] of Object.entries(tab)) NAMES.set(n, String.fromCodePoint(cp));
  return NAMES;
}
function glyph(signName){
  const g = names().get('CUNEIFORM SIGN ' + signName);
  if (!g) { throw new Error('unbekanntes Keilschriftzeichen: ' + signName); }
  return g;
}
/* Silbentrennung: Konsonant zwischen Vokalen zum folgenden Vokal,
   zwei Konsonanten werden getrennt. Danach KVK → KV + VK. */
function syllables(w){
  const s = [...w].map(c => LONG[c] || c).join('');
  const isV = c => V.includes(c);
  const out = []; let i = 0;
  while (i < s.length){
    let on = '';
    if (!isV(s[i])) { on = s[i]; i++; }
    if (i >= s.length) { if (on) throw new Error('Konsonant ohne Vokal: ' + w); break; }
    const vow = s[i]; i++;
    let coda = '';
    if (i < s.length && !isV(s[i]) && (i+1 >= s.length || !isV(s[i+1]))) { coda = s[i]; i++; }
    out.push(on + vow + coda);
  }
  return out;
}
function word(w){
  let g = '';
  for (const syl of syllables(w)){
    if (CVC[syl]) { g += glyph(CVC[syl]); continue; }
    const m = /^(.*?)([aeiu])(.*)$/.exec(syl);
    const on = m[1], vow = m[2], coda = m[3];
    /* Ohne Anlaut und mit Auslaut ist die Silbe selbst ein VK-Zeichen:
       um-ma-nu-um, nicht u-um-ma-nu-um. Ein eigenes V-Zeichen davor
       waere schlicht falsch. */
    if (!on && coda){ const k = vow + coda; if (!SIGN[k]) throw new Error('keine Silbe ' + k + ' (in ' + w + ')'); g += glyph(SIGN[k]); continue; }
    if (on){ const k = on + vow; if (!SIGN[k]) throw new Error('keine Silbe ' + k + ' (in ' + w + ')'); g += glyph(SIGN[k]); }
    else    { g += glyph(SIGN[vow]); }
    if (coda){ const k = vow + coda; if (!SIGN[k]) throw new Error('keine Silbe ' + k + ' (in ' + w + ')'); g += glyph(SIGN[k]); }
  }
  return g;
}
/* HTML-Auszeichnung, Entities, Zahlen, Paragraphenketten und lateinische
   Kuerzel bleiben unangetastet. Umgesetzt wird nur, was ganz aus kleinen
   akkadischen Buchstaben besteht und einen Vokal enthaelt: damit fallen
   BetrAVG, EStG, ETF, IRR und die Formelvariablen von selbst heraus.
   [[...]] ist die Notbremse fuer alles Uebrige (file://, localStorage). */
const AKK = /^[a-zāēīūâêîûšṣṭ]+$/;
function convert(text){
  return text.split(/(<[^>]*>|\{[^}]*\}|\[\[[^\]]*\]\]|&[a-zA-Z]+;|&#\d+;)/).map(part => {
    if (part.startsWith('<') || part.startsWith('&')) return part;
    if (part.startsWith('{')) return part.slice(1,-1).split('.').map(glyph).join('');
    if (part.startsWith('[[')) return part.slice(2,-2);
    return part.replace(/(?<![0-9])[A-Za-zāēīūâêîûšṣṭ]+/g, m =>
      AKK.test(m) && /[aeiuāēīū]/.test(m) ? word(m) : m);
  }).join('');
}
module.exports = { convert, word, glyph, syllables };