CETEIcean 1.7.3 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "CETEIcean",
3
- "version": "1.7.3",
3
+ "version": "1.8.0",
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
6
  "type": "module",
@@ -16,20 +16,26 @@
16
16
  "type": "git",
17
17
  "url": "https://github.com/TEIC/CETEIcean.git"
18
18
  },
19
+ "exports": {
20
+ ".": "./src/CETEI.js",
21
+ "./utilities.js": "./src/utilities.js"
22
+ },
19
23
  "devDependencies": {
20
24
  "@babel/core": "^7.15.5",
21
25
  "@babel/preset-env": "7.15.6",
22
26
  "@rollup/plugin-babel": "^5.3.0",
23
27
  "babel-preset-env": "^1.7.0",
24
28
  "http-server": "^14.1.1",
29
+ "jsdom": "^21.1.0",
25
30
  "onchange": "^6.1.1",
26
31
  "rollup": "^2.57.0",
27
32
  "rollup-plugin-terser": "^7.0.2",
28
33
  "terser": "^5.14.2"
29
34
  },
30
35
  "scripts": {
31
- "build": "rollup -c rollup.config.js",
36
+ "build": "npm test && rollup -c rollup.config.js",
32
37
  "build:tutorial": "npm run build && cp dist/CETEI.js tutorial/js/CETEI.js",
33
- "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"
34
40
  }
35
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();