Skip to content

Commit febe057

Browse files
committed
Add support for namespaces
…and additionally: * Rewrite module * 100% coverage
1 parent 8e3b90f commit febe057

File tree

7 files changed

+228
-61
lines changed

7 files changed

+228
-61
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ npm-debug.log
33
yarn-error.log
44
.DS_Store
55
/dist
6+
/coverage

package.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"dist/"
2626
],
2727
"dependencies": {
28-
"hastscript": "^5.0.0"
28+
"hastscript": "^5.0.0",
29+
"web-namespaces": "^1.0.0"
2930
},
3031
"devDependencies": {
3132
"@babel/core": "^7.0.0",
@@ -45,5 +46,19 @@
4546
"lint": "eslint .",
4647
"test": "jest",
4748
"test:dev": "jest --watchAll"
49+
},
50+
"jest": {
51+
"collectCoverage": true,
52+
"coveragePathIgnorePatterns": [
53+
"/src/utils.js"
54+
],
55+
"coverageThreshold": {
56+
"global": {
57+
"branches": 100,
58+
"functions": 100,
59+
"lines": 100,
60+
"statements": 100
61+
}
62+
}
4863
}
4964
}

src/__fixtures__/svg/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,15 @@
22
<svg width="230" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
33
<circle cx="60" cy="60" r="50" fill="red"/>
44
<circle cx="170" cy="60" r="50" fill="green"/>
5+
<switch fill="red">
6+
<text systemLanguage="en">en</text>
7+
<text>other</text>
8+
</switch>
9+
<foreignObject x="20" y="20" width="160" height="160">
10+
<!-- In the context of SVG embedded in an HTML document, the XHTML
11+
namespace could be omitted, but it is mandatory in the context of an SVG
12+
document -->
13+
<div width="100" xmlns="http://www.w3.org/1999/xhtml">Lorem ipsum.</div>
14+
</foreignObject>
515
</svg>
616
</div>

src/__fixtures__/svg/index.json

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
"type": "element",
3131
"tagName": "svg",
3232
"properties": {
33-
"width": 230,
34-
"height": 120,
33+
"width": "230",
34+
"height": "120",
3535
"xmlns": "http://www.w3.org/2000/svg",
3636
"xmlnsXLink": "http://www.w3.org/1999/xlink"
3737
},
@@ -66,6 +66,103 @@
6666
},
6767
"children": []
6868
},
69+
{
70+
"type": "text",
71+
"value": "\n "
72+
},
73+
{
74+
"type": "element",
75+
"tagName": "switch",
76+
"properties": {
77+
"fill": "red"
78+
},
79+
"children": [
80+
{
81+
"type": "text",
82+
"value": "\n "
83+
},
84+
{
85+
"type": "element",
86+
"tagName": "text",
87+
"properties": {
88+
"systemLanguage": [
89+
"en"
90+
]
91+
},
92+
"children": [
93+
{
94+
"type": "text",
95+
"value": "en"
96+
}
97+
]
98+
},
99+
{
100+
"type": "text",
101+
"value": "\n "
102+
},
103+
{
104+
"type": "element",
105+
"tagName": "text",
106+
"properties": {},
107+
"children": [
108+
{
109+
"type": "text",
110+
"value": "other"
111+
}
112+
]
113+
},
114+
{
115+
"type": "text",
116+
"value": "\n "
117+
}
118+
]
119+
},
120+
{
121+
"type": "text",
122+
"value": "\n "
123+
},
124+
{
125+
"type": "element",
126+
"tagName": "foreignObject",
127+
"properties": {
128+
"x": "20",
129+
"y": "20",
130+
"width": "160",
131+
"height": "160"
132+
},
133+
"children": [
134+
{
135+
"type": "text",
136+
"value": "\n "
137+
},
138+
{
139+
"type": "comment",
140+
"value": " In the context of SVG embedded in an HTML document, the XHTML\n namespace could be omitted, but it is mandatory in the context of an SVG\n document "
141+
},
142+
{
143+
"type": "text",
144+
"value": "\n "
145+
},
146+
{
147+
"type": "element",
148+
"tagName": "div",
149+
"properties": {
150+
"width": 100,
151+
"xmlns": "http://www.w3.org/1999/xhtml"
152+
},
153+
"children": [
154+
{
155+
"type": "text",
156+
"value": "Lorem ipsum."
157+
}
158+
]
159+
},
160+
{
161+
"type": "text",
162+
"value": "\n "
163+
}
164+
]
165+
},
69166
{
70167
"type": "text",
71168
"value": "\n "

src/index.js

Lines changed: 55 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import h from 'hastscript';
1+
import ns from 'web-namespaces';
2+
import h from 'hastscript/html';
3+
import s from 'hastscript/svg';
24

35
const ELEMENT_NODE = 1;
46
const TEXT_NODE = 3;
@@ -7,95 +9,90 @@ const DOCUMENT_NODE = 9;
79
const DOCUMENT_TYPE_NODE = 10;
810
const DOCUMENT_FRAGMENT_NODE = 11;
911

10-
function transform(el) {
11-
const children = [];
12-
const length = el.childNodes ? el.childNodes.length : 0;
13-
14-
for (let i = 0; i < length; i += 1) {
15-
children.push(transform(el.childNodes[i]));
16-
}
12+
function transform(value) {
13+
const node = value || {};
1714

18-
switch (el.nodeType) {
15+
switch (node.nodeType) {
16+
case ELEMENT_NODE:
17+
return element(node);
1918
case DOCUMENT_NODE:
2019
case DOCUMENT_FRAGMENT_NODE:
21-
return root(el, children);
20+
return root(node);
2221
case TEXT_NODE:
23-
return text(el, children);
22+
return text(node);
2423
case COMMENT_NODE:
25-
return comment(el, children);
24+
return comment(node);
2625
case DOCUMENT_TYPE_NODE:
27-
return doctype(el, children);
28-
case ELEMENT_NODE:
29-
return element(el, children);
30-
default:
31-
break;
32-
}
33-
34-
switch (el.nodeName) {
35-
case '#document':
36-
case '#document-fragment':
37-
return root(el, children);
38-
case '#text':
39-
return text(el, children);
40-
case '#comment':
41-
return comment(el, children);
42-
case 'html':
43-
case '#documentType':
44-
return doctype(el, children);
26+
return doctype(node);
4527
default:
46-
return element(el, children);
28+
return null;
4729
}
4830
}
4931

5032
// Transform a document.
51-
function root(el, children) {
52-
return { type: 'root', children };
33+
function root(node) {
34+
return { type: 'root', children: all(node) };
5335
}
5436

5537
// Transform a doctype.
56-
function doctype(el) {
38+
function doctype(node) {
5739
return {
5840
type: 'doctype',
59-
name: el.name || '',
60-
public: el.publicId || null,
61-
system: el.systemId || null,
41+
name: node.name || '',
42+
public: node.publicId || null,
43+
system: node.systemId || null,
6244
};
6345
}
6446

6547
// Transform text.
66-
function text(el) {
67-
return { type: 'text', value: el.nodeValue };
48+
function text(node) {
49+
return { type: 'text', value: node.nodeValue };
6850
}
6951

7052
// Transform a comment.
71-
function comment(el) {
72-
return { type: 'comment', value: el.data };
53+
function comment(node) {
54+
return { type: 'comment', value: node.nodeValue };
7355
}
7456

7557
// Transform an element.
76-
function element(el, children) {
77-
const tagName = el.tagName.toLowerCase();
58+
function element(node) {
59+
const space = node.namespaceURI;
60+
const fn = space === ns.svg ? s : h;
61+
const tagName = space === ns.html ? node.tagName.toLowerCase() : node.tagName;
62+
const content = space === ns.html && tagName === 'template' ? node.content : node;
63+
const attributes = node.getAttributeNames();
64+
const { length } = attributes;
7865
const props = {};
79-
const attrs = typeof el.getAttributeNames === 'function'
80-
? el.getAttributeNames()
81-
: [];
82-
const { length } = attrs;
83-
84-
for (let i = 0; i < length; i += 1) {
85-
const key = attrs[i];
86-
const value = el.getAttribute(key);
87-
props[key] = value;
66+
let index = 0;
67+
68+
while (index < length) {
69+
const key = attributes[index];
70+
props[key] = node.getAttribute(key);
71+
index += 1;
8872
}
8973

90-
const node = h(tagName, props, children);
74+
return fn(tagName, props, all(content));
75+
}
76+
77+
function all(node) {
78+
const nodes = node.childNodes;
79+
const { length } = nodes;
80+
const children = [];
81+
let index = 0;
82+
83+
while (index < length) {
84+
const child = transform(nodes[index]);
85+
86+
if (child !== null) {
87+
children.push(child);
88+
}
9189

92-
if (tagName === 'template' && 'content' in el) {
93-
node.content = transform(el.content);
90+
index += 1;
9491
}
9592

96-
return node;
93+
return children;
9794
}
9895

99-
export default function fromDOM(el) {
100-
return transform(el);
96+
export default function fromDOM(node) {
97+
return transform(node) || { type: 'root', children: [] };
10198
}

src/index.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,48 @@ describe('hast-util-from-dom', () => {
6565
],
6666
});
6767
});
68+
69+
it('should support an empty fragment', () => {
70+
const actual = fromDOM(document.createDocumentFragment());
71+
72+
expect(actual).toEqual({ type: 'root', children: [] });
73+
});
74+
75+
it('should support a comment', () => {
76+
const actual = fromDOM(document.createComment('alpha'));
77+
78+
expect(actual).toEqual({ type: 'comment', value: 'alpha' });
79+
});
80+
81+
it('should support a text', () => {
82+
const actual = fromDOM(document.createTextNode('bravo'));
83+
84+
expect(actual).toEqual({ type: 'text', value: 'bravo' });
85+
});
86+
87+
it('should handle CDATA', () => {
88+
const xmlDoc = new DOMParser().parseFromString('<xml></xml>', 'application/xml');
89+
const actual = fromDOM(xmlDoc.createCDATASection('charlie'));
90+
91+
expect(actual).toEqual({ type: 'root', children: [] });
92+
});
93+
94+
it('should handle CDATA in HTML', () => {
95+
const xmlDoc = new DOMParser().parseFromString('<xml></xml>', 'application/xml');
96+
const frag = document.createDocumentFragment();
97+
98+
frag.appendChild(xmlDoc.createCDATASection('charlie'));
99+
100+
const actual = fromDOM(frag);
101+
102+
expect(actual).toEqual({ type: 'root', children: [] });
103+
});
104+
105+
it('should handle a missing DOM tree', () => {
106+
const actual = fromDOM();
107+
108+
expect(actual).toEqual({ type: 'root', children: [] });
109+
});
68110
});
69111

70112
describe('fixtures', () => {

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4456,6 +4456,11 @@ walker@^1.0.7, walker@~1.0.5:
44564456
dependencies:
44574457
makeerror "1.0.x"
44584458

4459+
web-namespaces@^1.0.0:
4460+
version "1.1.3"
4461+
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.3.tgz#9bbf5c99ff0908d2da031f1d732492a96571a83f"
4462+
integrity sha512-r8sAtNmgR0WKOKOxzuSgk09JsHlpKlB+uHi937qypOu3PZ17UxPrierFKDye/uNHjNTTEshu5PId8rojIPj/tA==
4463+
44594464
webidl-conversions@^4.0.2:
44604465
version "4.0.2"
44614466
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"

0 commit comments

Comments
 (0)