CETEIcean 1.7.1 → 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,871 @@
1
+ var CETEI = (function () {
2
+ 'use strict';
3
+
4
+ /*
5
+ Performs a deep copy operation of the input node while stripping
6
+ out child elements introduced by CETEIcean.
7
+ */
8
+ function copyAndReset(node) {
9
+ let clone = (n) => {
10
+ let result = n.nodeType === Node.ELEMENT_NODE?document.createElement(n.nodeName):n.cloneNode(true);
11
+ if (n.attributes) {
12
+ for (let att of Array.from(n.attributes)) {
13
+ if (att.name !== "data-processed") {
14
+ result.setAttribute(att.name,att.value);
15
+ }
16
+ }
17
+ }
18
+ for (let nd of Array.from(n.childNodes)){
19
+ if (nd.nodeType == Node.ELEMENT_NODE) {
20
+ if (!n.hasAttribute("data-empty")) {
21
+ if (nd.hasAttribute("data-original")) {
22
+ for (let childNode of Array.from(nd.childNodes)) {
23
+ let child = result.appendChild(clone(childNode));
24
+ if (child.nodeType === Node.ELEMENT_NODE && child.hasAttribute("data-origid")) {
25
+ child.setAttribute("id", child.getAttribute("data-origid"));
26
+ child.removeAttribute("data-origid");
27
+ }
28
+ }
29
+ return result;
30
+ } else {
31
+ result.appendChild(clone(nd));
32
+ }
33
+ }
34
+ }
35
+ else {
36
+ result.appendChild(nd.cloneNode());
37
+ }
38
+ }
39
+ return result;
40
+ };
41
+ return clone(node);
42
+ }
43
+
44
+ /*
45
+ Given a space-separated list of URLs (e.g. in a ref with multiple
46
+ targets), returns just the first one.
47
+ */
48
+ function first(urls) {
49
+ return urls.replace(/ .*$/, "");
50
+ }
51
+
52
+ /*
53
+ Wraps the content of the element parameter in a <span data-original>
54
+ with display set to "none".
55
+ */
56
+ function hideContent(elt, rewriteIds = true) {
57
+ if (elt.childNodes.length > 0) {
58
+ let hidden = document.createElement("span");
59
+ elt.appendChild(hidden);
60
+ hidden.setAttribute("hidden", "");
61
+ hidden.setAttribute("data-original", "");
62
+ for (let node of Array.from(elt.childNodes)) {
63
+ if (node !== hidden) {
64
+ hidden.appendChild(elt.removeChild(node));
65
+ }
66
+ }
67
+ if (rewriteIds) {
68
+ for (let e of Array.from(hidden.querySelectorAll("*"))) {
69
+ if (e.hasAttribute("id")) {
70
+ e.setAttribute("data-origid", e.getAttribute("id"));
71
+ e.removeAttribute("id");
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ function normalizeURI(urls) {
79
+ return this.rw(this.first(urls))
80
+ }
81
+
82
+ /*
83
+ Takes a string and a number and returns the original string
84
+ printed that number of times.
85
+ */
86
+ function repeat(str, times) {
87
+ let result = "";
88
+ for (let i = 0; i < times; i++) {
89
+ result += str;
90
+ }
91
+ return result;
92
+ }
93
+
94
+ /*
95
+ Takes a relative URL and rewrites it based on the base URL of the
96
+ HTML document
97
+ */
98
+ function rw(url) {
99
+ if (!url.match(/^(?:http|mailto|file|\/|#).*$/)) {
100
+ return this.base + url;
101
+ } else {
102
+ return url;
103
+ }
104
+ }
105
+
106
+ /*
107
+ Takes an element and serializes it to an XML string or, if the stripElt
108
+ parameter is set, serializes the element's content.
109
+ */
110
+ function serialize(el, stripElt) {
111
+ let str = "";
112
+ if (!stripElt) {
113
+ str += "&lt;" + el.getAttribute("data-origname");
114
+ for (let attr of Array.from(el.attributes)) {
115
+ if (!attr.name.startsWith("data-") && !(["id", "lang", "class"].includes(attr.name))) {
116
+ str += " " + attr.name + "=\"" + attr.value + "\"";
117
+ }
118
+ if (attr.name == "data-xmlns") {
119
+ str += " xmlns=\"" + attr.value +"\"";
120
+ }
121
+ }
122
+ if (el.childNodes.length > 0) {
123
+ str += ">";
124
+ } else {
125
+ str += "/>";
126
+ }
127
+ }
128
+ //TODO: Be smarter about skipping generated content with hidden original
129
+ for (let node of Array.from(el.childNodes)) {
130
+ switch (node.nodeType) {
131
+ case Node.ELEMENT_NODE:
132
+ str += this.serialize(node);
133
+ break;
134
+ case Node.PROCESSING_INSTRUCTION_NODE:
135
+ str += "&lt;?" + node.nodeValue + "?>";
136
+ break;
137
+ case Node.COMMENT_NODE:
138
+ str += "&lt;!--" + node.nodeValue + "-->";
139
+ break;
140
+ default:
141
+ str += node.nodeValue.replace(/</g, "&lt;");
142
+ }
143
+ }
144
+ if (!stripElt && el.childNodes.length > 0) {
145
+ str += "&lt;/" + el.getAttribute("data-origname") + ">";
146
+ }
147
+ return str;
148
+ }
149
+
150
+ function unEscapeEntities(str) {
151
+ return str.replace(/&gt;/, ">")
152
+ .replace(/&quot;/, "\"")
153
+ .replace(/&apos;/, "'")
154
+ .replace(/&amp;/, "&");
155
+ }
156
+
157
+ var utilities = /*#__PURE__*/Object.freeze({
158
+ copyAndReset: copyAndReset,
159
+ first: first,
160
+ hideContent: hideContent,
161
+ normalizeURI: normalizeURI,
162
+ repeat: repeat,
163
+ rw: rw,
164
+ serialize: serialize,
165
+ unEscapeEntities: unEscapeEntities
166
+ });
167
+
168
+ var defaultBehaviors = {
169
+ "namespaces": {
170
+ "tei": "http://www.tei-c.org/ns/1.0",
171
+ "teieg": "http://www.tei-c.org/ns/Examples",
172
+ "rng": "http://relaxng.org/ns/structure/1.0"
173
+ },
174
+ "tei": {
175
+ "eg": ["<pre>","</pre>"],
176
+ // inserts a link inside <ptr> using the @target; the link in the
177
+ // @href is piped through the rw (rewrite) function before insertion
178
+ "ptr": ["<a href=\"$rw@target\">$@target</a>"],
179
+ // wraps the content of the <ref> in an HTML link
180
+ "ref": [
181
+ ["[target]", ["<a href=\"$rw@target\">","</a>"]]
182
+ ],
183
+ "graphic": function(elt) {
184
+ let content = new Image();
185
+ content.src = this.rw(elt.getAttribute("url"));
186
+ if (elt.hasAttribute("width")) {
187
+ content.setAttribute("width",elt.getAttribute("width"));
188
+ }
189
+ if (elt.hasAttribute("height")) {
190
+ content.setAttribute("height",elt.getAttribute("height"));
191
+ }
192
+ return content;
193
+ },
194
+ "list": [
195
+ // will only run on a list where @type="gloss"
196
+ ["[type=gloss]", function(elt) {
197
+ let dl = document.createElement("dl");
198
+ for (let child of Array.from(elt.children)) {
199
+ if (child.nodeType == Node.ELEMENT_NODE) {
200
+ if (child.localName == "tei-label") {
201
+ let dt = document.createElement("dt");
202
+ dt.innerHTML = child.innerHTML;
203
+ dl.appendChild(dt);
204
+ }
205
+ if (child.localName == "tei-item") {
206
+ let dd = document.createElement("dd");
207
+ dd.innerHTML = child.innerHTML;
208
+ dl.appendChild(dd);
209
+ }
210
+ }
211
+ }
212
+ return dl;
213
+ }
214
+ ]],
215
+ "note": [
216
+ // Make endnotes
217
+ ["[place=end]", function(elt){
218
+ if (!this.noteIndex){
219
+ this["noteIndex"] = 1;
220
+ } else {
221
+ this.noteIndex++;
222
+ }
223
+ let id = "_note_" + this.noteIndex;
224
+ let link = document.createElement("a");
225
+ link.setAttribute("id", "src" + id);
226
+ link.setAttribute("href", "#" + id);
227
+ link.innerHTML = this.noteIndex;
228
+ let content = document.createElement("sup");
229
+ content.appendChild(link);
230
+ let notes = this.dom.querySelector("ol.notes");
231
+ if (!notes) {
232
+ notes = document.createElement("ol");
233
+ notes.setAttribute("class", "notes");
234
+ this.dom.appendChild(notes);
235
+ }
236
+ let note = document.createElement("li");
237
+ note.id = id;
238
+ note.innerHTML = elt.innerHTML;
239
+ notes.appendChild(note);
240
+ return content;
241
+ }],
242
+ ["_", ["(",")"]]
243
+ ],
244
+ "table": function(elt) {
245
+ let table = document.createElement("table");
246
+ table.innerHTML = elt.innerHTML;
247
+ if (table.firstElementChild.localName == "tei-head") {
248
+ let head = table.firstElementChild;
249
+ head.remove();
250
+ let caption = document.createElement("caption");
251
+ caption.innerHTML = head.innerHTML;
252
+ table.appendChild(caption);
253
+ }
254
+ for (let row of Array.from(table.querySelectorAll("tei-row"))) {
255
+ let tr = document.createElement("tr");
256
+ tr.innerHTML = row.innerHTML;
257
+ for (let attr of Array.from(row.attributes)) {
258
+ tr.setAttribute(attr.name, attr.value);
259
+ }
260
+ row.parentElement.replaceChild(tr, row);
261
+ }
262
+ for (let cell of Array.from(table.querySelectorAll("tei-cell"))) {
263
+ let td = document.createElement("td");
264
+ if (cell.hasAttribute("cols")) {
265
+ td.setAttribute("colspan", cell.getAttribute("cols"));
266
+ }
267
+ td.innerHTML = cell.innerHTML;
268
+ for (let attr of Array.from(cell.attributes)) {
269
+ td.setAttribute(attr.name, attr.value);
270
+ }
271
+ cell.parentElement.replaceChild(td, cell);
272
+ }
273
+ return table;
274
+ },
275
+ "teiHeader": function(e) {
276
+ this.hideContent(e, false);
277
+ },
278
+ "title": [
279
+ ["tei-titlestmt>tei-title", function(elt) {
280
+ let title = document.createElement("title");
281
+ title.innerHTML = elt.innerText;
282
+ document.querySelector("head").appendChild(title);
283
+ }]
284
+ ]
285
+ },
286
+ "teieg": {
287
+ "egXML": function(elt) {
288
+ let pre = document.createElement("pre");
289
+ pre.innerHTML = this.serialize(elt, true);
290
+ return pre;
291
+ }
292
+ }
293
+ };
294
+
295
+ /*
296
+ Appends any element returned by the function passed in the first
297
+ parameter to the element in the second parameter. If the function
298
+ returns nothing, this is a no-op aside from any side effects caused
299
+ by the provided function.
300
+
301
+ Called by getHandler() and fallback()
302
+ */
303
+ function append(fn, elt) {
304
+ if (elt) {
305
+ let content = fn.call(utilities, elt);
306
+ if (content && !childExists(elt.firstElementChild, content.nodeName)) {
307
+ appendBasic(elt, content);
308
+ }
309
+ } else {
310
+ return function() {
311
+ if (!this.hasAttribute("data-processed")) {
312
+ let content = fn.call(utilities, this);
313
+ if (content && !childExists(this.firstElementChild, content.nodeName)) {
314
+ appendBasic(this, content);
315
+ }
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ function appendBasic(elt, content) {
322
+ hideContent(elt);
323
+ elt.appendChild(content);
324
+ }
325
+
326
+ // Given an element, return its qualified name as defined in a behaviors object
327
+ function bName(e) {
328
+ return e.tagName.substring(0,e.tagName.indexOf("-")).toLowerCase() + ":" + e.getAttribute("data-origname");
329
+ }
330
+
331
+ /*
332
+ Private method called by append(). Takes a child element and a name, and recurses through the
333
+ child's siblings until an element with that name is found, returning true if it is and false if not.
334
+ */
335
+ function childExists(elt, name) {
336
+ if (elt && elt.nodeName == name) {
337
+ return true;
338
+ } else {
339
+ return elt && elt.nextElementSibling && childExists(elt.nextElementSibling, name);
340
+ }
341
+ }
342
+
343
+ /*
344
+ Takes a template in the form of either an array of 1 or 2
345
+ strings or an object with CSS selector keys and either functions
346
+ or arrays as described above. Returns a closure around a function
347
+ that can be called in the element constructor or applied to an
348
+ individual element.
349
+
350
+ Called by the getHandler() and getFallback() methods
351
+ */
352
+ function decorator(template) {
353
+ if (Array.isArray(template) && !Array.isArray(template[0])) {
354
+ return applyDecorator(template)
355
+ }
356
+ return (elt) => {
357
+ for (let rule of template) {
358
+ if (elt.matches(rule[0]) || rule[0] === "_") {
359
+ if (Array.isArray(rule[1])) {
360
+ return decorator(rule[1]).call(this, elt);
361
+ } else {
362
+ return rule[1].call(this, elt);
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+
369
+ function applyDecorator(strings) {
370
+ return function (elt) {
371
+ let copy = [];
372
+ for (let i = 0; i < strings.length; i++) {
373
+ copy.push(template(strings[i], elt));
374
+ }
375
+ return insert(elt, copy);
376
+ }
377
+ }
378
+
379
+ /*
380
+ Returns the fallback function for the given element name.
381
+ Called by fallback().
382
+ */
383
+ function getFallback(behaviors, fn) {
384
+ if (behaviors[fn]) {
385
+ if (behaviors[fn] instanceof Function) {
386
+ return behaviors[fn];
387
+ } else {
388
+ return decorator(behaviors[fn]);
389
+ }
390
+ }
391
+ }
392
+
393
+ /*
394
+ Returns the handler function for the given element name
395
+ Called by define().
396
+ */
397
+ function getHandler(behaviors, fn) {
398
+ if (behaviors[fn]) {
399
+ if (behaviors[fn] instanceof Function) {
400
+ return append(behaviors[fn]);
401
+ } else {
402
+ return append(decorator(behaviors[fn]));
403
+ }
404
+ }
405
+ }
406
+
407
+ function insert(elt, strings) {
408
+ let span = document.createElement("span");
409
+ for (let node of Array.from(elt.childNodes)) {
410
+ if (node.nodeType === Node.ELEMENT_NODE && !node.hasAttribute("data-processed")) {
411
+ processElement(node);
412
+ }
413
+ }
414
+ // If we have before and after tags have them parsed by
415
+ // .innerHTML and then add the content to the resulting child
416
+ if (strings[0].match("<[^>]+>") && strings[1] && strings[1].match("<[^>]+>")) {
417
+ span.innerHTML = strings[0] + (strings[1]?strings[1]:"");
418
+ for (let node of Array.from(elt.childNodes)) {
419
+ span.firstElementChild.appendChild(node.cloneNode(true));
420
+ }
421
+ } else {
422
+ span.innerHTML = strings[0];
423
+ span.setAttribute("data-before", strings[0].replace(/<[^>]+>/g,"").length);
424
+ for (let node of Array.from(elt.childNodes)) {
425
+ span.appendChild(node.cloneNode(true));
426
+ }
427
+ if (strings.length > 1) {
428
+ span.innerHTML += strings[1];
429
+ span.setAttribute("data-after", strings[1].replace(/<[^>]+>/g,"").length);
430
+ }
431
+ }
432
+ return span;
433
+ }
434
+
435
+ // Runs behaviors recursively on the supplied element and children
436
+ function processElement(elt) {
437
+ if (elt.hasAttribute("data-origname") && ! elt.hasAttribute("data-processed")) {
438
+ let fn = getFallback(bName(elt));
439
+ if (fn) {
440
+ append(fn,elt);
441
+ elt.setAttribute("data-processed", "");
442
+ }
443
+ }
444
+ for (let node of Array.from(elt.childNodes)) {
445
+ if (node.nodeType === Node.ELEMENT_NODE) {
446
+ processElement(node);
447
+ }
448
+ }
449
+ }
450
+
451
+ // Given a qualified name (e.g. tei:text), return the element name
452
+ function tagName(name) {
453
+ if (name.includes(":"), 1) {
454
+ return name.replace(/:/,"-").toLowerCase(); }
455
+ }
456
+
457
+ function template(str, elt) {
458
+ let result = str;
459
+ if (str.search(/\$(\w*)(@([a-zA-Z:]+))/ )) {
460
+ let re = /\$(\w*)@([a-zA-Z:]+)/g;
461
+ let replacements;
462
+ while (replacements = re.exec(str)) {
463
+ if (elt.hasAttribute(replacements[2])) {
464
+ if (replacements[1] && utilities[replacements[1]]) {
465
+ result = result.replace(replacements[0], utilities[replacements[1]](elt.getAttribute(replacements[2])));
466
+ } else {
467
+ result = result.replace(replacements[0], elt.getAttribute(replacements[2]));
468
+ }
469
+ }
470
+ }
471
+ }
472
+ return result;
473
+ }
474
+
475
+ /*
476
+ Registers the list of elements provided with the browser.
477
+ Called by makeHTML5(), but can be called independently if, for example,
478
+ you've created Custom Elements via an XSLT transformation instead.
479
+ */
480
+ function define(names) {
481
+ for (let name of names) {
482
+ try {
483
+ let fn = getHandler(this.behaviors, name);
484
+ window.customElements.define(tagName(name), class extends HTMLElement {
485
+ constructor() {
486
+ super();
487
+ if (!this.matches(":defined")) { // "Upgraded" undefined elements can have attributes & children; new elements can't
488
+ if (fn) {
489
+ fn.call(this);
490
+ }
491
+ // We don't want to double-process elements, so add a flag
492
+ this.setAttribute("data-processed", "");
493
+ }
494
+ }
495
+ // Process new elements when they are connected to the browser DOM
496
+ connectedCallback() {
497
+ if (!this.hasAttribute("data-processed")) {
498
+ if (fn) {
499
+ fn.call(this);
500
+ }
501
+ this.setAttribute("data-processed", "");
502
+ }
503
+ };
504
+ });
505
+ } catch (error) {
506
+ // When using the same CETEIcean instance for multiple TEI files, this error becomes very common.
507
+ // It's muted by default unless the debug option is set.
508
+ if (this.debug) {
509
+ console.log(tagName(name) + " couldn't be registered or is already registered.");
510
+ console.log(error);
511
+ }
512
+ }
513
+
514
+ }
515
+ }
516
+
517
+ /*
518
+ Provides fallback functionality for browsers where Custom Elements
519
+ are not supported.
520
+
521
+ Like define(), this is called by makeHTML5(), but can be called
522
+ independently.
523
+ */
524
+ function fallback(names) {
525
+ for (let name of names) {
526
+ let fn = getFallback(this.behaviors, name);
527
+ if (fn) {
528
+ for (let elt of Array.from((
529
+ this.dom && !this.done
530
+ ? this.dom
531
+ : document
532
+ ).getElementsByTagName(tagName(name)))) {
533
+ if (!elt.hasAttribute("data-processed")) {
534
+ append(fn, elt);
535
+ }
536
+ }
537
+ }
538
+ }
539
+ }
540
+
541
+ /*
542
+ Add a user-defined set of behaviors to CETEIcean's processing
543
+ workflow. Added behaviors will override predefined behaviors with the
544
+ same name.
545
+ */
546
+ function addBehaviors(bhvs) {
547
+ if (bhvs.namespaces) {
548
+ for (let prefix of Object.keys(bhvs.namespaces)) {
549
+ if (!this.namespaces.has(bhvs.namespaces[prefix]) && !Array.from(this.namespaces.values()).includes(prefix)) {
550
+ this.namespaces.set(bhvs.namespaces[prefix], prefix);
551
+ }
552
+ }
553
+ }
554
+ for (let prefix of this.namespaces.values()) {
555
+ if (bhvs[prefix]) {
556
+ for (let b of Object.keys(bhvs[prefix])) {
557
+ this.behaviors[`${prefix}:${b}`] = bhvs[prefix][b];
558
+ }
559
+ }
560
+ }
561
+ if (bhvs["handlers"]) {
562
+ console.log("Behavior handlers are no longer used.");
563
+ }
564
+ if (bhvs["fallbacks"]) {
565
+ console.log("Fallback behaviors are no longer used.");
566
+ }
567
+ }
568
+
569
+ /*
570
+ Adds or replaces an individual behavior. Takes a namespace prefix or namespace definition,
571
+ the element name, and the behavior. E.g.
572
+ addBehavior("tei", "add", ["`","`"]) for an already-declared namespace or
573
+ addBehavior({"doc": "http://docbook.org/ns/docbook"}, "note", ["[","]"]) for a new one
574
+ */
575
+ function addBehavior(ns, element, b) {
576
+ let p;
577
+ if (ns === Object(ns)) {
578
+ for (let prefix of Object.keys(ns)) {
579
+ if (!this.namespaces.has(ns[prefix])) {
580
+ this.namespaces.set(ns[prefix], prefix);
581
+ p = prefix;
582
+ }
583
+ }
584
+ } else {
585
+ p = ns;
586
+ }
587
+ this.behaviors[`${p}:${element}`] = b;
588
+ }
589
+
590
+ // Define or apply behaviors for the document
591
+ function applyBehaviors() {
592
+ if (window.customElements) {
593
+ define.call(this, this.els);
594
+ } else {
595
+ fallback.call(this, this.els);
596
+ }
597
+ }
598
+
599
+ function learnElementNames(XML_dom, namespaces) {
600
+ const root = XML_dom.documentElement;
601
+ const els = new Set(
602
+ Array.from(root.querySelectorAll("*"),
603
+ e => (
604
+ namespaces.has(e.namespaceURI ? e.namespaceURI : "")
605
+ ? namespaces.get(e.namespaceURI ? e.namespaceURI : "") + ":"
606
+ : ""
607
+ ) + e.localName) );
608
+ // Add the root element to the array
609
+ els.add(
610
+ (
611
+ namespaces.has(root.namespaceURI ? root.namespaceURI : "")
612
+ ? namespaces.get(root.namespaceURI ? root.namespaceURI : "") + ":"
613
+ : ""
614
+ ) + root.localName);
615
+ return els
616
+ }
617
+
618
+ class CETEI {
619
+ constructor(options){
620
+ this.options = options ? options : {};
621
+
622
+ // Bind methods
623
+ this.addBehaviors = addBehaviors.bind(this);
624
+ this.addBehavior = addBehavior.bind(this);
625
+ this.applyBehaviors = applyBehaviors.bind(this);
626
+
627
+ // Bind utilities
628
+ this.utilities = {};
629
+ for (const u of Object.keys(utilities)) {
630
+ this.utilities[u] = utilities[u].bind(this);
631
+ }
632
+
633
+ // Set properties
634
+ this.els = [];
635
+ this.namespaces = new Map();
636
+ this.behaviors = {};
637
+ this.hasStyle = false;
638
+ this.prefixDefs = [];
639
+ this.debug = this.options.debug === true ? true : false;
640
+ if (this.options.base) {
641
+ this.base = this.options.base;
642
+ } else {
643
+ try {
644
+ if (window) {
645
+ this.base = window.location.href.replace(/\/[^\/]*$/, "/");
646
+ }
647
+ } catch (e) {
648
+ this.base = "";
649
+ }
650
+ }
651
+ if (!this.options.omitDefaultBehaviors) {
652
+ this.addBehaviors(defaultBehaviors);
653
+ }
654
+ }
655
+
656
+ /*
657
+ Returns a Promise that fetches an XML source document from the URL
658
+ provided in the first parameter and then calls the makeHTML5 method
659
+ on the returned document.
660
+ */
661
+ getHTML5(XML_url, callback, perElementFn){
662
+ if (window.location.href.startsWith(this.base) && (XML_url.indexOf("/") >= 0)) {
663
+ this.base = XML_url.replace(/\/[^\/]*$/, "/");
664
+ }
665
+ // Get XML from XML_url and create a promise
666
+ let promise = new Promise( function (resolve, reject) {
667
+ let client = new XMLHttpRequest();
668
+ client.open('GET', XML_url);
669
+ client.send();
670
+ client.onload = function () {
671
+ if (this.status >= 200 && this.status < 300) {
672
+ resolve(this.response);
673
+ } else {
674
+ reject(this.statusText);
675
+ }
676
+ };
677
+ client.onerror = function () {
678
+ reject(this.statusText);
679
+ };
680
+ })
681
+ .catch( function(reason) {
682
+ console.log("Could not get XML file.");
683
+ if (this.debug) {
684
+ console.log(reason);
685
+ }
686
+ });
687
+
688
+ return promise.then((XML) => {
689
+ return this.makeHTML5(XML, callback, perElementFn);
690
+ });
691
+ }
692
+
693
+ /*
694
+ Converts the supplied XML string into HTML5 Custom Elements. If a callback
695
+ function is supplied, calls it on the result.
696
+ */
697
+ makeHTML5(XML, callback, perElementFn){
698
+ // XML is assumed to be a string
699
+ let XML_dom = ( new DOMParser() ).parseFromString(XML, "text/xml");
700
+ return this.domToHTML5(XML_dom, callback, perElementFn);
701
+ }
702
+
703
+ /*
704
+ Converts the supplied XML DOM into HTML5 Custom Elements. If a callback
705
+ function is supplied, calls it on the result.
706
+ */
707
+ domToHTML5(XML_dom, callback, perElementFn){
708
+
709
+ this.els = learnElementNames(XML_dom, this.namespaces);
710
+
711
+ let convertEl = (el) => {
712
+ // Elements with defined namespaces get the prefix mapped to that element. All others keep
713
+ // their namespaces and are copied as-is.
714
+ let newElement;
715
+ if (this.namespaces.has(el.namespaceURI ? el.namespaceURI : "")) {
716
+ let prefix = this.namespaces.get(el.namespaceURI ? el.namespaceURI : "");
717
+ newElement = document.createElement(`${prefix}-${el.localName}`);
718
+ } else {
719
+ newElement = document.importNode(el, false);
720
+ }
721
+ // Copy attributes; @xmlns, @xml:id, @xml:lang, and
722
+ // @rendition get special handling.
723
+ for (let att of Array.from(el.attributes)) {
724
+ if (att.name == "xmlns") {
725
+ //Strip default namespaces, but hang on to the values
726
+ newElement.setAttribute("data-xmlns", att.value);
727
+ } else {
728
+ newElement.setAttribute(att.name, att.value);
729
+ }
730
+ if (att.name == "xml:id") {
731
+ newElement.setAttribute("id", att.value);
732
+ }
733
+ if (att.name == "xml:lang") {
734
+ newElement.setAttribute("lang", att.value);
735
+ }
736
+ if (att.name == "rendition") {
737
+ newElement.setAttribute("class", att.value.replace(/#/g, ""));
738
+ }
739
+ }
740
+ // Preserve element name so we can use it later
741
+ newElement.setAttribute("data-origname", el.localName);
742
+ // If element is empty, flag it
743
+ if (el.childNodes.length == 0) {
744
+ newElement.setAttribute("data-empty", "");
745
+ }
746
+ // Turn <rendition scheme="css"> elements into HTML styles
747
+ if (el.localName == "tagsDecl") {
748
+ let style = document.createElement("style");
749
+ for (let node of Array.from(el.childNodes)){
750
+ if (node.nodeType == Node.ELEMENT_NODE && node.localName == "rendition" && node.getAttribute("scheme") == "css") {
751
+ let rule = "";
752
+ if (node.hasAttribute("selector")) {
753
+ //rewrite element names in selectors
754
+ rule += node.getAttribute("selector").replace(/([^#, >]+\w*)/g, "tei-$1").replace(/#tei-/g, "#") + "{\n";
755
+ rule += node.textContent;
756
+ } else {
757
+ rule += "." + node.getAttribute("xml:id") + "{\n";
758
+ rule += node.textContent;
759
+ }
760
+ rule += "\n}\n";
761
+ style.appendChild(document.createTextNode(rule));
762
+ }
763
+ }
764
+ if (style.childNodes.length > 0) {
765
+ newElement.appendChild(style);
766
+ this.hasStyle = true;
767
+ }
768
+ }
769
+ // Get prefix definitions
770
+ if (el.localName == "prefixDef") {
771
+ this.prefixDefs.push(el.getAttribute("ident"));
772
+ this.prefixDefs[el.getAttribute("ident")] = {
773
+ "matchPattern": el.getAttribute("matchPattern"),
774
+ "replacementPattern": el.getAttribute("replacementPattern")
775
+ };
776
+ }
777
+ for (let node of Array.from(el.childNodes)) {
778
+ if (node.nodeType == Node.ELEMENT_NODE) {
779
+ newElement.appendChild(convertEl(node));
780
+ }
781
+ else {
782
+ newElement.appendChild(node.cloneNode());
783
+ }
784
+ }
785
+ if (perElementFn) {
786
+ perElementFn(newElement, el);
787
+ }
788
+ return newElement;
789
+ };
790
+
791
+ this.dom = convertEl(XML_dom.documentElement);
792
+
793
+ this.applyBehaviors();
794
+ this.done = true;
795
+ if (callback) {
796
+ callback(this.dom, this);
797
+ window.dispatchEvent(ceteiceanLoad);
798
+ } else {
799
+ window.dispatchEvent(ceteiceanLoad);
800
+ return this.dom;
801
+ }
802
+ }
803
+
804
+ /*
805
+ If the TEI document defines CSS styles in its tagsDecl, this method
806
+ copies them into the wrapper HTML document's head.
807
+ */
808
+ addStyle(doc, data) {
809
+ if (this.hasStyle) {
810
+ doc.getElementsByTagName("head").item(0).appendChild(data.getElementsByTagName("style").item(0).cloneNode(true));
811
+ }
812
+ }
813
+
814
+ /*
815
+ To change a namespace -> prefix mapping, the namespace must first be
816
+ unset. Takes a namespace URI. In order to process a TEI P4 document, e.g.,
817
+ the TEI namespace must be unset before it can be set to the empty string.
818
+ */
819
+ unsetNamespace(ns) {
820
+ this.namespaces.delete(ns);
821
+ }
822
+
823
+ /*
824
+ Sets the base URL for the document. Used to rewrite relative links in the
825
+ XML source (which may be in a completely different location from the HTML
826
+ wrapper).
827
+ */
828
+ setBaseUrl(base) {
829
+ this.base = base;
830
+ }
831
+
832
+ /**********************
833
+ * Utility functions *
834
+ **********************/
835
+
836
+ static savePosition() {
837
+ window.localStorage.setItem("scroll", window.scrollY);
838
+ }
839
+
840
+ static restorePosition() {
841
+ if (!window.location.hash) {
842
+ if (window.localStorage.getItem("scroll")) {
843
+ setTimeout(function() {
844
+ window.scrollTo(0, localStorage.getItem("scroll"));
845
+ }, 100);
846
+ }
847
+ } else {
848
+ setTimeout(function() {
849
+ document.querySelector(window.location.hash).scrollIntoView();
850
+ }, 100);
851
+ }
852
+ }
853
+
854
+ }
855
+
856
+ // Make main class available to pre-ES6 browser environments
857
+ try {
858
+ if (window) {
859
+ window.CETEI = CETEI;
860
+ window.addEventListener("beforeunload", CETEI.savePosition);
861
+ var ceteiceanLoad = new Event("ceteiceanload");
862
+ window.addEventListener("ceteiceanload", CETEI.restorePosition);
863
+ }
864
+ } catch (e) {
865
+ // window not defined
866
+ console.log(e);
867
+ }
868
+
869
+ return CETEI;
870
+
871
+ }());