MediaWiki:Gadget-TsadraEditionReviewCore.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/**
* TsadraEditionReviewCore — loaded on demand by Gadget-TsadraEditionReview.js.
*
* 1. jsewts 1.0.4 (BDRC, Apache-2.0, https://github.com/buda-base/jsewts), unmodified
* except `const` -> `var` (ResourceLoader on MW 1.39 accepts ES5 only).
* 2. ter_core: shared with the offline pipeline, so an inline edit gets exactly the
* Wylie the pipeline would produce. Edit it there, not here.
*/
( function () {
// ResourceLoader passes its own `module`/`require`; hide them so both parts take their
// browser branch (window.jsEWTS, window.terCore).
var exports, module, require;
function newHashSet() {
var x = []
x.add = function (K) {
if (this.indexOf(K) < 0) this.push(K)
}
x.contains = function (K) {
return this.indexOf(K) >= 0
}
return x
}
function newHashMap() {
var x = {}
x.k = [], x.v = []
x.put = function (K, V) {
var i = this.k.indexOf(K)
if (i < 0) this.k.push(K), this.v.push(V); else this.v[i] = V
}
x.containsKey = function (K) {
return this.k.indexOf(K) >= 0
}
x.get = function (K) {
var i = this.k.indexOf(K)
if (i >= 0) return this.v[i]
}
return x
}
var tmpSet;
// mappings are ported from Java code
// *** Wylie to Unicode mappings ***
// list of wylie consonant => unicode
var m_consonant = new newHashMap();
m_consonant.put("k", "\u0f40");
m_consonant.put("kh", "\u0f41");
m_consonant.put("g", "\u0f42");
m_consonant.put("gh", "\u0f42\u0fb7");
m_consonant.put("g+h", "\u0f42\u0fb7");
m_consonant.put("ng", "\u0f44");
m_consonant.put("c", "\u0f45");
m_consonant.put("ch", "\u0f46");
m_consonant.put("j", "\u0f47");
m_consonant.put("ny", "\u0f49");
m_consonant.put("T", "\u0f4a");
m_consonant.put("-t", "\u0f4a");
m_consonant.put("Th", "\u0f4b");
m_consonant.put("-th", "\u0f4b");
m_consonant.put("D", "\u0f4c");
m_consonant.put("-d", "\u0f4c");
m_consonant.put("Dh", "\u0f4c\u0fb7");
m_consonant.put("D+h", "\u0f4c\u0fb7");
m_consonant.put("-dh", "\u0f4c\u0fb7");
m_consonant.put("-d+h", "\u0f4c\u0fb7");
m_consonant.put("N", "\u0f4e");
m_consonant.put("-n", "\u0f4e");
m_consonant.put("t", "\u0f4f");
m_consonant.put("th", "\u0f50");
m_consonant.put("d", "\u0f51");
m_consonant.put("dh", "\u0f51\u0fb7");
m_consonant.put("d+h", "\u0f51\u0fb7");
m_consonant.put("n", "\u0f53");
m_consonant.put("p", "\u0f54");
m_consonant.put("ph", "\u0f55");
m_consonant.put("b", "\u0f56");
m_consonant.put("bh", "\u0f56\u0fb7");
m_consonant.put("b+h", "\u0f56\u0fb7");
m_consonant.put("m", "\u0f58");
m_consonant.put("ts", "\u0f59");
m_consonant.put("tsh", "\u0f5a");
m_consonant.put("dz", "\u0f5b");
m_consonant.put("dzh", "\u0f5b\u0fb7");
m_consonant.put("dz+h", "\u0f5b\u0fb7");
m_consonant.put("w", "\u0f5d");
m_consonant.put("zh", "\u0f5e");
m_consonant.put("z", "\u0f5f");
m_consonant.put("'", "\u0f60");
m_consonant.put("y", "\u0f61");
m_consonant.put("r", "\u0f62");
m_consonant.put("l", "\u0f63");
m_consonant.put("sh", "\u0f64");
m_consonant.put("Sh", "\u0f65");
m_consonant.put("-sh", "\u0f65");
m_consonant.put("s", "\u0f66");
m_consonant.put("h", "\u0f67");
m_consonant.put("W", "\u0f5d");
m_consonant.put("Y", "\u0f61");
m_consonant.put("R", "\u0f6a");
m_consonant.put("f", "\u0f55\u0f39");
m_consonant.put("v", "\u0f56\u0f39");
// subjoined letters
var m_subjoined = new newHashMap();
m_subjoined.put("k", "\u0f90");
m_subjoined.put("kh", "\u0f91");
m_subjoined.put("g", "\u0f92");
m_subjoined.put("gh", "\u0f92\u0fb7");
m_subjoined.put("g+h", "\u0f92\u0fb7");
m_subjoined.put("ng", "\u0f94");
m_subjoined.put("c", "\u0f95");
m_subjoined.put("ch", "\u0f96");
m_subjoined.put("j", "\u0f97");
m_subjoined.put("ny", "\u0f99");
m_subjoined.put("T", "\u0f9a");
m_subjoined.put("-t", "\u0f9a");
m_subjoined.put("Th", "\u0f9b");
m_subjoined.put("-th", "\u0f9b");
m_subjoined.put("D", "\u0f9c");
m_subjoined.put("-d", "\u0f9c");
m_subjoined.put("Dh", "\u0f9c\u0fb7");
m_subjoined.put("D+h", "\u0f9c\u0fb7");
m_subjoined.put("-dh", "\u0f9c\u0fb7");
m_subjoined.put("-d+h", "\u0f9c\u0fb7");
m_subjoined.put("N", "\u0f9e");
m_subjoined.put("-n", "\u0f9e");
m_subjoined.put("t", "\u0f9f");
m_subjoined.put("th", "\u0fa0");
m_subjoined.put("d", "\u0fa1");
m_subjoined.put("dh", "\u0fa1\u0fb7");
m_subjoined.put("d+h", "\u0fa1\u0fb7");
m_subjoined.put("n", "\u0fa3");
m_subjoined.put("p", "\u0fa4");
m_subjoined.put("ph", "\u0fa5");
m_subjoined.put("b", "\u0fa6");
m_subjoined.put("bh", "\u0fa6\u0fb7");
m_subjoined.put("b+h", "\u0fa6\u0fb7");
m_subjoined.put("m", "\u0fa8");
m_subjoined.put("ts", "\u0fa9");
m_subjoined.put("tsh", "\u0faa");
m_subjoined.put("dz", "\u0fab");
m_subjoined.put("dzh", "\u0fab\u0fb7");
m_subjoined.put("dz+h", "\u0fab\u0fb7");
m_subjoined.put("w", "\u0fad");
m_subjoined.put("zh", "\u0fae");
m_subjoined.put("z", "\u0faf");
m_subjoined.put("'", "\u0fb0");
m_subjoined.put("y", "\u0fb1");
m_subjoined.put("r", "\u0fb2");
m_subjoined.put("l", "\u0fb3");
m_subjoined.put("sh", "\u0fb4");
m_subjoined.put("Sh", "\u0fb5");
m_subjoined.put("-sh", "\u0fb5");
m_subjoined.put("s", "\u0fb6");
m_subjoined.put("h", "\u0fb7");
m_subjoined.put("a", "\u0fb8");
m_subjoined.put("W", "\u0fba");
m_subjoined.put("Y", "\u0fbb");
m_subjoined.put("R", "\u0fbc");
// vowels
var m_vowel = new newHashMap();
m_vowel.put("a", "\u0f68");
m_vowel.put("A", "\u0f71");
m_vowel.put("i", "\u0f72");
m_vowel.put("I", "\u0f71\u0f72");
m_vowel.put("u", "\u0f74");
m_vowel.put("U", "\u0f71\u0f74");
m_vowel.put("e", "\u0f7a");
m_vowel.put("ai", "\u0f7b");
m_vowel.put("o", "\u0f7c");
m_vowel.put("au", "\u0f7d");
m_vowel.put("-i", "\u0f80");
m_vowel.put("-I", "\u0f71\u0f80");
// final symbols to unicode
var m_final_uni = new newHashMap();
m_final_uni.put("M", "\u0f7e");
m_final_uni.put("~M`", "\u0f82");
m_final_uni.put("~M", "\u0f83");
m_final_uni.put("X", "\u0f37");
m_final_uni.put("~X", "\u0f35");
m_final_uni.put("H", "\u0f7f");
m_final_uni.put("?", "\u0f84");
m_final_uni.put("^", "\u0f39");
m_final_uni.put("&", "\u0f85");
// final symbols organized by class
var m_final_class = new newHashMap();
m_final_class.put("M", "M");
m_final_class.put("~M`", "M");
m_final_class.put("~M", "M");
m_final_class.put("X", "X");
m_final_class.put("~X", "X");
m_final_class.put("H", "H");
m_final_class.put("?", "?");
m_final_class.put("^", "^");
m_final_class.put("&", "&");
// other stand-alone symbols
var m_other = new newHashMap();
m_other.put("0", "\u0f20");
m_other.put("1", "\u0f21");
m_other.put("2", "\u0f22");
m_other.put("3", "\u0f23");
m_other.put("4", "\u0f24");
m_other.put("5", "\u0f25");
m_other.put("6", "\u0f26");
m_other.put("7", "\u0f27");
m_other.put("8", "\u0f28");
m_other.put("9", "\u0f29");
m_other.put(" ", "\u0f0b");
m_other.put("*", "\u0f0c");
m_other.put("/", "\u0f0d");
m_other.put("//", "\u0f0e");
m_other.put(";", "\u0f0f");
m_other.put("|", "\u0f11");
m_other.put("!", "\u0f08");
m_other.put(":", "\u0f14");
m_other.put("_", " ");
m_other.put("=", "\u0f34");
m_other.put("<", "\u0f3a");
m_other.put(">", "\u0f3b");
m_other.put("(", "\u0f3c");
m_other.put(")", "\u0f3d");
m_other.put("@", "\u0f04");
m_other.put("#", "\u0f05");
m_other.put("$", "\u0f06");
m_other.put("%", "\u0f07");
// special characters: flag those if they occur out of context
var m_special = new newHashSet();
m_special.add(".");
m_special.add("+");
m_special.add("-");
m_special.add("~");
m_special.add("^");
m_special.add("?");
m_special.add("`");
m_special.add("]");
// superscripts: hashmap of superscript => set of letters or stacks below
var m_superscripts = new newHashMap();
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("g");
tmpSet.add("ng");
tmpSet.add("j");
tmpSet.add("ny");
tmpSet.add("t");
tmpSet.add("d");
tmpSet.add("n");
tmpSet.add("b");
tmpSet.add("m");
tmpSet.add("ts");
tmpSet.add("dz");
tmpSet.add("k+y");
tmpSet.add("g+y");
tmpSet.add("m+y");
tmpSet.add("b+w");
tmpSet.add("ts+w");
tmpSet.add("g+w");
m_superscripts.put("r", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("g");
tmpSet.add("ng");
tmpSet.add("c");
tmpSet.add("j");
tmpSet.add("t");
tmpSet.add("d");
tmpSet.add("p");
tmpSet.add("b");
tmpSet.add("h");
m_superscripts.put("l", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("g");
tmpSet.add("ng");
tmpSet.add("ny");
tmpSet.add("t");
tmpSet.add("d");
tmpSet.add("n");
tmpSet.add("p");
tmpSet.add("b");
tmpSet.add("m");
tmpSet.add("ts");
tmpSet.add("k+y");
tmpSet.add("g+y");
tmpSet.add("p+y");
tmpSet.add("b+y");
tmpSet.add("m+y");
tmpSet.add("k+r");
tmpSet.add("g+r");
tmpSet.add("p+r");
tmpSet.add("b+r");
tmpSet.add("m+r");
tmpSet.add("n+r");
m_superscripts.put("s", tmpSet);
// subscripts => set of letters above
var m_subscripts = new newHashMap();
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("kh");
tmpSet.add("g");
tmpSet.add("p");
tmpSet.add("ph");
tmpSet.add("b");
tmpSet.add("m");
tmpSet.add("r+k");
tmpSet.add("r+g");
tmpSet.add("r+m");
tmpSet.add("s+k");
tmpSet.add("s+g");
tmpSet.add("s+p");
tmpSet.add("s+b");
tmpSet.add("s+m");
m_subscripts.put("y", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("kh");
tmpSet.add("g");
tmpSet.add("t");
tmpSet.add("th");
tmpSet.add("d");
tmpSet.add("n");
tmpSet.add("p");
tmpSet.add("ph");
tmpSet.add("b");
tmpSet.add("m");
tmpSet.add("sh");
tmpSet.add("s");
tmpSet.add("h");
tmpSet.add("dz");
tmpSet.add("s+k");
tmpSet.add("s+g");
tmpSet.add("s+p");
tmpSet.add("s+b");
tmpSet.add("s+m");
tmpSet.add("s+n");
m_subscripts.put("r", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("g");
tmpSet.add("b");
tmpSet.add("r");
tmpSet.add("s");
tmpSet.add("z");
m_subscripts.put("l", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("kh");
tmpSet.add("g");
tmpSet.add("c");
tmpSet.add("ny");
tmpSet.add("t");
tmpSet.add("d");
tmpSet.add("ts");
tmpSet.add("tsh");
tmpSet.add("zh");
tmpSet.add("z");
tmpSet.add("r");
tmpSet.add("l");
tmpSet.add("sh");
tmpSet.add("s");
tmpSet.add("h");
tmpSet.add("g+r");
tmpSet.add("d+r");
tmpSet.add("ph+y");
tmpSet.add("r+g");
tmpSet.add("r+ts");
m_subscripts.put("w", tmpSet);
// prefixes => set of consonants or stacks after
var m_prefixes = new newHashMap();
tmpSet = new newHashSet();
tmpSet.add("c");
tmpSet.add("ny");
tmpSet.add("t");
tmpSet.add("d");
tmpSet.add("n");
tmpSet.add("ts");
tmpSet.add("zh");
tmpSet.add("z");
tmpSet.add("y");
tmpSet.add("sh");
tmpSet.add("s");
m_prefixes.put("g", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("g");
tmpSet.add("ng");
tmpSet.add("p");
tmpSet.add("b");
tmpSet.add("m");
tmpSet.add("k+y");
tmpSet.add("g+y");
tmpSet.add("p+y");
tmpSet.add("b+y");
tmpSet.add("m+y");
tmpSet.add("k+r");
tmpSet.add("g+r");
tmpSet.add("p+r");
tmpSet.add("b+r");
m_prefixes.put("d", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("k");
tmpSet.add("g");
tmpSet.add("c");
tmpSet.add("t");
tmpSet.add("d");
tmpSet.add("ts");
tmpSet.add("zh");
tmpSet.add("z");
tmpSet.add("sh");
tmpSet.add("s");
tmpSet.add("r");
tmpSet.add("l");
tmpSet.add("k+y");
tmpSet.add("g+y");
tmpSet.add("k+r");
tmpSet.add("g+r");
tmpSet.add("r+l");
tmpSet.add("s+l");
tmpSet.add("r+k");
tmpSet.add("r+g");
tmpSet.add("r+ng");
tmpSet.add("r+j");
tmpSet.add("r+ny");
tmpSet.add("r+t");
tmpSet.add("r+d");
tmpSet.add("r+n");
tmpSet.add("r+ts");
tmpSet.add("r+dz");
tmpSet.add("s+k");
tmpSet.add("s+g");
tmpSet.add("s+ng");
tmpSet.add("s+ny");
tmpSet.add("s+t");
tmpSet.add("s+d");
tmpSet.add("s+n");
tmpSet.add("s+ts");
tmpSet.add("r+k+y");
tmpSet.add("r+g+y");
tmpSet.add("s+k+y");
tmpSet.add("s+g+y");
tmpSet.add("s+k+r");
tmpSet.add("s+g+r");
tmpSet.add("l+d");
tmpSet.add("l+t");
tmpSet.add("k+l");
tmpSet.add("s+r");
tmpSet.add("z+l");
tmpSet.add("s+w");
m_prefixes.put("b", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("kh");
tmpSet.add("g");
tmpSet.add("ng");
tmpSet.add("ch");
tmpSet.add("j");
tmpSet.add("ny");
tmpSet.add("th");
tmpSet.add("d");
tmpSet.add("n");
tmpSet.add("tsh");
tmpSet.add("dz");
tmpSet.add("kh+y");
tmpSet.add("g+y");
tmpSet.add("kh+r");
tmpSet.add("g+r");
m_prefixes.put("m", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("kh");
tmpSet.add("g");
tmpSet.add("ch");
tmpSet.add("j");
tmpSet.add("th");
tmpSet.add("d");
tmpSet.add("ph");
tmpSet.add("b");
tmpSet.add("tsh");
tmpSet.add("dz");
tmpSet.add("kh+y");
tmpSet.add("g+y");
tmpSet.add("ph+y");
tmpSet.add("b+y");
tmpSet.add("kh+r");
tmpSet.add("g+r");
tmpSet.add("d+r");
tmpSet.add("ph+r");
tmpSet.add("b+r");
m_prefixes.put("'", tmpSet);
m_prefixes.put("\u2018", tmpSet);
m_prefixes.put("\u2019", tmpSet);
// set of suffix letters
// also included are some Skt letters b/c they occur often in suffix position in Skt words
var m_suffixes = new newHashSet();
m_suffixes.add("'");
m_suffixes.add("\u2018");
m_suffixes.add("\u2019");
m_suffixes.add("g");
m_suffixes.add("ng");
m_suffixes.add("d");
m_suffixes.add("n");
m_suffixes.add("b");
m_suffixes.add("m");
m_suffixes.add("r");
m_suffixes.add("l");
m_suffixes.add("s");
m_suffixes.add("N");
m_suffixes.add("T");
m_suffixes.add("-n");
m_suffixes.add("-t");
// suffix2 => set of letters before
var m_suff2 = new newHashMap();
tmpSet = new newHashSet();
tmpSet.add("g");
tmpSet.add("ng");
tmpSet.add("b");
tmpSet.add("m");
m_suff2.put("s", tmpSet);
tmpSet = new newHashSet();
tmpSet.add("n");
tmpSet.add("r");
tmpSet.add("l");
m_suff2.put("d", tmpSet);
// root letter index for very ambiguous three-stack syllables
var m_ambiguous_key = new newHashMap();
m_ambiguous_key.put("dgs", 1);
m_ambiguous_key.put("dms", 1);
m_ambiguous_key.put("dngs", 0);
m_ambiguous_key.put("'gs", 1);
m_ambiguous_key.put("'bs", 1);
m_ambiguous_key.put("mngs", 0);
m_ambiguous_key.put("mgs", 0);
m_ambiguous_key.put("bgs", 0);
m_ambiguous_key.put("dbs", 1);
var m_ambiguous_wylie = new newHashMap();
m_ambiguous_wylie.put("dgs", "dgas");
m_ambiguous_wylie.put("dngs", "dangs");
m_ambiguous_wylie.put("dms", "dmas");
m_ambiguous_wylie.put("'gs", "'gas");
m_ambiguous_wylie.put("'bs", "'bas");
m_ambiguous_wylie.put("mngs", "mangs");
m_ambiguous_wylie.put("mgs", "mags");
m_ambiguous_wylie.put("bgs", "bags");
m_ambiguous_wylie.put("dbs", "dbas");
// *** Unicode to Wylie mappings ***
// top letters
var m_tib_top = new newHashMap();
m_tib_top.put('\u0f40', "k");
m_tib_top.put('\u0f41', "kh");
m_tib_top.put('\u0f42', "g");
m_tib_top.put('\u0f43', "g+h");
m_tib_top.put('\u0f44', "ng");
m_tib_top.put('\u0f45', "c");
m_tib_top.put('\u0f46', "ch");
m_tib_top.put('\u0f47', "j");
m_tib_top.put('\u0f49', "ny");
m_tib_top.put('\u0f4a', "T");
m_tib_top.put('\u0f4b', "Th");
m_tib_top.put('\u0f4c', "D");
m_tib_top.put('\u0f4d', "D+h");
m_tib_top.put('\u0f4e', "N");
m_tib_top.put('\u0f4f', "t");
m_tib_top.put('\u0f50', "th");
m_tib_top.put('\u0f51', "d");
m_tib_top.put('\u0f52', "d+h");
m_tib_top.put('\u0f53', "n");
m_tib_top.put('\u0f54', "p");
m_tib_top.put('\u0f55', "ph");
m_tib_top.put('\u0f56', "b");
m_tib_top.put('\u0f57', "b+h");
m_tib_top.put('\u0f58', "m");
m_tib_top.put('\u0f59', "ts");
m_tib_top.put('\u0f5a', "tsh");
m_tib_top.put('\u0f5b', "dz");
m_tib_top.put('\u0f5c', "dz+h");
m_tib_top.put('\u0f5d', "w");
m_tib_top.put('\u0f5e', "zh");
m_tib_top.put('\u0f5f', "z");
m_tib_top.put('\u0f60', "'");
m_tib_top.put('\u0f61', "y");
m_tib_top.put('\u0f62', "r");
m_tib_top.put('\u0f63', "l");
m_tib_top.put('\u0f64', "sh");
m_tib_top.put('\u0f65', "Sh");
m_tib_top.put('\u0f66', "s");
m_tib_top.put('\u0f67', "h");
m_tib_top.put('\u0f68', "a");
m_tib_top.put('\u0f69', "k+Sh");
m_tib_top.put('\u0f6a', "R");
// subjoined letters
var m_tib_subjoined = new newHashMap();
m_tib_subjoined.put('\u0f90', "k");
m_tib_subjoined.put('\u0f91', "kh");
m_tib_subjoined.put('\u0f92', "g");
m_tib_subjoined.put('\u0f93', "g+h");
m_tib_subjoined.put('\u0f94', "ng");
m_tib_subjoined.put('\u0f95', "c");
m_tib_subjoined.put('\u0f96', "ch");
m_tib_subjoined.put('\u0f97', "j");
m_tib_subjoined.put('\u0f99', "ny");
m_tib_subjoined.put('\u0f9a', "T");
m_tib_subjoined.put('\u0f9b', "Th");
m_tib_subjoined.put('\u0f9c', "D");
m_tib_subjoined.put('\u0f9d', "D+h");
m_tib_subjoined.put('\u0f9e', "N");
m_tib_subjoined.put('\u0f9f', "t");
m_tib_subjoined.put('\u0fa0', "th");
m_tib_subjoined.put('\u0fa1', "d");
m_tib_subjoined.put('\u0fa2', "d+h");
m_tib_subjoined.put('\u0fa3', "n");
m_tib_subjoined.put('\u0fa4', "p");
m_tib_subjoined.put('\u0fa5', "ph");
m_tib_subjoined.put('\u0fa6', "b");
m_tib_subjoined.put('\u0fa7', "b+h");
m_tib_subjoined.put('\u0fa8', "m");
m_tib_subjoined.put('\u0fa9', "ts");
m_tib_subjoined.put('\u0faa', "tsh");
m_tib_subjoined.put('\u0fab', "dz");
m_tib_subjoined.put('\u0fac', "dz+h");
m_tib_subjoined.put('\u0fad', "w");
m_tib_subjoined.put('\u0fae', "zh");
m_tib_subjoined.put('\u0faf', "z");
m_tib_subjoined.put('\u0fb0', "'");
m_tib_subjoined.put('\u0fb1', "y");
m_tib_subjoined.put('\u0fb2', "r");
m_tib_subjoined.put('\u0fb3', "l");
m_tib_subjoined.put('\u0fb4', "sh");
m_tib_subjoined.put('\u0fb5', "Sh");
m_tib_subjoined.put('\u0fb6', "s");
m_tib_subjoined.put('\u0fb7', "h");
m_tib_subjoined.put('\u0fb8', "a");
m_tib_subjoined.put('\u0fb9', "k+Sh");
m_tib_subjoined.put('\u0fba', "W");
m_tib_subjoined.put('\u0fbb', "Y");
m_tib_subjoined.put('\u0fbc', "R");
// vowel signs:
// a-chen is not here because that's a top character, not a vowel sign.
// pre-composed "I" and "U" are dealt here; other pre-composed Skt vowels are more
// easily handled by a global replace in toWylie(), b/c they turn into subjoined "r"/"l".
var m_tib_vowel = new newHashMap();
m_tib_vowel.put('\u0f71', "A");
m_tib_vowel.put('\u0f72', "i");
m_tib_vowel.put('\u0f73', "I");
m_tib_vowel.put('\u0f74', "u");
m_tib_vowel.put('\u0f75', "U");
m_tib_vowel.put('\u0f7a', "e");
m_tib_vowel.put('\u0f7b', "ai");
m_tib_vowel.put('\u0f7c', "o");
m_tib_vowel.put('\u0f7d', "au");
m_tib_vowel.put('\u0f80', "-i");
// long (Skt) vowels
var m_tib_vowel_long = new newHashMap();
m_tib_vowel_long.put("i", "I");
m_tib_vowel_long.put("u", "U");
m_tib_vowel_long.put("-i", "-I");
// final symbols => wylie
var m_tib_final_wylie = new newHashMap();
m_tib_final_wylie.put('\u0f7e', "M");
m_tib_final_wylie.put('\u0f82', "~M`");
m_tib_final_wylie.put('\u0f83', "~M");
m_tib_final_wylie.put('\u0f37', "X");
m_tib_final_wylie.put('\u0f35', "~X");
m_tib_final_wylie.put('\u0f39', "^");
m_tib_final_wylie.put('\u0f7f', "H");
m_tib_final_wylie.put('\u0f84', "?");
m_tib_final_wylie.put('\u0f85', "&");
// final symbols by class
var m_tib_final_class = new newHashMap();
m_tib_final_class.put('\u0f7e', "M");
m_tib_final_class.put('\u0f82', "M");
m_tib_final_class.put('\u0f83', "M");
m_tib_final_class.put('\u0f37', "X");
m_tib_final_class.put('\u0f35', "X");
m_tib_final_class.put('\u0f39', "^");
m_tib_final_class.put('\u0f7f', "H");
m_tib_final_class.put('\u0f84', "?");
m_tib_final_class.put('\u0f85', "&");
// special characters introduced by ^
var m_tib_caret = new newHashMap();
m_tib_caret.put("ph", "f");
m_tib_caret.put("b", "v");
// other stand-alone characters
var m_tib_other = new newHashMap();
m_tib_other.put(' ', "_");
m_tib_other.put('\u0f04', "@");
m_tib_other.put('\u0f05', "#");
m_tib_other.put('\u0f06', "$");
m_tib_other.put('\u0f07', "%");
m_tib_other.put('\u0f08', "!");
m_tib_other.put('\u0f0b', " ");
m_tib_other.put('\u0f0c', "*");
m_tib_other.put('\u0f0d', "/");
m_tib_other.put('\u0f0e', "//");
m_tib_other.put('\u0f0f', ";");
m_tib_other.put('\u0f11', "|");
m_tib_other.put('\u0f14', ":");
m_tib_other.put('\u0f20', "0");
m_tib_other.put('\u0f21', "1");
m_tib_other.put('\u0f22', "2");
m_tib_other.put('\u0f23', "3");
m_tib_other.put('\u0f24', "4");
m_tib_other.put('\u0f25', "5");
m_tib_other.put('\u0f26', "6");
m_tib_other.put('\u0f27', "7");
m_tib_other.put('\u0f28', "8");
m_tib_other.put('\u0f29', "9");
m_tib_other.put('\u0f34', "=");
m_tib_other.put('\u0f3a', "<");
m_tib_other.put('\u0f3b', ">");
m_tib_other.put('\u0f3c', "(");
m_tib_other.put('\u0f3d', ")");
// all these stacked consonant combinations don't need "+"s in them
var m_tib_stacks = new newHashSet();
m_tib_stacks.add("b+l");
m_tib_stacks.add("b+r");
m_tib_stacks.add("b+y");
m_tib_stacks.add("c+w");
m_tib_stacks.add("d+r");
m_tib_stacks.add("d+r+w");
m_tib_stacks.add("d+w");
m_tib_stacks.add("dz+r");
m_tib_stacks.add("g+l");
m_tib_stacks.add("g+r");
m_tib_stacks.add("g+r+w");
m_tib_stacks.add("g+w");
m_tib_stacks.add("g+y");
m_tib_stacks.add("h+r");
m_tib_stacks.add("h+w");
m_tib_stacks.add("k+l");
m_tib_stacks.add("k+r");
m_tib_stacks.add("k+w");
m_tib_stacks.add("k+y");
m_tib_stacks.add("kh+r");
m_tib_stacks.add("kh+w");
m_tib_stacks.add("kh+y");
m_tib_stacks.add("l+b");
m_tib_stacks.add("l+c");
m_tib_stacks.add("l+d");
m_tib_stacks.add("l+g");
m_tib_stacks.add("l+h");
m_tib_stacks.add("l+j");
m_tib_stacks.add("l+k");
m_tib_stacks.add("l+ng");
m_tib_stacks.add("l+p");
m_tib_stacks.add("l+t");
m_tib_stacks.add("l+w");
m_tib_stacks.add("m+r");
m_tib_stacks.add("m+y");
m_tib_stacks.add("n+r");
m_tib_stacks.add("ny+w");
m_tib_stacks.add("p+r");
m_tib_stacks.add("p+y");
m_tib_stacks.add("ph+r");
m_tib_stacks.add("ph+y");
m_tib_stacks.add("ph+y+w");
m_tib_stacks.add("r+b");
m_tib_stacks.add("r+d");
m_tib_stacks.add("r+dz");
m_tib_stacks.add("r+g");
m_tib_stacks.add("r+g+w");
m_tib_stacks.add("r+g+y");
m_tib_stacks.add("r+j");
m_tib_stacks.add("r+k");
m_tib_stacks.add("r+k+y");
m_tib_stacks.add("r+l");
m_tib_stacks.add("r+m");
m_tib_stacks.add("r+m+y");
m_tib_stacks.add("r+n");
m_tib_stacks.add("r+ng");
m_tib_stacks.add("r+ny");
m_tib_stacks.add("r+t");
m_tib_stacks.add("r+ts");
m_tib_stacks.add("r+ts+w");
m_tib_stacks.add("r+w");
m_tib_stacks.add("s+b");
m_tib_stacks.add("s+b+r");
m_tib_stacks.add("s+b+y");
m_tib_stacks.add("s+d");
m_tib_stacks.add("s+g");
m_tib_stacks.add("s+g+r");
m_tib_stacks.add("s+g+y");
m_tib_stacks.add("s+k");
m_tib_stacks.add("s+k+r");
m_tib_stacks.add("s+k+y");
m_tib_stacks.add("s+l");
m_tib_stacks.add("s+m");
m_tib_stacks.add("s+m+r");
m_tib_stacks.add("s+m+y");
m_tib_stacks.add("s+n");
m_tib_stacks.add("s+n+r");
m_tib_stacks.add("s+ng");
m_tib_stacks.add("s+ny");
m_tib_stacks.add("s+p");
m_tib_stacks.add("s+p+r");
m_tib_stacks.add("s+p+y");
m_tib_stacks.add("s+r");
m_tib_stacks.add("s+t");
m_tib_stacks.add("s+ts");
m_tib_stacks.add("s+w");
m_tib_stacks.add("sh+r");
m_tib_stacks.add("sh+w");
m_tib_stacks.add("t+r");
m_tib_stacks.add("t+w");
m_tib_stacks.add("th+r");
m_tib_stacks.add("ts+w");
m_tib_stacks.add("tsh+w");
m_tib_stacks.add("z+l");
m_tib_stacks.add("z+w");
m_tib_stacks.add("zh+w");
// a map used to split the input string into tokens for fromWylie().
// all letters which start tokens longer than one letter are mapped to the max length of
// tokens starting with that letter.
var m_tokens_start = new newHashMap();
m_tokens_start.put('S', 2);
m_tokens_start.put('/', 2);
m_tokens_start.put('d', 4);
m_tokens_start.put('g', 3);
m_tokens_start.put('b', 3);
m_tokens_start.put('D', 3);
m_tokens_start.put('z', 2);
m_tokens_start.put('~', 3);
m_tokens_start.put('-', 4);
m_tokens_start.put('T', 2);
m_tokens_start.put('a', 2);
m_tokens_start.put('k', 2);
m_tokens_start.put('t', 3);
m_tokens_start.put('s', 2);
m_tokens_start.put('c', 2);
m_tokens_start.put('n', 2);
m_tokens_start.put('p', 2);
m_tokens_start.put('\r', 2);
// also for tokenization - a set of tokens longer than one letter
var m_tokens = new newHashSet();
m_tokens.add("-d+h");
m_tokens.add("dz+h");
m_tokens.add("-dh");
m_tokens.add("-sh");
m_tokens.add("-th");
m_tokens.add("D+h");
m_tokens.add("b+h");
m_tokens.add("d+h");
m_tokens.add("dzh");
m_tokens.add("g+h");
m_tokens.add("tsh");
m_tokens.add("~M`");
m_tokens.add("-I");
m_tokens.add("-d");
m_tokens.add("-i");
m_tokens.add("-n");
m_tokens.add("-t");
m_tokens.add("//");
m_tokens.add("Dh");
m_tokens.add("Sh");
m_tokens.add("Th");
m_tokens.add("ai");
m_tokens.add("au");
m_tokens.add("bh");
m_tokens.add("ch");
m_tokens.add("dh");
m_tokens.add("dz");
m_tokens.add("gh");
m_tokens.add("kh");
m_tokens.add("ng");
m_tokens.add("ny");
m_tokens.add("ph");
m_tokens.add("sh");
m_tokens.add("th");
m_tokens.add("ts");
m_tokens.add("zh");
m_tokens.add("~M");
m_tokens.add("~X");
m_tokens.add("\r\n");
// A class to encapsulate the return value of fromWylieOneStack.
var WylieStack = function() {
this.uni_string = ''
this.tokens_used = 0
this.single_consonant = ''
this.single_cons_a = ''
this.warns = []
this.visarga = false
return this
}
// Looking from i onwards within tokens, returns as many consonants as it finds,
// up to and not including the next vowel or punctuation. Skips the caret "^".
// Returns: a string of consonants joined by "+" signs.
function consonantString(tokens, i) { // strings, int
var out = [];
var t = '';
while (tokens[i] != null) {
t = tokens[i++];
if (t == '+' || t == '^') continue;
if (consonant(t) == null) break;
out.push(t);
}
return out.join("+");
}
// Looking from i backwards within tokens, at most up to orig_i, returns as
// many consonants as it finds, up to and not including the next vowel or
// punctuation. Skips the caret "^".
// Returns: a string of consonants (in forward order) joined by "+" signs.
function consonantStringBackwards(tokens, i, orig_i) {
var out = [];
var t = '';
while (i >= orig_i && tokens[i] != null) {
t = tokens[i--];
if (t == '+' || t == '^') continue;
if (consonant(t) == null) break;
out.unshift(t);
}
return out.join("+");
}
// A class to encapsulate the return value of fromWylieOneTsekbar.
var WylieTsekbar = function() {
this.uni_string = ''
this.tokens_used = 0
this.warns = []
return this
}
// A class to encapsulate an analyzed tibetan stack, while converting Unicode to Wylie.
var ToWylieStack = function() {
this.top = ''
this.stack = []
this.caret = false
this.vowels = []
this.finals = []
this.finals_found = newHashMap()
this.visarga = false
this.cons_str = ''
this.single_cons = ''
this.prefix = false
this.suffix = false
this.suff2 = false
this.dot = false
this.tokens_used = 0
this.warns = []
return this
}
// A class to encapsulate the return value of toWylieOneTsekbar.
var ToWylieTsekbar = function() {
this.wylie = ''
this.tokens_used = 0
this.warns = []
return this
}
// Converts successive stacks of Wylie into unicode, starting at the given index
// within the array of tokens.
//
// Assumes that the first available token is valid, and is either a vowel or a consonant.
// Returns a WylieTsekbar object
// HELPER CLASSES AND STRUCTURES
var State = { PREFIX: 0, MAIN: 1, SUFF1: 2, SUFF2: 3, NONE: 4 }
// split a string into Wylie tokens;
// make sure there is room for at least one null element at the end of the array
var splitIntoTokens = function(str) {
var tokens = [] // size = str.length + 2
var i = 0;
var maxlen = str.length;
TOKEN:
while (i < maxlen) {
var c = str.charAt(i);
var mlo = m_tokens_start.get(c);
// if there are multi-char tokens starting with this char, try them
if (mlo != null) {
for (var len = mlo; len > 1; len--) {
if (i <= maxlen - len) {
var tr = str.substring(i, i + len);
if (m_tokens.contains(tr)) {
tokens.push(tr);
i += len;
continue TOKEN;
}
}
}
}
// things starting with backslash are special
if (c == '\\' && i <= maxlen - 2) {
if (str.charAt(i + 1) == 'u' && i <= maxlen - 6) {
tokens.push(str.substring(i, i + 6)); // \\uxxxx
i += 6;
} else if (str.charAt(i + 1) == 'U' && i <= maxlen - 10) {
tokens.push(str.substring(i, i + 10)); // \\Uxxxxxxxx
i += 10;
} else {
tokens.push(str.substring(i, i + 2)); // \\x
i += 2;
}
continue TOKEN;
}
// otherwise just take one char
tokens.push(c.toString());
i += 1;
}
return tokens;
}
// helper functions to access the various hash tables
var consonant = function(s) { return m_consonant.get(s) }
var subjoined = function(s) { return m_subjoined.get(s) }
var vowel = function(s) { return m_vowel.get(s) }
var final_uni = function(s) { return m_final_uni.get(s) }
var final_class = function(s) { return m_final_class.get(s) }
var other = function(s) { return m_other.get(s) }
var isSpecial = function(s) { return m_special.contains(s) }
var isSuperscript = function(s) { return m_superscripts.containsKey(s) }
var superscript = function(sup, below) {
var tmpSet = m_superscripts.get(sup);
if (tmpSet == null) return false;
return tmpSet.contains(below);
}
var isSubscript = function(s) { return m_subscripts.containsKey(s) }
var subscript = function(sub, above) {
var tmpSet = m_subscripts.get(sub);
if (tmpSet == null) return false;
return tmpSet.contains(above);
}
var isPrefix = function(s) { return m_prefixes.containsKey(s) }
var prefix = function(pref, after) {
var tmpSet = m_prefixes.get(pref);
if (tmpSet == null) return false;
return tmpSet.contains(after);
}
var isSuffix = function(s) { return m_suffixes.contains(s) }
var isSuff2 = function(s) { return m_suff2.containsKey(s) }
var suff2 = function(suff, before) {
var tmpSet = m_suff2.get(suff);
if (tmpSet == null) return false;
return tmpSet.contains(before);
}
var ambiguous_key = function(syll) { return m_ambiguous_key.get(syll) }
var ambiguous_wylie = function(syll) { return m_ambiguous_wylie.get(syll) }
var tib_top = function(c) { return m_tib_top.get(c) }
var tib_subjoined = function(c) { return m_tib_subjoined.get(c) }
var tib_vowel = function(c) { return m_tib_vowel.get(c) }
var tib_vowel_long = function(s) { return m_tib_vowel_long.get(s) }
var tib_final_wylie = function(c) { return m_tib_final_wylie.get(c) }
var tib_final_class = function(c) { return m_tib_final_class.get(c) }
var tib_caret = function(s) { return m_tib_caret.get(s) }
var tib_other = function(c) { return m_tib_other.get(c) }
var tib_stack = function(s) { return m_tib_stacks.contains(s) }
// does this string consist of only hexadecimal digits?
function validHex(t) {
for (var i = 0; i < t.length; i++) {
var c = t.charAt(i);
if (!((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'))) return false;
}
return true;
}
// generate a warning if we are keeping them; prints it out if we were asked to
// handle a Wylie unicode escape, \\uxxxx or \\Uxxxxxxxx
function unicodeEscape (warns, line, t) { // [], int, str
var hex = t.substring(2);
if (hex == '') return null;
if (!validHex(hex)) {
warnl(warns, line, "\"" + t + "\": invalid hex code.");
return "";
}
return String.fromCharCode(parseInt(hex, 16))
}
function warn(warns, str) {
if (warns != null) warns.push(str);
}
// warn with line number
function warnl(warns, line, str) {
warn(warns, "line " + line + ": " + str);
}
function fromWylieOneTsekbar(tokens, i, opts) { // (str, int)
var orig_i = i
var t = tokens[i]
// variables for tracking the state within the syllable as we parse it
var stack = null
var prev_cons = ''
var visarga = false
// variables for checking the root letter, after parsing a whole tsekbar made of only single
// consonants and one consonant with "a" vowel
var check_root = true
var consonants = [] // strings
var root_idx = -1
var out = ''
var warns = []
// the type of token that we are expecting next in the input stream
// - PREFIX : expect a prefix consonant, or a main stack
// - MAIN : expect only a main stack
// - SUFF1 : expect a 1st suffix
// - SUFF2 : expect a 2nd suffix
// - NONE : expect nothing (after a 2nd suffix)
//
// the state machine is actually more lenient than this, in that a "main stack" is allowed
// to come at any moment, even after suffixes. this is because such syllables are sometimes
// found in abbreviations or other places. basically what we check is that prefixes and
// suffixes go with what they are attached to.
//
// valid tsek-bars end in one of these states: SUFF1, SUFF2, NONE
var state = State.PREFIX;
// iterate over the stacks of a tsek-bar
STACK:
while (t != null && (vowel(t) != null || consonant(t) != null) && !visarga) {
// translate a stack
if (stack != null) prev_cons = stack.single_consonant;
stack = fromWylieOneStack(tokens, i, opts);
i += stack.tokens_used;
t = tokens[i];
out += stack.uni_string;
warns = warns.concat(stack.warns);
visarga = stack.visarga;
if (!opts.check) continue;
// check for syllable structure consistency by iterating a simple state machine
// - prefix consonant
if (state == State.PREFIX && stack.single_consonant != null) {
consonants.push(stack.single_consonant);
if (isPrefix(stack.single_consonant)) {
var next = t;
if (opts.check_strict) next = consonantString(tokens, i);
if (next != null && !prefix(stack.single_consonant, next)) {
next = next.replace(/\+/g, "");
warns.push("Prefix \"" + stack.single_consonant + "\" does not occur before \"" + next + "\".");
}
} else {
warns.push("Invalid prefix consonant: \"" + stack.single_consonant + "\".");
}
state = State.MAIN;
// - main stack with vowel or multiple consonants
} else if (stack.single_consonant == null) {
state = State.SUFF1;
// keep track of the root consonant if it was a single cons with an "a" vowel
if (root_idx >= 0) {
check_root = false;
} else if (stack.single_cons_a != null) {
consonants.push(stack.single_cons_a);
root_idx = consonants.length - 1;
}
// - unexpected single consonant after prefix
} else if (state == State.MAIN) {
warns.push("Expected vowel after \"" + stack.single_consonant + "\".");
// - 1st suffix
} else if (state == State.SUFF1) {
consonants.push(stack.single_consonant);
// check this one only in strict mode b/c it trips on lots of Skt stuff
if (opts.check_strict) {
if (!isSuffix(stack.single_consonant)) {
warns.push("Invalid suffix consonant: \"" + stack.single_consonant + "\".");
}
}
state = State.SUFF2;
// - 2nd suffix
} else if (state == State.SUFF2) {
consonants.push(stack.single_consonant);
if (isSuff2(stack.single_consonant)) {
if (!suff2(stack.single_consonant, prev_cons)) {
warns.push("Second suffix \"" + stack.single_consonant
+ "\" does not occur after \"" + prev_cons + "\".");
}
} else {
warns.push("Invalid 2nd suffix consonant: \"" + stack.single_consonant + "\".");
}
state = State.NONE;
// - more crap after a 2nd suffix
} else if (state == State.NONE) {
warns.push("Cannot have another consonant \"" + stack.single_consonant + "\" after 2nd suffix.");
}
}
if (state == State.MAIN && stack.single_consonant != null && isPrefix(stack.single_consonant)) {
warns.push("Vowel expected after \"" + stack.single_consonant + "\".");
}
// check root consonant placement only if there were no warnings so far, and the syllable
// looks ambiguous. not many checks are needed here because the previous state machine
// already takes care of most illegal combinations.
if (opts.check && warns.length == 0 && check_root && root_idx >= 0) {
// 2 letters where each could be prefix/suffix: root is 1st
if (consonants.length == 2 && root_idx != 0
&& prefix(consonants[0], consonants[1]) && isSuffix(consonants[1]))
{
warns.push("Syllable should probably be \"" + consonants[0] + "a" + consonants[1] + "\".");
// 3 letters where 1st can be prefix, 2nd can be postfix before "s" and last is "s":
// use a lookup table as this is completely ambiguous.
} else if (consonants.length == 3 && isPrefix(consonants[0]) &&
suff2("s", consonants[1]) && consonants[2] == "s")
{
var cc = consonants.join("");
var expect_key = ambiguous_key(cc);
// console.log('typeof expect_key', typeof expect_key)
if (expect_key != null && expect_key != root_idx) {
warns.push("Syllable should probably be \"" + ambiguous_wylie(cc) + "\".");
}
}
}
// return the stuff as a WylieTsekbar struct
var ret = new WylieTsekbar();
ret.uni_string = out;
ret.tokens_used = i - orig_i;
ret.warns = warns;
return ret;
}
// Converts one stack's worth of Wylie into unicode, starting at the given index
// within the array of tokens.
// Assumes that the first available token is valid, and is either a vowel or a consonant.
// Returns a WylieStack object.
function fromWylieOneStack(tokens, i, opts) {
var orig_i = i
var t = '', t2 = '', o = ''
var out = ''
var warns = []
var consonants = 0 // how many consonants found
var vowel_found = null; // any vowels (including a-chen)
var vowel_sign = null; // any vowel signs (that go under or above the main stack)
var single_consonant = null; // did we find just a single consonant?
var plus = false; // any explicit subjoining via '+'?
var caret = 0; // find any '^'?
var final_found = new newHashMap(); // keep track of finals (H, M, etc) by class
// do we have a superscript?
t = tokens[i]
t2 = tokens[i + 1]
if (t2 != null && isSuperscript(t) && superscript(t, t2)) {
if (opts.check_strict) {
var next = consonantString(tokens, i + 1);
if (!superscript(t, next)) {
next = next.replace(/\+/g, '')
warns.push("Superscript \"" + t + "\" does not occur above combination \"" + next + "\".");
}
}
out += consonant(t);
consonants++;
i++;
while (tokens[i] != null && tokens[i] == ("^")) { caret++; i++; }
}
// main consonant + stuff underneath.
// this is usually executed just once, but the "+" subjoining operator makes it come back here
MAIN:
while (true) {
// main consonant (or a "a" after a "+")
t = tokens[i];
if (consonant(t) != null || (out.length > 0 && subjoined(t) != null)) {
if (out.length > 0) {
out += (subjoined(t));
} else {
out += (consonant(t));
}
i++;
if (t == "a") {
vowel_found = "a";
} else {
consonants++;
single_consonant = t;
}
while (tokens[i] != null && tokens[i] == "^") {
caret++;
i++;
}
// subjoined: rata, yata, lata, wazur. there can be up two subjoined letters in a stack.
for (var z = 0; z < 2; z++) {
t2 = tokens[i];
if (t2 != null && isSubscript(t2)) {
// lata does not occur below multiple consonants
// (otherwise we mess up "brla" = "b.r+la")
if (t2 == "l" && consonants > 1) break;
// full stack checking (disabled by "+")
if (opts.check_strict && !plus) {
var prev = consonantStringBackwards(tokens, i-1, orig_i);
if (!subscript(t2, prev)) {
prev = prev.replace(/\+/g, "");
warns.push("Subjoined \"" + t2 + "\" not expected after \"" + prev + "\".");
}
// simple check only
} else if (opts.check) {
if (!subscript(t2, t) && !(z == 1 && t2 == ("w") && t == ("y"))) {
warns.push("Subjoined \"" + t2 + "\"not expected after \"" + t + "\".");
}
}
out += subjoined(t2);
i++;
consonants++;
while (tokens[i] != null && tokens[i] == ("^")) { caret++; i++; }
t = t2;
} else {
break;
}
}
}
// caret (^) can come anywhere in Wylie but in Unicode we generate it at the end of
// the stack but before vowels if it came there (seems to be what OpenOffice expects),
// or at the very end of the stack if that's how it was in the Wylie.
if (caret > 0) {
if (caret > 1) {
warns.push("Cannot have more than one \"^\" applied to the same stack.");
}
final_found.put(final_class("^"), "^");
out += (final_uni("^"));
caret = 0;
}
// vowel(s)
t = tokens[i];
if (t != null && vowel(t) != null) {
if (out.length == 0) out += (vowel("a"));
if (t != "a") out += (vowel(t));
i++;
vowel_found = t;
if (t != "a") vowel_sign = t;
}
// plus sign: forces more subjoining
t = tokens[i];
if (t != null && t == ("+")) {
i++;
plus = true;
// sanity check: next token must be vowel or subjoinable consonant.
t = tokens[i];
if (t == null || (vowel(t) == null && subjoined(t) == null)) {
if (opts.check) warns.push("Expected vowel or consonant after \"+\".");
break MAIN;
}
// consonants after vowels doesn't make much sense but process it anyway
if (opts.check) {
if (vowel(t) == null && vowel_sign != null) {
warns.push("Cannot subjoin consonant (" + t + ") after vowel (" + vowel_sign + ") in same stack.");
} else if (t == ("a") && vowel_sign != null) {
warns.push("Cannot subjoin a-chen (a) after vowel (" + vowel_sign + ") in same stack.");
}
}
continue MAIN;
}
break MAIN;
}
// final tokens
t = tokens[i];
while (t != null && final_class(t) != null) {
var uni = final_uni(t);
var klass = final_class(t);
// check for duplicates
if (final_found.containsKey(klass)) {
if (final_found.get(klass) == t) {
warns.push("Cannot have two \"" + t + "\" applied to the same stack.");
} else {
warns.push("Cannot have \"" + t + "\" and \"" + final_found.get(klass)
+ "\" applied to the same stack.");
}
} else {
final_found.put(klass, t);
out += (uni);
}
i++;
single_consonant = null;
t = tokens[i];
}
// if next is a dot "." (stack separator), skip it.
if (tokens[i] != null && tokens[i] == (".")) i++;
// if we had more than a consonant and no vowel, and no explicit "+" joining, backtrack and
// return the 1st consonant alone
if (consonants > 1 && vowel_found == null) {
if (plus) {
if (opts.check) warns.push("Stack with multiple consonants should end with vowel.");
} else {
i = orig_i + 1;
consonants = 1;
single_consonant = tokens[orig_i];
out = '';
out += (consonant(single_consonant));
}
}
// calculate "single consonant"
if (consonants != 1 || plus) {
single_consonant = null;
}
// return the stuff as a WylieStack struct
var ret = new WylieStack();
ret.uni_string = out;
ret.tokens_used = i - orig_i;
if (vowel_found != null) {
ret.single_consonant = null;
} else {
ret.single_consonant = single_consonant;
}
if (vowel_found != null && vowel_found == ("a")) {
ret.single_cons_a = single_consonant;
} else {
ret.single_cons_a = null;
}
ret.warns = warns;
ret.visarga = final_found.containsKey("H");
return ret;
}
function sloppyRepl(str) {
str = str.replace(/[ʼʹ‘’ʾ]/g, "'");
str = str.replace(/ ([\(0-9])/g, "_$1");
str = str.replace(/([_\)\/]) /g, "$1_");
str = str.replace(/G/g, "g");
str = str.replace(/C/g, "c");
str = str.replace(/B/g, "b");
str = str.replace(/L/g, "l");
str = str.replace(/P/g, "p");
str = str.replace(/Z/g, "z");
str = str.replace(/^\s+/, '');
return str;
}
// Converts a Wylie (EWTS) string to unicode. If 'warns' is not 'null', puts warnings into it.
function fromWylie(str, opts, warns) {
if (!warns) {
warns = [];
}
if (!opts) {
opts = {
sloppy: true
};
}
if (opts.sloppy) {
str = sloppyRepl(str);
}
var out = '', line = 1, units = 0, i = 0;
var tokens = splitIntoTokens(str);
ITER:
while (tokens[i] != null) {
var t = tokens[i], o = null
// [non-tibetan text] : pass through, nesting brackets
if (t == "[") {
var nesting = 1;
i++;
ESC:while (tokens[i] != null) {
t = tokens[i++];
if (t == "[") nesting++;
if (t == "]") nesting--;
if (nesting == 0) continue ITER;
// handle unicode escapes and \1-char escapes within [comments]...
if (t.charAt(0) == '\\' && (t.charAt(1) == 'u' || t.charAt(1) == 'U')) {
o = unicodeEscape(warns, line, t);
if (o != null) {
out += o;
continue ESC;
}
}
if (t.charAt(0) == '\\') {
o = t.substring(1);
} else {
o = t;
}
out += o;
}
warnl(warns, line, "Unfinished [non-Wylie stuff].");
break ITER;
}
// punctuation, numbers, etc
o = other(t);
if (o != null) {
out += o;
i++;
units++;
// collapse multiple spaces?
if (t == " " && opts.sloppy) {
while (tokens[i] != null && tokens[i] == " ") i++;
}
continue ITER;
}
// vowels & consonants: process tibetan script up to a tsek, punctuation or line noise
if (vowel(t) != null || consonant(t) != null) {
var tb = fromWylieOneTsekbar(tokens, i, opts);
var word = '';
for (var j = 0; j < tb.tokens_used; j++) {
word += (tokens[i+j]);
}
out += tb.uni_string;
i += tb.tokens_used;
units++;
for (var w = 0; w < tb.warns.length; w++) {
warnl(warns, line, "\"" + word + "\": " + tb.warns[w]);
}
continue ITER;
}
// *** misc unicode and line handling stuff ***
// ignore BOM and zero-width space
if (t == "\ufeff" || t == "\u200b") {
i++;
continue ITER;
}
// \\u, \\U unicode characters
if (t.charAt(0) == '\\' && (t.charAt(1) == 'u' || t.charAt(1) == 'U')) {
o = unicodeEscape(warns, line, t);
if (o != null) {
i++;
out += o;
continue ITER;
}
}
// backslashed characters
if (t.charAt(0) == '\\') {
out += t.substring(1);
i++;
continue ITER;
}
// count lines
if (t == "\r\n" || t == "\n" || t == "\r") {
line++;
out += t;
i++;
// also eat spaces after newlines (optional)
if (opts.sloppy) {
while (tokens[i] != null && tokens[i] == " ") i++;
}
continue ITER;
}
// stuff that shouldn't occur out of context: special chars and remaining [a-zA-Z]
var c = t.charAt(0);
if (isSpecial(t) || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
warnl(warns, line, "Unexpected character \"" + t + "\".");
}
// anything else: pass through
out += t;
i++;
}
if (units == 0) warn(warns, "No Tibetan characters found!");
return out
}
// given a character, return a string like "\\uxxxx", with its code in hex
function formatHex(t) { //char
// not compatible with GWT...
// return String.format("\\u%04x", (int)t);
var sb = '';
sb += '\\u';
var s = t.charCodeAt(0).toString(16);
for (var i = s.length; i < 4; i++) sb += '0';
sb += s;
return sb;
}
// handles spaces (if any) in the input stream, turning them into '_'.
// this is abstracted out because in non-escaping mode, we only want to turn spaces into _
// when they come in the middle of Tibetan script.
function handleSpaces(str, i, out) { //return int
var found = 0;
var orig_i = i;
while (i < str.length && str.charAt(i) == ' ') {
i++;
found++;
}
if (found == 0 || i == str.length) return 0;
var t = str.charAt(i);
if (tib_top(t) == null && tib_other(t) == null) return 0;
// found 'found' spaces between two tibetan bits; generate the same number of '_'s
for (i = 0; i < found; i++) out += '_';
return found;
}
// for space-handling in escaping mode: is the next thing coming (after a number of spaces)
// some non-tibetan bit, within the same line?
function followedByNonTibetan(str, i) {
var len = str.length;
while (i < len && str.charAt(i) == ' ') i++;
if (i == len) return false;
var t = str.charAt(i);
return tib_top(t) == null && tib_other(t) == null && t != '\r' && t != '\n';
}
// Convert Unicode to Wylie: one tsekbar
function toWylieOneTsekbar(str, len, i) {
var orig_i = i;
var warns = [];
var stacks = [];// ArrayList<ToWylieStack>;
ITER:
while (true) {
var st = toWylieOneStack(str, len, i);
stacks.push(st);
warns = warns.concat(st.warns);
i += st.tokens_used;
if (st.visarga) break ITER;
if (i >= len || tib_top(str.charAt(i)) == null) break ITER;
}
// figure out if some of these stacks can be prefixes or suffixes (in which case
// they don't need their "a" vowels)
var last = stacks.length - 1;
if (stacks.length > 1 && stacks[0].single_cons != null) {
// we don't count the wazur in the root stack, for prefix checking
var cs = stacks[1].cons_str.replace(/\+w/g, "")
if (prefix(stacks[0].single_cons, cs)) stacks[0].prefix = true;
}
if (stacks.length > 1 && stacks[last].single_cons != null
&& isSuffix(stacks[last].single_cons)) {
stacks[last].suffix = true;
}
if (stacks.length > 2 && stacks[last].single_cons != null
&& stacks[last - 1].single_cons != null
&& isSuffix(stacks[last - 1].single_cons)
&& suff2(stacks[last].single_cons, stacks[last - 1].single_cons)) {
stacks[last].suff2 = true;
stacks[last - 1].suffix = true;
}
// if there are two stacks and both can be prefix-suffix, then 1st is root
if (stacks.length == 2 && stacks[0].prefix && stacks[1].suffix) {
stacks[0].prefix = false;
}
// if there are three stacks and they can be prefix, suffix and suff2, then check w/ a table
if (stacks.length == 3 && stacks[0].prefix && stacks[1].suffix && stacks[2].suff2) {
var strb = []
for (var si = 0; si < stacks.length; si++) strb.push(stacks[si].single_cons)
var ztr = strb.join('')
var root = ambiguous_key(ztr)
if (root == null) {
warns.push("Ambiguous syllable found: root consonant not known for \"" + ztr + "\".")
// make it up... (ex. "mgas" for ma, ga, sa)
root = 1
}
stacks[root].prefix = stacks[root].suffix = false
stacks[root + 1].suff2 = false
}
// if the prefix together with the main stack could be mistaken for a single stack, add a "."
if (stacks[0].prefix && tib_stack(stacks[0].single_cons + "+" + stacks[1].cons_str))
stacks[0].dot = true;
// put it all together
var out = ''
for (var si = 0; si < stacks.length; si++) out += putStackTogether(stacks[si])
var ret = new ToWylieTsekbar();
ret.wylie = out;
ret.tokens_used = i - orig_i;
ret.warns = warns;
return ret;
}
// Unicode to Wylie: one stack at a time
function toWylieOneStack(str, len, i) {
var orig_i = i;
var ffinal = null, vowel = null, klass = null;
// split the stack into a ToWylieStack object:
// - top symbol
// - stacked signs (first is the top symbol again, then subscribed main characters...)
// - caret (did we find a stray tsa-phru or not?)
// - vowel signs (including small subscribed a-chung, "-i" Skt signs, etc)
// - final stuff (including anusvara, visarga, halanta...)
// - and some more variables to keep track of what has been found
var st = new ToWylieStack();
// assume: tib_top(t) exists
var t = str.charAt(i++);
st.top = tib_top(t);
st.stack.push(tib_top(t));
// grab everything else below the top sign and classify in various categories
while (i < len) {
t = str.charAt(i);
var o;
if ((o = tib_subjoined(t)) != null) {
i++;
st.stack.push(o);
// check for bad ordering
if (st.finals.length > 0) {
st.warns.push("Subjoined sign \"" + o + "\" found after final sign \"" + ffinal + "\".");
} else if (st.vowels.length > 0) {
st.warns.push("Subjoined sign \"" + o + "\" found after vowel sign \"" + vowel + "\".");
}
} else if ((o = tib_vowel(t)) != null) {
i++;
st.vowels.push(o);
if (vowel == null) vowel = o;
// check for bad ordering
if (st.finals.length > 0) {
st.warns.push("Vowel sign \"" + o + "\" found after final sign \"" + ffinal + "\".");
}
} else if ((o = tib_final_wylie(t)) != null) {
i++;
klass = tib_final_class(t);
if (o == "^") {
st.caret = true;
} else {
if (o == "H") st.visarga = true;
st.finals.push(o);
if (ffinal == null) ffinal = o;
// check for invalid combinations
if (st.finals_found.containsKey(klass)) {
st.warns.push("Final sign \"" + o
+ "\" should not combine with found after final sign \"" + ffinal + "\".");
} else {
st.finals_found.put(klass, o);
}
}
} else break;
}
// now analyze the stack according to various rules
// a-chen with vowel signs: remove the "a" and keep the vowel signs
if (st.top == "a" && st.stack.length == 1 && st.vowels.length > 0) st.stack.shift();
// handle long vowels: A+i becomes I, etc.
if (st.vowels.length > 1 && st.vowels[0] == "A" && tib_vowel_long(st.vowels[1]) != null) {
var l = tib_vowel_long(st.vowels[1]);
st.vowels.shift();
st.vowels.shift();
st.vowels.unshift(l);
}
// special cases: "ph^" becomes "f", "b^" becomes "v"
if (st.caret && st.stack.length == 1 && tib_caret(st.top) != null) {
var l = tib_caret(st.top);
st.top = l;
st.stack.shift();
st.stack.unshift(l);
st.caret = false;
}
st.cons_str = st.stack.join("+");
// if this is a single consonant, keep track of it (useful for prefix/suffix analysis)
if (st.stack.length == 1 && st.stack[0] != ("a") && !st.caret
&& st.vowels.length == 0 && st.finals.length == 0) {
st.single_cons = st.cons_str;
}
// return the analyzed stack
st.tokens_used = i - orig_i;
return st;
}
// Puts an analyzed stack together into Wylie output, adding an implicit "a" if needed.
function putStackTogether(st) {
var out = '';
// put the main elements together... stacked with "+" unless it's a regular stack
if (tib_stack(st.cons_str)) {
out += st.stack.join("");
} else out += (st.cons_str);
// caret (tsa-phru) goes here as per some (halfway broken) Unicode specs...
if (st.caret) out += ("^");
// vowels...
if (st.vowels.length > 0) {
out += st.vowels.join("+");
} else if (!st.prefix && !st.suffix && !st.suff2
&& (st.cons_str.length == 0 || st.cons_str.charAt(st.cons_str.length - 1) != 'a')) {
out += "a"
}
// final stuff
out += st.finals.join("");
if (st.dot) out += ".";
return out;
}
// Converts from Unicode strings to Wylie (EWTS) transliteration.
//
// Arguments are:
// str : the unicode string to be converted
// escape: whether to escape non-tibetan characters according to Wylie encoding.
// if escape == false, anything that is not tibetan will be just passed through.
//
// Returns: the transliterated string.
//
// To get the warnings, call getWarnings() afterwards.
function toWylie(str, escape, warns) {
if (!warns) {
warns = [];
}
if (escape === undefined) escape = true;
var out = ''
var line = 1
var units = 0
// globally search and replace some deprecated pre-composed Sanskrit vowels
str = str.replace(/\u0f76/g, "\u0fb2\u0f80");
str = str.replace(/\u0f77/g, "\u0fb2\u0f71\u0f80");
str = str.replace(/\u0f78/g, "\u0fb3\u0f80");
str = str.replace(/\u0f79/g, "\u0fb3\u0f71\u0f80");
str = str.replace(/\u0f81/g, "\u0f71\u0f80");
var i = 0
var len = str.length
// iterate over the string, codepoint by codepoint
ITER:
while (i < len) {
var t = str.charAt(i);
// found tibetan script - handle one tsekbar
if (tib_top(t) != null) {
var tb = toWylieOneTsekbar(str, len, i);
out += tb.wylie;
i += tb.tokens_used;
units++;
for (var w = 0; w < tb.warns.length; w++) warnl(warns, line, tb.warns[w]);
if (!escape) i += handleSpaces(str, i, out);
continue ITER;
}
// punctuation and special stuff. spaces are tricky:
// - in non-escaping mode: spaces are not turned to '_' here (handled by handleSpaces)
// - in escaping mode: don't do spaces if there is non-tibetan coming, so they become part
// of the [escaped block].
var o = tib_other(t);
if (o != null && (t != ' ' || (escape && !followedByNonTibetan(str, i)))) {
out += o;
i++;
units++;
if (!escape) i += handleSpaces(str, i, out);
continue ITER;
}
// newlines, count lines. "\r\n" together count as one newline.
if (t == '\r' || t == '\n') {
line++;
i++;
out += t;
if (t == '\r' && i < len && str.charAt(i) == '\n') {
i++;
out += ('\n');
}
continue ITER;
}
// ignore BOM and zero-width space
if (t == '\ufeff' || t == '\u200b') {
i++;
continue ITER;
}
// anything else - pass along?
if (!escape) {
out += (t);
i++;
continue ITER;
}
// other characters in the tibetan plane, escape with \\u0fxx
if (t >= '\u0f00' && t <= '\u0fff') {
var c = formatHex(t);
out += c;
i++;
// warn for tibetan codepoints that should appear only after a tib_top
if (tib_subjoined(t) != null || tib_vowel(t) != null || tib_final_wylie(t) != null) {
warnl(warns, line, "Tibetan sign " + c + " needs a top symbol to attach to.");
}
continue ITER;
}
// ... or escape according to Wylie:
// put it in [comments], escaping [] sequences and closing at line ends
out += "[";
while (tib_top(t) == null && (tib_other(t) == null || t == ' ') && t != '\r' && t != '\n') {
// \escape [opening and closing] brackets
if (t == '[' || t == ']') {
out += "\\";
out += t;
// unicode-escape anything in the tibetan plane (i.e characters not handled by Wylie)
} else if (t >= '\u0f00' && t <= '\u0fff') {
out += formatHex(t);
// and just pass through anything else!
} else {
out += t;
}
if (++i >= len) break;
t = str.charAt(i);
}
out += "]";
}
return out;
}
function exportJsEWTS(global) {
var jsEWTS = {
fromWylie: fromWylie,
toWylie: toWylie
}
/*
if (typeof define === 'function' && define.amd) {
define(function(){ return jsEWTS; });
}
else
*/
if(typeof exports !== 'undefined'){
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = jsEWTS ;
}
exports.jsEWTS = jsEWTS;
}else {
if(global) { global.jsEWTS = jsEWTS }
if(window) { window.jsEWTS = jsEWTS }
}
};
exportJsEWTS(this);
/**
* ter_core — shared logic for the Tsadra 2026 edition review (BCA 017 / SSC 016).
*
* The SAME file is used by the Node pipeline (gen_wylie.js) and, concatenated after
* jsewts, by MediaWiki:Gadget-TsadraEditionReviewCore.js, so a curator's inline edit
* gets exactly the Wylie the pipeline would have produced. Keep it ES5: ResourceLoader
* on MW 1.39 rejects ES6 in gadgets.
*/
( function ( root, factory ) {
if ( typeof module === 'object' && module.exports ) {
module.exports = factory( require( 'jsewts' ) );
} else {
root.terCore = factory( root.jsEWTS );
}
}( typeof self !== 'undefined' ? self : this, function ( ewts ) {
'use strict';
/* ---------- wikitext pieces ---------- */
// Split into [kind, text] with kind 'text' or 'tpl', templates matched depth-aware.
function pieces( s ) {
var out = [], i = 0, start = 0, d, j;
while ( i < s.length ) {
if ( s.substr( i, 2 ) === '{{' ) {
if ( i > start ) { out.push( [ 'text', s.slice( start, i ) ] ); }
d = 0; j = i;
while ( j < s.length ) {
if ( s.substr( j, 2 ) === '{{' ) { d++; j += 2; continue; }
if ( s.substr( j, 2 ) === '}}' ) { d--; j += 2; if ( d === 0 ) { break; } continue; }
j++;
}
out.push( [ 'tpl', s.slice( i, j ) ] ); i = start = j; continue;
}
i++;
}
if ( start < s.length ) { out.push( [ 'text', s.slice( start ) ] ); }
return out;
}
/* ---------- Wylie, the way the wiki writes it ---------- */
// Only runs of Tibetan (with interior spaces) are converted; Latin, quotes, brackets,
// digits and newlines pass through. BCA writes ༴ as '=', SSC as 'la:'.
var TIB_RUN = /[ༀ-]+(?:[ ]+[ༀ-]+)*/g;
function conv( text, id ) {
var w = text.replace( TIB_RUN, function ( run ) { return '\u0001' + ewts.toWylie( run ) + '\u0002'; } );
// passthrough text: collapse space runs (the wiki's Wylie never carries doubled spaces)
w = w.replace( /(^|\u0002)([^\u0001]*)/g, function ( m, a, b ) { return a + b.replace( / {2,}/g, ' ' ); } );
w = w.replace( /[\u0001\u0002]/g, '' ).replace( / +\n/g, '\n' );
if ( id === 'bca' ) { w = w.replace( /=/g, '=' ); } else { w = w.replace( / ?=/g, ':' ); }
if ( id === 'ssc' ) { w = w.replace( /^(\d+)\.(?=\S)/, '$1. ' ); } // Root Verses: '9. mnyam'
return w;
}
function wylieOf( tib, id ) {
return pieces( tib ).map( function ( p ) {
if ( p[ 0 ] === 'text' ) { return conv( p[ 1 ], id ); }
var m = /^\{\{note\|([^|]*)\|([\s\S]*)\}\}$/.exec( p[ 1 ] );
if ( m ) { return '{{note|' + m[ 1 ] + '|' + conv( m[ 2 ], id ) + '}}'; }
return p[ 1 ];
} ).join( '' );
}
// Valid = converts back to the same Tibetan as the reference conversion does. Comparing
// with fromWylie(wylieOf(tib)) rather than tib means Latin/Sanskrit passthrough in notes
// is garbled identically on both sides; only Tibetan differences count.
function toTib( w, id ) {
var x = id === 'bca' ? w.replace( /=/g, '=' ) : w.replace( /:/g, '=' );
return ewts.fromWylie( x ).replace( /༎/g, '།།' ).replace( /[་༼༽\s]/g, '' );
}
function valid( w, tib, id ) { return toTib( w, id ) === toTib( wylieOf( tib, id ), id ); }
/* ---------- diff (Myers), on strings or arrays ---------- */
function ops( a, b ) {
var p = 0, s = 0;
while ( p < a.length && p < b.length && a[ p ] === b[ p ] ) { p++; }
while ( s < a.length - p && s < b.length - p && a[ a.length - 1 - s ] === b[ b.length - 1 - s ] ) { s++; }
var A = a.slice( p, a.length - s ), B = b.slice( p, b.length - s );
var n = A.length, m = B.length, max = n + m, off = max + 1;
var trace = [], v = [], d, k, x, y, done = n === 0 && m === 0, i;
for ( i = 0; i < 2 * max + 3; i++ ) { v[ i ] = 0; }
for ( d = 0; d <= max && !done; d++ ) {
trace.push( v.slice() );
for ( k = -d; k <= d; k += 2 ) {
x = ( k === -d || ( k !== d && v[ off + k - 1 ] < v[ off + k + 1 ] ) ) ? v[ off + k + 1 ] : v[ off + k - 1 ] + 1;
y = x - k;
while ( x < n && y < m && A[ x ] === B[ y ] ) { x++; y++; }
v[ off + k ] = x;
if ( x >= n && y >= m ) { done = true; break; }
}
}
var steps = [], vv, prevK, px, py;
x = n; y = m;
for ( d = trace.length - 1; d >= 0 && ( x > 0 || y > 0 ); d-- ) {
vv = trace[ d ]; k = x - y;
prevK = ( k === -d || ( k !== d && vv[ off + k - 1 ] < vv[ off + k + 1 ] ) ) ? k + 1 : k - 1;
px = vv[ off + prevK ]; py = px - prevK;
while ( x > px && y > py ) { steps.push( [ '=', x - 1, y - 1 ] ); x--; y--; }
if ( d > 0 ) {
if ( x === px ) { steps.push( [ '+', x, y - 1 ] ); y--; } else { steps.push( [ '-', x - 1, y ] ); x--; }
}
}
steps.reverse();
var out = [];
function push( op, i1, i2, j1, j2 ) {
var last = out[ out.length - 1 ];
if ( last && ( op === 'equal' ) === ( last.op === 'equal' ) ) { last.i2 = i2; last.j2 = j2; return; }
out.push( { op: op, i1: i1, i2: i2, j1: j1, j2: j2 } );
}
if ( p ) { push( 'equal', 0, p, 0, p ); }
steps.forEach( function ( st ) {
if ( st[ 0 ] === '=' ) { push( 'equal', p + st[ 1 ], p + st[ 1 ] + 1, p + st[ 2 ], p + st[ 2 ] + 1 ); }
else if ( st[ 0 ] === '-' ) { push( 'change', p + st[ 1 ], p + st[ 1 ] + 1, p + st[ 2 ], p + st[ 2 ] ); }
else { push( 'change', p + st[ 1 ], p + st[ 1 ], p + st[ 2 ], p + st[ 2 ] + 1 ); }
} );
if ( s ) { push( 'equal', a.length - s, a.length, b.length - s, b.length ); }
out.forEach( function ( o ) {
if ( o.op !== 'equal' ) { o.op = o.i1 === o.i2 ? 'insert' : o.j1 === o.j2 ? 'delete' : 'replace'; }
} );
return out;
}
/* ---------- Wylie merge: keep the wiki's valid hand edits ---------- */
// Re-apply each hand hunk (plainOld -> wikiW) onto plainNew where the edition left that
// spot untouched; keep a hunk only if the result still converts back to tibNew.
function merge( plainOld, wikiW, plainNew, tibNew, id ) {
var hand = ops( plainOld, wikiW ).filter( function ( o ) { return o.op !== 'equal'; } );
var edit = ops( plainOld, plainNew ).filter( function ( o ) { return o.op === 'equal'; } );
function map( i, isEnd ) {
for ( var q = 0; q < edit.length; q++ ) {
var o = edit[ q ];
if ( isEnd ? ( i > o.i1 && i <= o.i2 ) : ( i >= o.i1 && i < o.i2 ) ) { return o.j1 + ( i - o.i1 ); }
}
return null;
}
var cur = plainNew, kept = 0, dropped = 0;
hand.slice().reverse().forEach( function ( h ) {
var a, b, cand;
if ( h.i1 === h.i2 ) { a = map( h.i1, false ); if ( a === null ) { a = map( h.i1, true ); } b = a; }
else { a = map( h.i1, false ); b = map( h.i2, true ); }
if ( a === null || b === null || b < a || plainNew.slice( a, b ) !== plainOld.slice( h.i1, h.i2 ) ) { dropped++; return; }
cand = cur.slice( 0, a ) + wikiW.slice( h.j1, h.j2 ) + cur.slice( b );
if ( valid( cand, tibNew, id ) ) { cur = cand; kept++; } else { dropped++; }
} );
return { w: cur, kept: kept, dropped: dropped };
}
// The Wylie to propose for a segment whose Tibetan goes from oldTib to newTib.
function proposeWylie( oldTib, oldW, newTib, id ) {
if ( !oldTib || !oldW ) { return { w: wylieOf( newTib, id ), how: 'generated' }; }
var m = merge( wylieOf( oldTib, id ), oldW, wylieOf( newTib, id ), newTib, id );
return { w: m.w, how: ( m.kept || m.dropped ) ? 'merged (kept ' + m.kept + ' hand edits, dropped ' + m.dropped + ')' : 'generated', kept: m.kept, dropped: m.dropped };
}
/* ---------- segment wikitext: read / replace one field, depth-aware ---------- */
// Locate |Field= value inside {{TranslationMemory ...}}: value runs until the next
// depth-1 '|' or the closing '}}'. Returns {start, end} of the value, or null.
function findField( text, field ) {
var s = text.indexOf( '{{TranslationMemory' ), d = 0, i = s, cur = null, vstart = 0, m;
if ( s < 0 ) { return null; }
while ( i < text.length ) {
if ( text.substr( i, 2 ) === '{{' ) { d++; i += 2; continue; }
if ( text.substr( i, 2 ) === '}}' ) {
d--;
if ( d === 0 ) { return cur === field ? { start: vstart, end: trimEnd( text, vstart, i ), close: i } : { close: i, missing: true }; }
i += 2; continue;
}
if ( d === 1 && text[ i ] === '|' ) {
if ( cur === field ) { return { start: vstart, end: trimEnd( text, vstart, i ) }; }
m = /^\|\s*([^=|}\n]+?)\s*=/.exec( text.slice( i ) );
if ( m ) { cur = m[ 1 ]; vstart = i + m[ 0 ].length; i = vstart; continue; }
cur = null;
}
i++;
}
return null;
}
function trimEnd( text, a, b ) { while ( b > a && text[ b - 1 ] === '\n' ) { b--; } return b; }
// A segment is safe to edit only if its {{ }} balance and the template closes: an unclosed
// {{note or a stray brace in another field (English uses {…} for editorial insertions)
// shifts every field boundary after it.
function wellFormed( text ) {
var d = 0, i = 0;
if ( text.indexOf( '{{TranslationMemory' ) < 0 ) { return false; }
while ( i < text.length ) {
if ( text.substr( i, 2 ) === '{{' ) { d++; i += 2; continue; }
if ( text.substr( i, 2 ) === '}}' ) { d--; if ( d < 0 ) { return false; } i += 2; continue; }
i++;
}
return d === 0 && findField( text, '\u0000' ) !== null;
}
function getField( text, field ) {
var f = findField( text, field );
return f && !f.missing ? text.slice( f.start, f.end ) : null;
}
// Replace (or add, before the closing braces) the value of a field.
function setField( text, field, value ) {
var f = findField( text, field );
if ( !f ) { throw new Error( 'no {{TranslationMemory}} in segment' ); }
if ( f.missing ) {
var before = text.slice( 0, f.close ).replace( /\n*$/, '' );
return before + '\n|' + field + '=' + value + '\n' + text.slice( f.close );
}
return text.slice( 0, f.start ) + value + text.slice( f.end );
}
/* ---------- display: tsheg-level diff as HTML ---------- */
function esc( s ) {
return String( s ).replace( /&/g, '&' ).replace( /</g, '<' ).replace( />/g, '>' ).replace( /"/g, '"' );
}
// syllables = runs ending in a tsheg, or single separators; notes/pagebreaks are atoms
function atoms( s ) {
var out = [];
pieces( s ).forEach( function ( p ) {
if ( p[ 0 ] === 'tpl' ) { out.push( p[ 1 ] ); return; }
var re = /[^་།\s]+་?|་|།|\n| +|\s/g, m;
while ( ( m = re.exec( p[ 1 ] ) ) ) { out.push( m[ 0 ] ); }
} );
return out;
}
function atomHtml( a ) {
var m = /^\{\{note\|([^|]*)\|/.exec( a );
if ( m ) { return '<sup class="ter-note" title="' + esc( a.slice( m[ 0 ].length, -2 ) ) + '">' + esc( m[ 1 ] ) + '</sup>'; }
m = /^\{\{pagebreak\|([^}|]*)/i.exec( a );
if ( m ) { return '<span class="ter-pb">[' + esc( m[ 1 ] ) + ']</span>'; }
if ( a === '\n' ) { return '<br>'; }
return esc( a );
}
// side: 'old' marks deletions, 'new' marks insertions
function diffHtml( oldS, newS, side ) {
var A = atoms( oldS ), B = atoms( newS ), html = '';
ops( A, B ).forEach( function ( o ) {
var seg = side === 'old' ? A.slice( o.i1, o.i2 ) : B.slice( o.j1, o.j2 );
var h = seg.map( atomHtml ).join( '' );
if ( o.op === 'equal' || !seg.length ) { html += h; return; }
// keep line breaks outside the mark so the highlight doesn't swallow them
html += '<mark class="ter-' + ( side === 'old' ? 'del' : 'ins' ) + '">' + h.replace( /<br>/g, '</mark><br><mark class="ter-' + ( side === 'old' ? 'del' : 'ins' ) + '">' ) + '</mark>';
} );
return html;
}
// notes that differ, as [{num, oldBody, newBody}] matched by best body similarity
function noteChanges( oldS, newS ) {
function notes( s ) {
return pieces( s ).filter( function ( p ) { return /^\{\{note\|/.test( p[ 1 ] ); } ).map( function ( p ) {
var m = /^\{\{note\|([^|]*)\|([\s\S]*)\}\}$/.exec( p[ 1 ] );
return m ? { num: m[ 1 ], body: m[ 2 ] } : null;
} ).filter( Boolean );
}
var O = notes( oldS ), N = notes( newS ), used = {}, out = [];
function sim( a, b ) {
var e = 0; ops( a, b ).forEach( function ( o ) { if ( o.op === 'equal' ) { e += o.i2 - o.i1; } } );
return 2 * e / ( a.length + b.length || 1 );
}
N.forEach( function ( n ) {
var best = -1, bs = 0;
O.forEach( function ( o, i ) { if ( used[ i ] ) { return; } var sc = sim( o.body, n.body ); if ( sc > bs ) { bs = sc; best = i; } } );
if ( best >= 0 && bs >= 0.5 ) {
used[ best ] = true;
if ( O[ best ].body !== n.body ) { out.push( { num: n.num, oldNum: O[ best ].num, oldBody: O[ best ].body, newBody: n.body } ); }
} else { out.push( { num: n.num, oldBody: null, newBody: n.body } ); }
} );
O.forEach( function ( o, i ) { if ( !used[ i ] ) { out.push( { num: null, oldNum: o.num, oldBody: o.body, newBody: null } ); } } );
return out;
}
function textDiffHtml( a, b, side ) {
var A = a.match( /[^་\s]+་?|\s+|་/g ) || [], B = b.match( /[^་\s]+་?|\s+|་/g ) || [], html = '';
ops( A, B ).forEach( function ( o ) {
var seg = ( side === 'old' ? A.slice( o.i1, o.i2 ) : B.slice( o.j1, o.j2 ) ).join( '' );
html += o.op === 'equal' || !seg ? esc( seg ) : '<mark class="ter-' + ( side === 'old' ? 'del' : 'ins' ) + '">' + esc( seg ) + '</mark>';
} );
return html;
}
return {
pieces: pieces, wylieOf: wylieOf, valid: valid, ops: ops, merge: merge, proposeWylie: proposeWylie,
getField: getField, setField: setField, wellFormed: wellFormed,
diffHtml: diffHtml, noteChanges: noteChanges, textDiffHtml: textDiffHtml, esc: esc
};
} ) );
}() );