CETEIcean 1.8.0 → 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
- package/.vscode/settings.json +6 -0
- package/README.md +1 -4
- package/TEI-CE_specification.md +86 -0
- package/package.json +1 -1
- package/src/CETEI.js +53 -44
- package/src/behaviors.js +1 -1
- package/src/defaultBehaviors.js +6 -2
- package/src/dom.js +1 -1
- package/src/utilities.js +129 -32
- package/test/CETEIcean.css +3 -0
- package/test/P5.css +362 -0
- package/test/addBehaviorTest.html +1 -1
- package/test/nodeTest.js +13 -3
- package/test/tcw20.html +960 -0
- package/test/tcw22.xml +468 -0
- package/test/testTEI.xml +5 -1
- package/tutorial_es/Ruy_Diaz-La_Argentina_Manuscrita.tei.xml +11579 -0
- package/xslt/make-CETEIcean-3.xsl +79 -0
package/README.md
CHANGED
@@ -12,10 +12,7 @@ documents to be displayed in a web browser without first transforming them to
|
|
12
12
|
HTML. It uses the emerging [Web Components](http://webcomponents.org) standards,
|
13
13
|
especially [Custom Elements](http://w3c.github.io/webcomponents/spec/custom/). It
|
14
14
|
works by loading the TEI file dynamically, renaming the elements to follow the
|
15
|
-
Custom Elements conventions, and registering them with the browser.
|
16
|
-
that support Web Components will use them to add the appropriate display and
|
17
|
-
behaviors to the TEI elements; other browsers will use fallback methods to
|
18
|
-
achieve the same result.
|
15
|
+
Custom Elements conventions, and registering them with the browser.
|
19
16
|
|
20
17
|
Because it preserves the full structure and information from your TEI data model,
|
21
18
|
CETEIcean allows you to build rich web applications from your source documents
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# TEI Custom Elements Specification and Contract
|
2
|
+
|
3
|
+
When CETEIcean processes a TEI document, the document structures will by default be converted as follows:
|
4
|
+
|
5
|
+
## Elements
|
6
|
+
|
7
|
+
All tag names will be converted into [prefix]-[lowercased tagname]. The prefix can be defined in a behaviors object. There must be one prefix defined per XML namespace. There are three predefined prefixes:
|
8
|
+
|
9
|
+
```js
|
10
|
+
"namespaces": {
|
11
|
+
"tei": "http://www.tei-c.org/ns/1.0",
|
12
|
+
"teieg": "http://www.tei-c.org/ns/Examples",
|
13
|
+
"rng": "http://relaxng.org/ns/structure/1.0"
|
14
|
+
}
|
15
|
+
```
|
16
|
+
|
17
|
+
These prefixes can be remapped in the behaviors object passed to CETEIcean.
|
18
|
+
|
19
|
+
### CETEIcean-created Elements
|
20
|
+
|
21
|
+
CETEIcean may add the following elements to markup it processes:
|
22
|
+
|
23
|
+
* `<cetei-content>`: holds any content returned from a behavior function call.
|
24
|
+
* `<cetei-original`: wraps any original content that has been hidden and replaced by the result of a behavior function call.
|
25
|
+
|
26
|
+
## Attributes
|
27
|
+
|
28
|
+
CETEIcean adds a number of data attributes when it processes an XML element:
|
29
|
+
|
30
|
+
* `@data-origname`: contains the original tag name with whatever upper- or lowercasing it used. The HTML DOM normalizes tag names to uppercase.
|
31
|
+
* `@data-origatts`: contains a list of attribute names on the element before it was processed with whatever upper- or lowercasing they used. The HTML DOM normalizes attribute names to lowercase.
|
32
|
+
* `@data-processed`: a flag CETEIcean uses to determine whether elements have had behaviors applied. Normally, this will happen upon insertion into the browser DOM.
|
33
|
+
* `@data-empty`: a flag CETEIcean uses to mark empty elements (these may not remain empty after behaviors have been applied).
|
34
|
+
|
35
|
+
### Automatically Converted Attributes
|
36
|
+
|
37
|
+
* `@xml:id` is preserved and the value copied to `@id`.
|
38
|
+
* `@xml:lang` is preserved and the value copied to `@lang`.
|
39
|
+
* `@xmlns` is removed and its value copied to `@data-xmlns`.
|
40
|
+
* `@rendition` is preserved and its value copied to `@class`.
|
41
|
+
|
42
|
+
## Processing Instructions and Comments
|
43
|
+
|
44
|
+
PIs and comments are copied over into the result, so, for example, `<?xml-model?>` PIs pointing to a schema will be present before the root element of the converted document. The XML declaration, if present, is not a PI, and will not be copied over.
|
45
|
+
|
46
|
+
## Document Type Declarations
|
47
|
+
|
48
|
+
DTDs are not copied over into the result.
|
49
|
+
|
50
|
+
## Contract
|
51
|
+
|
52
|
+
If a source XML document has been processed into CETEIcean Custom Elements and is then re-serialized into XML using the `CETEI.utilities` instance method `resetAndSerialize()`, then the result will be logically equivalent to the source, provided that additional processing has not altered the document. Note that custom behaviors have direct access to the DOM, and thus are capable of directly manipulating the document in any way. No guarantees can be made as to the consistency of a serialized document if it has been rearranged, fo example.
|
53
|
+
|
54
|
+
This contract means that CETEIcean can be used in conjunction with in-browser editing or annotation tools to produce prospectively valid output XML.
|
55
|
+
|
56
|
+
For example, the built-in behavior for TEI `<ref>` is to create an HTML anchor tag with the content being the content of the `<ref>` and the `@href` attribute containing the pointer in the `@target` of the `<ref>` (or the first pointer if there are more than one). Given an element like
|
57
|
+
|
58
|
+
```xml
|
59
|
+
<ref target="https://github.com/TEIC/TEI/blob/dev/P5/Source/guidelines-en.xml" mimeType="application/tei+xml">guidelines-en.xml</ref>
|
60
|
+
```
|
61
|
+
|
62
|
+
The structure CETEIcean creates will look like this:
|
63
|
+
|
64
|
+
```xml
|
65
|
+
<tei-ref target="https://github.com/TEIC/TEI/blob/dev/P5/Source/guidelines-en.xml" mimetype="application/tei+xml" data-origname="ref" data-origatts="target mimeType" data-processed="">
|
66
|
+
<cetei-original hidden="" data-original="">guidelines-en.xml</cetei-original>
|
67
|
+
<a href="https://github.com/TEIC/TEI/blob/dev/P5/Source/guidelines-en.xml">guidelines-en.xml</a>
|
68
|
+
</tei-ref>
|
69
|
+
```
|
70
|
+
|
71
|
+
If this element is loaded into a variable and then passed to the `CETEI.utilities.copyAndReset()` function, the result will be:
|
72
|
+
|
73
|
+
```xml
|
74
|
+
<tei-ref target="https://github.com/TEIC/TEI/blob/dev/P5/Source/guidelines-en.xml" mimetype="application/tei+xml" data-origname="ref" data-origatts="target mimeType">guidelines-en.xml</tei-ref>
|
75
|
+
```
|
76
|
+
|
77
|
+
that is, the element as it was after CETEIcean has loaded it, but before it was inserted into the document DOM and had behaviors applied to it.
|
78
|
+
|
79
|
+
If this result is then passed to the `CETEI.utilities.serialize()` function, the output is
|
80
|
+
|
81
|
+
```xml
|
82
|
+
<ref target="https://github.com/TEIC/TEI/blob/dev/P5/Source/guidelines-en.xml" mimeType="application/tei+xml">guidelines-en.xml</ref>
|
83
|
+
```
|
84
|
+
|
85
|
+
CETEIcean's contract means that
|
86
|
+
|
package/package.json
CHANGED
package/src/CETEI.js
CHANGED
@@ -61,7 +61,6 @@ class CETEI {
|
|
61
61
|
window.removeEventListener("ceteiceanload", CETEI.restorePosition);
|
62
62
|
}
|
63
63
|
}
|
64
|
-
|
65
64
|
}
|
66
65
|
|
67
66
|
/*
|
@@ -69,36 +68,22 @@ class CETEI {
|
|
69
68
|
provided in the first parameter and then calls the makeHTML5 method
|
70
69
|
on the returned document.
|
71
70
|
*/
|
72
|
-
getHTML5(XML_url, callback, perElementFn){
|
71
|
+
async getHTML5(XML_url, callback, perElementFn){
|
73
72
|
if (window && window.location.href.startsWith(this.base) && (XML_url.indexOf("/") >= 0)) {
|
74
73
|
this.base = XML_url.replace(/\/[^\/]*$/, "/");
|
75
74
|
}
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
client.send();
|
81
|
-
client.onload = function () {
|
82
|
-
if (this.status >= 200 && this.status < 300) {
|
83
|
-
resolve(this.response);
|
84
|
-
} else {
|
85
|
-
reject(this.statusText);
|
86
|
-
}
|
87
|
-
};
|
88
|
-
client.onerror = function () {
|
89
|
-
reject(this.statusText);
|
90
|
-
};
|
91
|
-
})
|
92
|
-
.catch( function(reason) {
|
93
|
-
console.log("Could not get XML file.");
|
94
|
-
if (this.debug) {
|
95
|
-
console.log(reason);
|
96
|
-
}
|
97
|
-
});
|
98
|
-
|
99
|
-
return promise.then((XML) => {
|
75
|
+
try {
|
76
|
+
const response = await fetch(XML_url);
|
77
|
+
if (response.ok) {
|
78
|
+
const XML = await response.text();
|
100
79
|
return this.makeHTML5(XML, callback, perElementFn);
|
101
|
-
|
80
|
+
} else {
|
81
|
+
console.log(`Could not get XML file ${XML_url}.\nServer returned ${response.status}: ${response.statusText}`);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
catch (error) {
|
85
|
+
console.log(error);
|
86
|
+
}
|
102
87
|
}
|
103
88
|
|
104
89
|
/*
|
@@ -120,7 +105,7 @@ class CETEI {
|
|
120
105
|
let newElement;
|
121
106
|
if (this.namespaces.has(el.namespaceURI ? el.namespaceURI : "")) {
|
122
107
|
let prefix = this.namespaces.get(el.namespaceURI ? el.namespaceURI : "");
|
123
|
-
newElement = this.document.createElement(`${prefix}-${el.localName}`);
|
108
|
+
newElement = this.document.createElement(`${prefix}-${el.localName.toLowerCase()}`);
|
124
109
|
} else {
|
125
110
|
newElement = this.document.importNode(el, false);
|
126
111
|
}
|
@@ -207,8 +192,23 @@ class CETEI {
|
|
207
192
|
return newElement;
|
208
193
|
}
|
209
194
|
|
210
|
-
this.dom =
|
211
|
-
|
195
|
+
this.dom = this.document.createDocumentFragment();
|
196
|
+
for (let node of Array.from(XML_dom.childNodes)) {
|
197
|
+
// Node.ELEMENT_NODE
|
198
|
+
if (node.nodeType == 1) {
|
199
|
+
this.dom.appendChild(convertEl(node));
|
200
|
+
}
|
201
|
+
// Node.PROCESSING_INSTRUCTION_NODE
|
202
|
+
if (node.nodeType == 7) {
|
203
|
+
this.dom.appendChild(this.document.importNode(node, true));
|
204
|
+
}
|
205
|
+
// Node.COMMENT_NODE
|
206
|
+
if (node.nodeType == 8) {
|
207
|
+
this.dom.appendChild(this.document.importNode(node, true));
|
208
|
+
}
|
209
|
+
}
|
210
|
+
// DocumentFragments don't work in the same ways as other nodes, so use the root element.
|
211
|
+
this.utilities.dom = this.dom.firstElementChild;
|
212
212
|
|
213
213
|
if (callback) {
|
214
214
|
callback(this.dom, this);
|
@@ -255,6 +255,9 @@ class CETEI {
|
|
255
255
|
processPage() {
|
256
256
|
this.els = learnCustomElementNames(this.document);
|
257
257
|
this.applyBehaviors();
|
258
|
+
if (window) {
|
259
|
+
window.dispatchEvent(ceteiceanLoad);
|
260
|
+
}
|
258
261
|
}
|
259
262
|
|
260
263
|
/*
|
@@ -282,19 +285,19 @@ class CETEI {
|
|
282
285
|
by the provided function.
|
283
286
|
|
284
287
|
Called by getHandler() and fallback()
|
285
|
-
*/
|
288
|
+
*/
|
286
289
|
append(fn, elt) {
|
287
290
|
let self = this;
|
288
|
-
if (elt) {
|
291
|
+
if (elt && !elt.hasAttribute('data-processed')) {
|
289
292
|
let content = fn.call(self.utilities, elt);
|
290
|
-
if (content
|
293
|
+
if (content) {
|
291
294
|
self.appendBasic(elt, content);
|
292
295
|
}
|
293
296
|
} else {
|
294
297
|
return function() {
|
295
298
|
if (!this.hasAttribute("data-processed")) {
|
296
299
|
let content = fn.call(self.utilities, this);
|
297
|
-
if (content
|
300
|
+
if (content) {
|
298
301
|
self.appendBasic(this, content);
|
299
302
|
}
|
300
303
|
}
|
@@ -398,7 +401,7 @@ getHandler(behaviors, fn) {
|
|
398
401
|
}
|
399
402
|
|
400
403
|
insert(elt, strings) {
|
401
|
-
let
|
404
|
+
let content = this.document.createElement("cetei-content");
|
402
405
|
for (let node of Array.from(elt.childNodes)) {
|
403
406
|
// nodeType 1 is Node.ELEMENT_NODE
|
404
407
|
if (node.nodeType === 1 && !node.hasAttribute("data-processed")) {
|
@@ -408,19 +411,23 @@ insert(elt, strings) {
|
|
408
411
|
// If we have before and after tags have them parsed by
|
409
412
|
// .innerHTML and then add the content to the resulting child
|
410
413
|
if (strings[0].match("<[^>]+>") && strings[1] && strings[1].match("<[^>]+>")) {
|
411
|
-
|
414
|
+
content.innerHTML = strings[0] + elt.innerHTML + (strings[1]?strings[1]:"");
|
412
415
|
} else {
|
413
|
-
|
414
|
-
|
416
|
+
content.innerHTML = strings[0];
|
417
|
+
content.setAttribute("data-before", strings[0].replace(/<[^>]+>/g,"").length);
|
415
418
|
for (let node of Array.from(elt.childNodes)) {
|
416
|
-
|
419
|
+
content.appendChild(node.cloneNode(true));
|
417
420
|
}
|
418
421
|
if (strings.length > 1) {
|
419
|
-
|
420
|
-
|
422
|
+
content.innerHTML += strings[1];
|
423
|
+
content.setAttribute("data-after", strings[1].replace(/<[^>]+>/g,"").length);
|
421
424
|
}
|
422
425
|
}
|
423
|
-
|
426
|
+
if (content.childNodes.length < 2) {
|
427
|
+
return content.firstChild;
|
428
|
+
} else {
|
429
|
+
return content;
|
430
|
+
}
|
424
431
|
}
|
425
432
|
|
426
433
|
// Runs behaviors recursively on the supplied element and children
|
@@ -482,7 +489,7 @@ define(names) {
|
|
482
489
|
}
|
483
490
|
|
484
491
|
/*
|
485
|
-
Provides fallback functionality for
|
492
|
+
Provides fallback functionality for environments where Custom Elements
|
486
493
|
are not supported.
|
487
494
|
|
488
495
|
Like define(), this is called by makeHTML5(), but can be called
|
@@ -496,9 +503,10 @@ fallback(names) {
|
|
496
503
|
this.dom && !this.done
|
497
504
|
? this.dom
|
498
505
|
: this.document
|
499
|
-
).
|
506
|
+
).querySelectorAll(utilities.tagName(name)))) {
|
500
507
|
if (!elt.hasAttribute("data-processed")) {
|
501
508
|
this.append(fn, elt);
|
509
|
+
elt.setAttribute("data-processed", "");
|
502
510
|
}
|
503
511
|
}
|
504
512
|
}
|
@@ -518,6 +526,7 @@ fallback(names) {
|
|
518
526
|
if (!window.location.hash) {
|
519
527
|
let scroll;
|
520
528
|
if (scroll = window.sessionStorage.getItem(window.location + "-scroll")) {
|
529
|
+
window.sessionStorage.removeItem(window.location + "-scroll");
|
521
530
|
setTimeout(function() {
|
522
531
|
window.scrollTo(0, scroll);
|
523
532
|
}, 100);
|
package/src/behaviors.js
CHANGED
@@ -21,7 +21,7 @@ export function addBehaviors(bhvs) {
|
|
21
21
|
}
|
22
22
|
if (bhvs["functions"]) {
|
23
23
|
for (let fn of Object.keys(bhvs["functions"])) {
|
24
|
-
this.utilities[fn] = bhvs["functions"][fn];
|
24
|
+
this.utilities[fn] = bhvs["functions"][fn].bind(this.utilities);
|
25
25
|
}
|
26
26
|
}
|
27
27
|
if (bhvs["handlers"]) {
|
package/src/defaultBehaviors.js
CHANGED
@@ -9,10 +9,12 @@ export default {
|
|
9
9
|
// inserts a link inside <ptr> using the @target; the link in the
|
10
10
|
// @href is piped through the rw (rewrite) function before insertion
|
11
11
|
"ptr": ["<a href=\"$rw@target\">$@target</a>"],
|
12
|
-
// wraps the content of the <ref> in an HTML link
|
12
|
+
// wraps the content of the <ref> in an HTML link with the @target in
|
13
|
+
// the @href. If there are multiple @targets, only the first is used.
|
13
14
|
"ref": [
|
14
15
|
["[target]", ["<a href=\"$rw@target\">","</a>"]]
|
15
16
|
],
|
17
|
+
// creates an img tag with the @url as the src attribute
|
16
18
|
"graphic": function(elt) {
|
17
19
|
let content = new Image();
|
18
20
|
content.src = this.rw(elt.getAttribute("url"));
|
@@ -71,15 +73,17 @@ export default {
|
|
71
73
|
}
|
72
74
|
let note = doc.createElement("li");
|
73
75
|
note.id = id;
|
74
|
-
note.innerHTML = elt.innerHTML
|
76
|
+
note.innerHTML = elt.innerHTML;
|
75
77
|
notes.appendChild(note);
|
76
78
|
return content;
|
77
79
|
}],
|
78
80
|
["_", ["(",")"]]
|
79
81
|
],
|
82
|
+
// Hide the teiHeader by default
|
80
83
|
"teiHeader": function(e) {
|
81
84
|
this.hideContent(e, false);
|
82
85
|
},
|
86
|
+
// Make the title element the HTML title
|
83
87
|
"title": [
|
84
88
|
["tei-titlestmt>tei-title", function(elt) {
|
85
89
|
const doc = elt.ownerDocument;
|
package/src/dom.js
CHANGED
@@ -16,5 +16,5 @@ export function learnElementNames(XML_dom, namespaces) {
|
|
16
16
|
}
|
17
17
|
|
18
18
|
export function learnCustomElementNames(HTML_dom) {
|
19
|
-
return Array.from(HTML_dom.querySelectorAll("*[data-origname]"), e => e.localName.replace(/(\w+)-.+/,"$1:") + e.getAttribute("data-origname"));
|
19
|
+
return new Set(Array.from(HTML_dom.querySelectorAll("*[data-origname]"), e => e.localName.replace(/(\w+)-.+/,"$1:") + e.getAttribute("data-origname")));
|
20
20
|
}
|
package/src/utilities.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
export function getOrdinality(elt, name) {
|
2
2
|
let pos = 1;
|
3
3
|
let e = elt;
|
4
|
-
while (e && e.previousElementSibling !== null && (name?e.previousElementSibling.localName == name:true)) {
|
4
|
+
while (e && e.previousElementSibling !== null && (name ? e.previousElementSibling.localName == name : true)) {
|
5
5
|
pos++;
|
6
6
|
e = e.previousElementSibling;
|
7
7
|
if (!e.previousElementSibling) {
|
@@ -17,11 +17,21 @@ export function getOrdinality(elt, name) {
|
|
17
17
|
*/
|
18
18
|
export function copyAndReset(node) {
|
19
19
|
const doc = node.ownerDocument;
|
20
|
-
let clone = (n) => {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
let clone = (n) => {
|
21
|
+
let result;
|
22
|
+
switch (n.nodeType) {
|
23
|
+
case 1: // nodeType 1 is Node.ELEMENT_NODE
|
24
|
+
result = doc.createElement(n.nodeName);
|
25
|
+
break;
|
26
|
+
case 9: // nodeType 9 is Node.DOCUMENT_NODE
|
27
|
+
result = doc.implementation.createDocument();
|
28
|
+
break;
|
29
|
+
case 11: // nodeType 11 is Node.DOCUMENT_FRAGMENT_NODE
|
30
|
+
result = doc.createDocumentFragment();
|
31
|
+
break;
|
32
|
+
default:
|
33
|
+
result = n.cloneNode(true);
|
34
|
+
}
|
25
35
|
if (n.attributes) {
|
26
36
|
for (let att of Array.from(n.attributes)) {
|
27
37
|
if (att.name !== "data-processed") {
|
@@ -32,23 +42,20 @@ export function copyAndReset(node) {
|
|
32
42
|
for (let nd of Array.from(n.childNodes)){
|
33
43
|
// nodeType 1 is Node.ELEMENT_NODE
|
34
44
|
if (nd.nodeType == 1) {
|
35
|
-
if (
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
child.removeAttribute("data-origid");
|
43
|
-
}
|
45
|
+
if (nd.hasAttribute("data-original")) {
|
46
|
+
for (let childNode of Array.from(nd.childNodes)) {
|
47
|
+
let child = result.appendChild(clone(childNode));
|
48
|
+
// nodeType 1 is Node.ELEMENT_NODE
|
49
|
+
if (child.nodeType === 1 && child.hasAttribute("data-origid")) {
|
50
|
+
child.setAttribute("id", child.getAttribute("data-origid"));
|
51
|
+
child.removeAttribute("data-origid");
|
44
52
|
}
|
45
|
-
return result;
|
46
|
-
} else {
|
47
|
-
result.appendChild(clone(nd));
|
48
53
|
}
|
54
|
+
return result;
|
55
|
+
} else if (nd.hasAttribute("data-origname")) {
|
56
|
+
result.appendChild(clone(nd));
|
49
57
|
}
|
50
|
-
}
|
51
|
-
else {
|
58
|
+
} else {
|
52
59
|
result.appendChild(nd.cloneNode());
|
53
60
|
}
|
54
61
|
}
|
@@ -66,13 +73,12 @@ export function first(urls) {
|
|
66
73
|
}
|
67
74
|
|
68
75
|
/*
|
69
|
-
Wraps the content of the element parameter in a <
|
70
|
-
with display set to "none".
|
76
|
+
Wraps the content of the element parameter in a hidden <cetei-original data-original>
|
71
77
|
*/
|
72
78
|
export function hideContent(elt, rewriteIds = true) {
|
73
79
|
const doc = elt.ownerDocument;
|
74
80
|
if (elt.childNodes.length > 0) {
|
75
|
-
let hidden = doc.createElement("
|
81
|
+
let hidden = doc.createElement("cetei-original");
|
76
82
|
elt.appendChild(hidden);
|
77
83
|
hidden.setAttribute("hidden", "");
|
78
84
|
hidden.setAttribute("data-original", "");
|
@@ -139,12 +145,20 @@ export function getPrefixDef(prefix) {
|
|
139
145
|
*/
|
140
146
|
export function rw(url) {
|
141
147
|
if (!url.match(/^(?:http|mailto|file|\/|#).*$/)) {
|
142
|
-
return this.base +
|
148
|
+
return this.base + first(url);
|
143
149
|
} else {
|
144
150
|
return url;
|
145
151
|
}
|
146
152
|
}
|
147
153
|
|
154
|
+
/*
|
155
|
+
Combines the functionality of copyAndReset() and serialize() to return
|
156
|
+
a "clean" version of the XML markup.
|
157
|
+
*/
|
158
|
+
export function resetAndSerialize(el, stripElt, ws) {
|
159
|
+
return serialize(copyAndReset(el), stripElt, ws);
|
160
|
+
}
|
161
|
+
|
148
162
|
/*
|
149
163
|
Takes an element and serializes it to an XML string or, if the stripElt
|
150
164
|
parameter is set, serializes the element's content. The ws parameter, if
|
@@ -153,9 +167,12 @@ export function rw(url) {
|
|
153
167
|
*/
|
154
168
|
export function serialize(el, stripElt, ws) {
|
155
169
|
let str = "";
|
156
|
-
|
170
|
+
const ignorable = (txt) => {
|
157
171
|
return !(/[^\t\n\r ]/.test(txt));
|
158
172
|
}
|
173
|
+
if (el.nodeType === 9 || el.nodeType === 11) { // nodeType 9 is Node.DOCUMENT_NODE; nodeType 11 is Node.DOCUMENT_FRAGMENT_NODE
|
174
|
+
str += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
175
|
+
}
|
159
176
|
// nodeType 1 is Node.ELEMENT_NODE
|
160
177
|
if (!stripElt && el.nodeType == 1) {
|
161
178
|
if ((typeof ws === "string") && ws !== "") {
|
@@ -180,7 +197,6 @@ export function serialize(el, stripElt, ws) {
|
|
180
197
|
str += "/>";
|
181
198
|
}
|
182
199
|
}
|
183
|
-
//TODO: Be smarter about skipping generated content with hidden original
|
184
200
|
for (let node of Array.from(el.childNodes)) {
|
185
201
|
// nodeType 1 is Node.ELEMENT_NODE
|
186
202
|
// nodeType 7 is Node.PROCESSING_INSTRUCTION_NODE
|
@@ -194,10 +210,16 @@ export function serialize(el, stripElt, ws) {
|
|
194
210
|
}
|
195
211
|
break;
|
196
212
|
case 7:
|
197
|
-
str +=
|
213
|
+
str += `<?${node.nodeName} ${node.nodeValue}?>`;
|
214
|
+
if (el.nodeType === 9 || el.nodeType === 11) {
|
215
|
+
str += "\n";
|
216
|
+
}
|
198
217
|
break;
|
199
218
|
case 8:
|
200
|
-
str +=
|
219
|
+
str += `<!--${node.nodeValue}-->`;
|
220
|
+
if (el.nodeType === 9 || el.nodeType === 11) {
|
221
|
+
str += "\n";
|
222
|
+
}
|
201
223
|
break;
|
202
224
|
default:
|
203
225
|
if (stripElt && ignorable(node.nodeValue)) {
|
@@ -209,7 +231,7 @@ export function serialize(el, stripElt, ws) {
|
|
209
231
|
str += node.nodeValue;
|
210
232
|
}
|
211
233
|
}
|
212
|
-
if (!stripElt && el.childNodes.length > 0) {
|
234
|
+
if (!stripElt && el.nodeType == 1 && el.childNodes.length > 0) {
|
213
235
|
if (typeof ws === "string") {
|
214
236
|
str += "\n" + ws + "</";
|
215
237
|
} else {
|
@@ -217,6 +239,81 @@ export function serialize(el, stripElt, ws) {
|
|
217
239
|
}
|
218
240
|
str += el.getAttribute("data-origname") + ">";
|
219
241
|
}
|
242
|
+
if (el.nodeType === 9 || el.nodeType === 11) {
|
243
|
+
str += "\n";
|
244
|
+
}
|
245
|
+
return str;
|
246
|
+
}
|
247
|
+
|
248
|
+
/*
|
249
|
+
Write out the HTML markup to a string, using HTML conventions.
|
250
|
+
*/
|
251
|
+
export function serializeHTML(el, stripElt, ws) {
|
252
|
+
const EMPTY_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
|
253
|
+
let str = "";
|
254
|
+
const ignorable = (txt) => {
|
255
|
+
return !(/[^\t\n\r ]/.test(txt));
|
256
|
+
}
|
257
|
+
// nodeType 1 is Node.ELEMENT_NODE
|
258
|
+
if (!stripElt && el.nodeType == 1) {
|
259
|
+
if ((typeof ws === "string") && ws !== "") {
|
260
|
+
str += "\n" + ws + "<";
|
261
|
+
} else {
|
262
|
+
str += "<";
|
263
|
+
}
|
264
|
+
str += el.nodeName;
|
265
|
+
for (let attr of Array.from(el.attributes)) {
|
266
|
+
str += " " + attr.name + "=\"" + attr.value + "\"";
|
267
|
+
}
|
268
|
+
str += ">";
|
269
|
+
}
|
270
|
+
for (let node of Array.from(el.childNodes)) {
|
271
|
+
// nodeType 1 is Node.ELEMENT_NODE
|
272
|
+
// nodeType 7 is Node.PROCESSING_INSTRUCTION_NODE
|
273
|
+
// nodeType 8 is Node.COMMENT_NODE
|
274
|
+
switch (node.nodeType) {
|
275
|
+
case 1:
|
276
|
+
if (typeof ws === "string") {
|
277
|
+
str += serializeHTML(node, false, ws + " ");
|
278
|
+
} else {
|
279
|
+
str += serializeHTML(node, false, ws);
|
280
|
+
}
|
281
|
+
break;
|
282
|
+
case 7:
|
283
|
+
str += `<?${node.nodeName} ${node.nodeValue}?>`;
|
284
|
+
if (el.nodeType === 9 || el.nodeType === 11) {
|
285
|
+
str += "\n";
|
286
|
+
}
|
287
|
+
break;
|
288
|
+
case 8:
|
289
|
+
str += `<!--${node.nodeValue}-->`;
|
290
|
+
if (el.nodeType === 9 || el.nodeType === 11) {
|
291
|
+
str += "\n";
|
292
|
+
}
|
293
|
+
break;
|
294
|
+
default:
|
295
|
+
if (stripElt && ignorable(node.nodeValue)) {
|
296
|
+
str += node.nodeValue.replace(/^\s*\n/, "");
|
297
|
+
}
|
298
|
+
if ((typeof ws === "string") && ignorable(node.nodeValue)) {
|
299
|
+
break;
|
300
|
+
}
|
301
|
+
str += node.nodeValue;
|
302
|
+
}
|
303
|
+
}
|
304
|
+
if (!EMPTY_ELEMENTS.includes(el.nodeName)) {
|
305
|
+
if (!stripElt && el.nodeType == 1) {
|
306
|
+
if (typeof ws === "string") {
|
307
|
+
str += `\n${ws}</`;
|
308
|
+
} else {
|
309
|
+
str += "</";
|
310
|
+
}
|
311
|
+
str += `${el.nodeName}>`;
|
312
|
+
}
|
313
|
+
}
|
314
|
+
if (el.nodeType === 9 || el.nodeType === 11) {
|
315
|
+
str += "\n";
|
316
|
+
}
|
220
317
|
return str;
|
221
318
|
}
|
222
319
|
|
@@ -249,9 +346,9 @@ export function defineCustomElement(name, behavior = null, debug = false) {
|
|
249
346
|
if (!this.matches(":defined")) { // "Upgraded" undefined elements can have attributes & children; new elements can't
|
250
347
|
if (behavior) {
|
251
348
|
behavior.call(this);
|
349
|
+
// We don't want to double-process elements, so add a flag
|
350
|
+
this.setAttribute("data-processed", "");
|
252
351
|
}
|
253
|
-
// We don't want to double-process elements, so add a flag
|
254
|
-
this.setAttribute("data-processed", "");
|
255
352
|
}
|
256
353
|
}
|
257
354
|
// Process new elements when they are connected to the browser DOM
|
@@ -259,8 +356,8 @@ export function defineCustomElement(name, behavior = null, debug = false) {
|
|
259
356
|
if (!this.hasAttribute("data-processed")) {
|
260
357
|
if (behavior) {
|
261
358
|
behavior.call(this);
|
359
|
+
this.setAttribute("data-processed", "");
|
262
360
|
}
|
263
|
-
this.setAttribute("data-processed", "");
|
264
361
|
}
|
265
362
|
};
|
266
363
|
});
|
package/test/CETEIcean.css
CHANGED