CETEIcean 1.7.2 → 1.8.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,6 +52,20 @@ new CETEI({
52
52
  })
53
53
  ```
54
54
 
55
+ ### Usage with Node
56
+
57
+ CETEIcean can be used on the server by providing a DOM implementation, such as [JSDOM](https://github.com/jsdom/jsdom). You can pass a document object as an option when instantiating CETEIcean.
58
+
59
+ ```js
60
+ import { JSDOM } from 'jsdom';
61
+ import CETEI from 'CETEIcean';
62
+
63
+ const jdom = new JSDOM(`<TEI xmlns="http://www.tei-c.org/ns/1.0" />`, {contentType: 'text/xml'});
64
+ new CETEI({
65
+ documentObject: jdom.window.document
66
+ })
67
+ ```
68
+
55
69
  ### Other methods
56
70
 
57
71
  #### getHTML5( url, callback, perElementFn )
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "CETEIcean",
3
- "version": "1.7.2",
3
+ "version": "1.8.0-beta.1",
4
4
  "description": "JavaScript library to load a TEI XML document and register it as HTML5 custom elements.",
5
5
  "main": "src/CETEI.js",
6
+ "type": "module",
6
7
  "keywords": [
7
8
  "TEI",
8
9
  "XML",
@@ -15,20 +16,26 @@
15
16
  "type": "git",
16
17
  "url": "https://github.com/TEIC/CETEIcean.git"
17
18
  },
19
+ "exports": {
20
+ ".": "./src/CETEI.js",
21
+ "./utilities.js": "./src/utilities.js"
22
+ },
18
23
  "devDependencies": {
19
24
  "@babel/core": "^7.15.5",
20
25
  "@babel/preset-env": "7.15.6",
21
26
  "@rollup/plugin-babel": "^5.3.0",
22
27
  "babel-preset-env": "^1.7.0",
23
28
  "http-server": "^14.1.1",
29
+ "jsdom": "^21.1.0",
24
30
  "onchange": "^6.1.1",
25
31
  "rollup": "^2.57.0",
26
32
  "rollup-plugin-terser": "^7.0.2",
27
33
  "terser": "^5.14.2"
28
34
  },
29
35
  "scripts": {
30
- "build": "rollup -c rollup.config.js",
36
+ "build": "npm test && rollup -c rollup.config.js",
31
37
  "build:tutorial": "npm run build && cp dist/CETEI.js tutorial/js/CETEI.js",
32
- "dev": "npm run build && http-server -p 8888 & onchange src -- npm run build"
38
+ "dev": "npm run build && http-server -p 8888 & onchange src -- npm run build",
39
+ "test": "node test/nodeTest.js"
33
40
  }
34
41
  }
package/src/CETEI.js CHANGED
@@ -7,6 +7,17 @@ class CETEI {
7
7
  constructor(options){
8
8
  this.options = options ? options : {}
9
9
 
10
+ // Set a local reference to the Document object
11
+ // Determine document in this order of preference: options, window, global
12
+ this.document = this.options.documentObject ? this.options.documentObject : undefined
13
+ if (this.document === undefined) {
14
+ if (typeof window !== 'undefined' && window.document) {
15
+ this.document = window.document
16
+ } else if (typeof global !== 'undefined' && global.document) {
17
+ this.document = global.document
18
+ }
19
+ }
20
+
10
21
  // Bind methods
11
22
  this.addBehaviors = addBehaviors.bind(this);
12
23
  this.addBehavior = addBehavior.bind(this);
@@ -100,12 +111,7 @@ class CETEI {
100
111
  return this.domToHTML5(this.XML_dom, callback, perElementFn);
101
112
  }
102
113
 
103
- /*
104
- Converts the supplied XML DOM into HTML5 Custom Elements. If a callback
105
- function is supplied, calls it on the result.
106
- */
107
- domToHTML5(XML_dom, callback, perElementFn){
108
-
114
+ preprocess(XML_dom, callback, perElementFn) {
109
115
  this.els = learnElementNames(XML_dom, this.namespaces);
110
116
 
111
117
  let convertEl = (el) => {
@@ -114,9 +120,9 @@ class CETEI {
114
120
  let newElement;
115
121
  if (this.namespaces.has(el.namespaceURI ? el.namespaceURI : "")) {
116
122
  let prefix = this.namespaces.get(el.namespaceURI ? el.namespaceURI : "");
117
- newElement = document.createElement(`${prefix}-${el.localName}`);
123
+ newElement = this.document.createElement(`${prefix}-${el.localName}`);
118
124
  } else {
119
- newElement = document.importNode(el, false);
125
+ newElement = this.document.importNode(el, false);
120
126
  }
121
127
  // Copy attributes; @xmlns, @xml:id, @xml:lang, and
122
128
  // @rendition get special handling.
@@ -148,16 +154,18 @@ class CETEI {
148
154
  }
149
155
  // <head> elements need to know their level
150
156
  if (el.localName == "head") {
157
+ // 1 is XPathResult.NUMBER_TYPE
151
158
  let level = XML_dom.evaluate("count(ancestor::*[tei:head])", el, function(ns) {
152
159
  if (ns == "tei") return "http://www.tei-c.org/ns/1.0";
153
- }, XPathResult.NUMBER_TYPE, null);
160
+ }, 1, null);
154
161
  newElement.setAttribute("data-level", level.numberValue);
155
162
  }
156
163
  // Turn <rendition scheme="css"> elements into HTML styles
157
164
  if (el.localName == "tagsDecl") {
158
- let style = document.createElement("style");
165
+ let style = this.document.createElement("style");
159
166
  for (let node of Array.from(el.childNodes)){
160
- if (node.nodeType == Node.ELEMENT_NODE && node.localName == "rendition" && node.getAttribute("scheme") == "css") {
167
+ // nodeType 1 is Node.ELEMENT_NODE
168
+ if (node.nodeType == 1 && node.localName == "rendition" && node.getAttribute("scheme") == "css") {
161
169
  let rule = "";
162
170
  if (node.hasAttribute("selector")) {
163
171
  //rewrite element names in selectors
@@ -168,7 +176,7 @@ class CETEI {
168
176
  rule += node.textContent;
169
177
  }
170
178
  rule += "\n}\n";
171
- style.appendChild(document.createTextNode(rule));
179
+ style.appendChild(this.document.createTextNode(rule));
172
180
  }
173
181
  }
174
182
  if (style.childNodes.length > 0) {
@@ -185,7 +193,8 @@ class CETEI {
185
193
  };
186
194
  }
187
195
  for (let node of Array.from(el.childNodes)) {
188
- if (node.nodeType == Node.ELEMENT_NODE) {
196
+ // Node.ELEMENT_NODE
197
+ if (node.nodeType == 1 ) {
189
198
  newElement.appendChild(convertEl(node));
190
199
  }
191
200
  else {
@@ -201,6 +210,27 @@ class CETEI {
201
210
  this.dom = convertEl(XML_dom.documentElement);
202
211
  this.utilities.dom = this.dom;
203
212
 
213
+ if (callback) {
214
+ callback(this.dom, this);
215
+ if (window) {
216
+ window.dispatchEvent(ceteiceanLoad);
217
+ }
218
+ } else {
219
+ if (typeof window !== 'undefined') {
220
+ window.dispatchEvent(ceteiceanLoad);
221
+ }
222
+ return this.dom;
223
+ }
224
+ }
225
+
226
+ /*
227
+ Converts the supplied XML DOM into HTML5 Custom Elements. If a callback
228
+ function is supplied, calls it on the result.
229
+ */
230
+ domToHTML5(XML_dom, callback, perElementFn){
231
+
232
+ this.preprocess(XML_dom, null, perElementFn);
233
+
204
234
  this.applyBehaviors();
205
235
  this.done = true;
206
236
  if (callback) {
@@ -209,7 +239,7 @@ class CETEI {
209
239
  window.dispatchEvent(ceteiceanLoad);
210
240
  }
211
241
  } else {
212
- if (window) {
242
+ if (typeof window !== 'undefined') {
213
243
  window.dispatchEvent(ceteiceanLoad);
214
244
  }
215
245
  return this.dom;
@@ -223,7 +253,7 @@ class CETEI {
223
253
  c.processPage();
224
254
  */
225
255
  processPage() {
226
- this.els = learnCustomElementNames(document);
256
+ this.els = learnCustomElementNames(this.document);
227
257
  this.applyBehaviors();
228
258
  }
229
259
 
@@ -348,7 +378,7 @@ getFallback(behaviors, fn) {
348
378
  if (behaviors[fn] instanceof Function) {
349
379
  return behaviors[fn];
350
380
  } else {
351
- return decorator(behaviors[fn]);
381
+ return this.decorator(behaviors[fn]);
352
382
  }
353
383
  }
354
384
  }
@@ -368,9 +398,10 @@ getHandler(behaviors, fn) {
368
398
  }
369
399
 
370
400
  insert(elt, strings) {
371
- let span = document.createElement("span");
401
+ let span = this.document.createElement("span");
372
402
  for (let node of Array.from(elt.childNodes)) {
373
- if (node.nodeType === Node.ELEMENT_NODE && !node.hasAttribute("data-processed")) {
403
+ // nodeType 1 is Node.ELEMENT_NODE
404
+ if (node.nodeType === 1 && !node.hasAttribute("data-processed")) {
374
405
  this.processElement(node);
375
406
  }
376
407
  }
@@ -402,21 +433,13 @@ processElement(elt) {
402
433
  }
403
434
  }
404
435
  for (let node of Array.from(elt.childNodes)) {
405
- if (node.nodeType === Node.ELEMENT_NODE) {
436
+ // nodeType 1 is Node.ELEMENT_NODE
437
+ if (node.nodeType === 1) {
406
438
  this.processElement(node);
407
439
  }
408
440
  }
409
441
  }
410
442
 
411
- // Given a qualified name (e.g. tei:text), return the element name
412
- tagName(name) {
413
- if (name.includes(":"), 1) {
414
- return name.replace(/:/,"-").toLowerCase();
415
- } else {
416
- return "ceteicean-" + name.toLowerCase();
417
- }
418
- }
419
-
420
443
  template(str, elt) {
421
444
  let result = str;
422
445
  if (str.search(/\$(\w*)(@([a-zA-Z:]+))/ )) {
@@ -439,7 +462,7 @@ template(str, elt) {
439
462
 
440
463
  // Define or apply behaviors for the document
441
464
  applyBehaviors() {
442
- if (window.customElements) {
465
+ if (typeof window !== 'undefined' && window.customElements) {
443
466
  this.define.call(this, this.els);
444
467
  } else {
445
468
  this.fallback.call(this, this.els);
@@ -453,37 +476,8 @@ applyBehaviors() {
453
476
  */
454
477
  define(names) {
455
478
  for (let name of names) {
456
- try {
457
- const fn = this.getHandler(this.behaviors, name);
458
- window.customElements.define(this.tagName(name), class extends HTMLElement {
459
- constructor() {
460
- super();
461
- if (!this.matches(":defined")) { // "Upgraded" undefined elements can have attributes & children; new elements can't
462
- if (fn) {
463
- fn.call(this);
464
- }
465
- // We don't want to double-process elements, so add a flag
466
- this.setAttribute("data-processed", "");
467
- }
468
- }
469
- // Process new elements when they are connected to the browser DOM
470
- connectedCallback() {
471
- if (!this.hasAttribute("data-processed")) {
472
- if (fn) {
473
- fn.call(this);
474
- }
475
- this.setAttribute("data-processed", "");
476
- }
477
- };
478
- });
479
- } catch (error) {
480
- // When using the same CETEIcean instance for multiple TEI files, this error becomes very common.
481
- // It's muted by default unless the debug option is set.
482
- if (this.debug) {
483
- console.log(this.tagName(name) + " couldn't be registered or is already registered.");
484
- console.log(error);
485
- }
486
- }
479
+ const fn = this.getHandler(this.behaviors, name);
480
+ utilities.defineCustomElement(name, fn, this.debug);
487
481
  }
488
482
  }
489
483
 
@@ -496,15 +490,15 @@ define(names) {
496
490
  */
497
491
  fallback(names) {
498
492
  for (let name of names) {
499
- let fn = getFallback(this.behaviors, name);
493
+ let fn = this.getFallback(this.behaviors, name);
500
494
  if (fn) {
501
495
  for (let elt of Array.from((
502
496
  this.dom && !this.done
503
497
  ? this.dom
504
- : document
505
- ).getElementsByTagName(tagName(name)))) {
498
+ : this.document
499
+ ).getElementsByTagName(utilities.tagName(name)))) {
506
500
  if (!elt.hasAttribute("data-processed")) {
507
- append(fn, elt);
501
+ this.append(fn, elt);
508
502
  }
509
503
  }
510
504
  }
@@ -530,7 +524,7 @@ fallback(names) {
530
524
  }
531
525
  } else {
532
526
  setTimeout(function() {
533
- let h = document.querySelector(window.decodeURI(window.location.hash));
527
+ let h = this.document.querySelector(window.decodeURI(window.location.hash));
534
528
  if (h) {
535
529
  h.scrollIntoView();
536
530
  }
@@ -541,7 +535,7 @@ fallback(names) {
541
535
  }
542
536
 
543
537
  try {
544
- if (window) {
538
+ if (typeof window !== 'undefined') {
545
539
  window.CETEI = CETEI;
546
540
  window.addEventListener("beforeunload", CETEI.savePosition);
547
541
  var ceteiceanLoad = new Event("ceteiceanload");
@@ -551,4 +545,4 @@ try {
551
545
  console.log(e);
552
546
  }
553
547
 
554
- export default CETEI
548
+ export default CETEI;
@@ -27,16 +27,18 @@ export default {
27
27
  "list": [
28
28
  // will only run on a list where @type="gloss"
29
29
  ["[type=gloss]", function(elt) {
30
- let dl = document.createElement("dl");
30
+ const doc = elt.ownerDocument;
31
+ let dl = doc.createElement("dl");
31
32
  for (let child of Array.from(elt.children)) {
32
- if (child.nodeType == Node.ELEMENT_NODE) {
33
+ // nodeType 1 is Node.ELEMENT_NODE
34
+ if (child.nodeType == 1) {
33
35
  if (child.localName == "tei-label") {
34
- let dt = document.createElement("dt");
36
+ let dt = doc.createElement("dt");
35
37
  dt.innerHTML = child.innerHTML;
36
38
  dl.appendChild(dt);
37
39
  }
38
40
  if (child.localName == "tei-item") {
39
- let dd = document.createElement("dd");
41
+ let dd = doc.createElement("dd");
40
42
  dd.innerHTML = child.innerHTML;
41
43
  dl.appendChild(dd);
42
44
  }
@@ -48,25 +50,26 @@ export default {
48
50
  "note": [
49
51
  // Make endnotes
50
52
  ["[place=end]", function(elt){
53
+ const doc = elt.ownerDocument;
51
54
  if (!this.noteIndex){
52
55
  this["noteIndex"] = 1;
53
56
  } else {
54
57
  this.noteIndex++;
55
58
  }
56
59
  let id = "_note_" + this.noteIndex;
57
- let link = document.createElement("a");
60
+ let link = doc.createElement("a");
58
61
  link.setAttribute("id", "src" + id);
59
62
  link.setAttribute("href", "#" + id);
60
63
  link.innerHTML = this.noteIndex;
61
- let content = document.createElement("sup");
64
+ let content = doc.createElement("sup");
62
65
  content.appendChild(link);
63
- let notes = this.dom.querySelector("ol.notes");
66
+ let notes = doc.querySelector("ol.notes");
64
67
  if (!notes) {
65
- notes = document.createElement("ol");
68
+ notes = doc.createElement("ol");
66
69
  notes.setAttribute("class", "notes");
67
70
  this.dom.appendChild(notes);
68
71
  }
69
- let note = document.createElement("li");
72
+ let note = doc.createElement("li");
70
73
  note.id = id;
71
74
  note.innerHTML = elt.innerHTML
72
75
  notes.appendChild(note);
@@ -79,15 +82,17 @@ export default {
79
82
  },
80
83
  "title": [
81
84
  ["tei-titlestmt>tei-title", function(elt) {
82
- let title = document.createElement("title");
85
+ const doc = elt.ownerDocument;
86
+ let title = doc.createElement("title");
83
87
  title.innerHTML = elt.innerText;
84
- document.querySelector("head").appendChild(title);
88
+ doc.querySelector("head").appendChild(title);
85
89
  }]
86
90
  ],
87
91
  },
88
92
  "teieg": {
89
93
  "egXML": function(elt) {
90
- let pre = document.createElement("pre");
94
+ const doc = elt.ownerDocument;
95
+ let pre = doc.createElement("pre");
91
96
  let content = this.serialize(elt, true).replace(/</g, "&lt;");
92
97
  let ws = content.match(/^[\t ]+/);
93
98
  if (ws) {
package/src/utilities.js CHANGED
@@ -16,8 +16,12 @@ export function getOrdinality(elt, name) {
16
16
  out child elements introduced by CETEIcean.
17
17
  */
18
18
  export function copyAndReset(node) {
19
+ const doc = node.ownerDocument;
19
20
  let clone = (n) => {
20
- let result = n.nodeType === Node.ELEMENT_NODE?document.createElement(n.nodeName):n.cloneNode(true);
21
+ // nodeType 1 is Node.ELEMENT_NODE
22
+ let result = n.nodeType === 1
23
+ ? doc.createElement(n.nodeName)
24
+ : n.cloneNode(true);
21
25
  if (n.attributes) {
22
26
  for (let att of Array.from(n.attributes)) {
23
27
  if (att.name !== "data-processed") {
@@ -26,12 +30,14 @@ export function copyAndReset(node) {
26
30
  }
27
31
  }
28
32
  for (let nd of Array.from(n.childNodes)){
29
- if (nd.nodeType == Node.ELEMENT_NODE) {
33
+ // nodeType 1 is Node.ELEMENT_NODE
34
+ if (nd.nodeType == 1) {
30
35
  if (!n.hasAttribute("data-empty")) {
31
36
  if (nd.hasAttribute("data-original")) {
32
37
  for (let childNode of Array.from(nd.childNodes)) {
33
38
  let child = result.appendChild(clone(childNode));
34
- if (child.nodeType === Node.ELEMENT_NODE && child.hasAttribute("data-origid")) {
39
+ // nodeType 1 is Node.ELEMENT_NODE
40
+ if (child.nodeType === 1 && child.hasAttribute("data-origid")) {
35
41
  child.setAttribute("id", child.getAttribute("data-origid"));
36
42
  child.removeAttribute("data-origid");
37
43
  }
@@ -64,14 +70,16 @@ export function first(urls) {
64
70
  with display set to "none".
65
71
  */
66
72
  export function hideContent(elt, rewriteIds = true) {
73
+ const doc = elt.ownerDocument;
67
74
  if (elt.childNodes.length > 0) {
68
- let hidden = document.createElement("span");
75
+ let hidden = doc.createElement("span");
69
76
  elt.appendChild(hidden);
70
77
  hidden.setAttribute("hidden", "");
71
78
  hidden.setAttribute("data-original", "");
72
79
  for (let node of Array.from(elt.childNodes)) {
73
80
  if (node !== hidden) {
74
- if (node.nodeType === Node.ELEMENT_NODE) {
81
+ // nodeType 1 is Node.ELEMENT_NODE
82
+ if (node.nodeType === 1) {
75
83
  node.setAttribute("data-processed", "");
76
84
  for (let e of node.querySelectorAll("*")) {
77
85
  e.setAttribute("data-processed", "");
@@ -148,7 +156,8 @@ export function serialize(el, stripElt, ws) {
148
156
  let ignorable = (txt) => {
149
157
  return !(/[^\t\n\r ]/.test(txt));
150
158
  }
151
- if (!stripElt && el.nodeType == Node.ELEMENT_NODE) {
159
+ // nodeType 1 is Node.ELEMENT_NODE
160
+ if (!stripElt && el.nodeType == 1) {
152
161
  if ((typeof ws === "string") && ws !== "") {
153
162
  str += "\n" + ws + "<";
154
163
  } else {
@@ -173,18 +182,21 @@ export function serialize(el, stripElt, ws) {
173
182
  }
174
183
  //TODO: Be smarter about skipping generated content with hidden original
175
184
  for (let node of Array.from(el.childNodes)) {
185
+ // nodeType 1 is Node.ELEMENT_NODE
186
+ // nodeType 7 is Node.PROCESSING_INSTRUCTION_NODE
187
+ // nodeType 8 is Node.COMMENT_NODE
176
188
  switch (node.nodeType) {
177
- case Node.ELEMENT_NODE:
189
+ case 1:
178
190
  if (typeof ws === "string") {
179
- str += this.serialize(node, false, ws + " ");
191
+ str += serialize(node, false, ws + " ");
180
192
  } else {
181
- str += this.serialize(node, false, ws);
193
+ str += serialize(node, false, ws);
182
194
  }
183
195
  break;
184
- case Node.PROCESSING_INSTRUCTION_NODE:
196
+ case 7:
185
197
  str += "<?" + node.nodeValue + "?>";
186
198
  break;
187
- case Node.COMMENT_NODE:
199
+ case 8:
188
200
  str += "<!--" + node.nodeValue + "-->";
189
201
  break;
190
202
  default:
@@ -214,3 +226,50 @@ export function unEscapeEntities(str) {
214
226
  .replace(/&apos;/, "'")
215
227
  .replace(/&amp;/, "&");
216
228
  }
229
+
230
+ // Given a qualified name (e.g. tei:text), return the element name
231
+ export function tagName(name) {
232
+ if (name.includes(":"), 1) {
233
+ return name.replace(/:/,"-").toLowerCase();
234
+ } else {
235
+ return "ceteicean-" + name.toLowerCase();
236
+ }
237
+ }
238
+
239
+ export function defineCustomElement(name, behavior = null, debug = false) {
240
+ /*
241
+ Registers the list of elements provided with the browser.
242
+ Called by makeHTML5(), but can be called independently if, for example,
243
+ you've created Custom Elements via an XSLT transformation instead.
244
+ */
245
+ try {
246
+ window.customElements.define(tagName(name), class extends HTMLElement {
247
+ constructor() {
248
+ super();
249
+ if (!this.matches(":defined")) { // "Upgraded" undefined elements can have attributes & children; new elements can't
250
+ if (behavior) {
251
+ behavior.call(this);
252
+ }
253
+ // We don't want to double-process elements, so add a flag
254
+ this.setAttribute("data-processed", "");
255
+ }
256
+ }
257
+ // Process new elements when they are connected to the browser DOM
258
+ connectedCallback() {
259
+ if (!this.hasAttribute("data-processed")) {
260
+ if (behavior) {
261
+ behavior.call(this);
262
+ }
263
+ this.setAttribute("data-processed", "");
264
+ }
265
+ };
266
+ });
267
+ } catch (error) {
268
+ // When using the same CETEIcean instance for multiple TEI files, this error becomes very common.
269
+ // It's muted by default unless the debug option is set.
270
+ if (debug) {
271
+ console.log(tagName(name) + " couldn't be registered or is already registered.");
272
+ console.log(error);
273
+ }
274
+ }
275
+ }
@@ -20,7 +20,6 @@
20
20
 
21
21
  CETEIcean.makeHTML5(TEI, function(data) {
22
22
  document.getElementById("TEI").appendChild(data);
23
- CETEIcean.addStyle(document, data);
24
23
  });
25
24
  }
26
25
  };
@@ -0,0 +1,24 @@
1
+ import { JSDOM } from 'jsdom';
2
+ import CETEI from '../src/CETEI.js';
3
+
4
+ const jdom = new JSDOM(`<TEI xmlns="http://www.tei-c.org/ns/1.0"><div>test</div></TEI>`, {contentType: 'text/xml'});
5
+ const teiDoc = jdom.window.document;
6
+
7
+ const test = () => {
8
+ console.log('Get HTML5 from JSDOM');
9
+ const processedTEI = (new CETEI({documentObject: teiDoc})).domToHTML5(teiDoc);
10
+ if (processedTEI) {
11
+ console.log(' > pass');
12
+ } else {
13
+ console.log(' > fail');
14
+ return;
15
+ }
16
+ console.log('Check content');
17
+ if (processedTEI.querySelector("tei-div")) {
18
+ console.log(' > pass');
19
+ } else {
20
+ console.log(' > fail');
21
+ }
22
+ };
23
+
24
+ test();