Simplified JSX parser - initial commit
commit
578ecb9526
|
@ -0,0 +1,149 @@
|
||||||
|
const htmlspecialchars_table = {
|
||||||
|
nbsp: "\xA0",
|
||||||
|
quot: '"',
|
||||||
|
apos: "'",
|
||||||
|
lt: "<",
|
||||||
|
gt: ">",
|
||||||
|
amp: "&"
|
||||||
|
};
|
||||||
|
|
||||||
|
function htmlspecialchars_decode(s)
|
||||||
|
{
|
||||||
|
// Only a limited set of named entries is supported by design,
|
||||||
|
// because supporting all of them would require a large translation table
|
||||||
|
if (s == null)
|
||||||
|
{
|
||||||
|
// or undefined, because null == undefined
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return s.replace(
|
||||||
|
/&(nbsp|quot|apos|lt|gt|amp|#x[0-9a-f]+|#[0-9]+);/gi,
|
||||||
|
(m, m1) =>
|
||||||
|
{
|
||||||
|
if (m1[0] == "#")
|
||||||
|
{
|
||||||
|
return String.fromCharCode(
|
||||||
|
m1[1] == "x"
|
||||||
|
? parseInt(m1.substr(2), 16)
|
||||||
|
: parseInt(m1.substr(1), 10)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return htmlspecialchars_table[m1];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(content, components, createElement)
|
||||||
|
{
|
||||||
|
content = content.replace(/>\s+</g, "><");
|
||||||
|
const tag_re = /<(\/?)([a-z0-9\-]+)(([^>'"]+|"[^"]*"|'[^']*')*)>/i;
|
||||||
|
const attr_re = /^\s*([^\s="']+)(?:=([^"'\s]+|"[^"]*"|'[^']*'))?/i;
|
||||||
|
let m;
|
||||||
|
let r = [];
|
||||||
|
let stack = [r];
|
||||||
|
while ((m = tag_re.exec(content)))
|
||||||
|
{
|
||||||
|
let text = content.substr(0, m.index);
|
||||||
|
let close = m[1];
|
||||||
|
let tag = m[2];
|
||||||
|
let attrs = m[3];
|
||||||
|
content = content.substr(m.index + m[0].length);
|
||||||
|
text = text.replace(/^\s+/, "").replace(/\s+$/, "");
|
||||||
|
if (text !== "")
|
||||||
|
{
|
||||||
|
r.push(htmlspecialchars_decode(text));
|
||||||
|
}
|
||||||
|
if (close && stack.length > 1)
|
||||||
|
{
|
||||||
|
stack.pop();
|
||||||
|
r = stack[stack.length - 1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
attrs = attrs.replace(/\s+$/, "");
|
||||||
|
if (attrs[attrs.length - 1] == "/")
|
||||||
|
{
|
||||||
|
close = true;
|
||||||
|
attrs = attrs.substr(0, attrs.length - 1).replace(/\s+$/, "");
|
||||||
|
}
|
||||||
|
let attrhash = {};
|
||||||
|
while ((m = attr_re.exec(attrs)))
|
||||||
|
{
|
||||||
|
let key = m[1].toLowerCase();
|
||||||
|
let value = m[2];
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
// remember that null == undefined, so this checks for undefined too
|
||||||
|
if (value[0] == '"' || value[0] == "'")
|
||||||
|
{
|
||||||
|
value = value.substr(1, value.length - 2);
|
||||||
|
}
|
||||||
|
value = htmlspecialchars_decode(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = "true";
|
||||||
|
}
|
||||||
|
if (key === "href")
|
||||||
|
{
|
||||||
|
// 0x0A-0B-0C are ignored in schema value in IE
|
||||||
|
value = value.replace(/[\x0a\x0b\x0c]+/g, "");
|
||||||
|
}
|
||||||
|
if (key.substr(0, 2) !== "on" &&
|
||||||
|
(key !== "href" || !/^javascript:/.exec(value)))
|
||||||
|
{
|
||||||
|
if (key === "class")
|
||||||
|
{
|
||||||
|
// Convert to className for React
|
||||||
|
key = "className";
|
||||||
|
}
|
||||||
|
else if (key === "style")
|
||||||
|
{
|
||||||
|
// Convert to an object for React
|
||||||
|
let obj = {};
|
||||||
|
value = value.replace(/^\s+/, "").replace(/\s+$/, "");
|
||||||
|
for (let part of value.split(/\s*;\s*/))
|
||||||
|
{
|
||||||
|
let pos = part.indexOf(":");
|
||||||
|
if (pos >= 0)
|
||||||
|
{
|
||||||
|
let part_key = part
|
||||||
|
.substr(0, pos)
|
||||||
|
.replace(/\s+$/, "")
|
||||||
|
.replace(/-([a-z])/g, (m, m1) =>
|
||||||
|
m1.toUpperCase()
|
||||||
|
);
|
||||||
|
let part_value = part
|
||||||
|
.substr(pos + 1)
|
||||||
|
.replace(/^\s+/, "");
|
||||||
|
obj[part_key] = part_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = obj;
|
||||||
|
}
|
||||||
|
attrhash[key] = value;
|
||||||
|
}
|
||||||
|
attrs = attrs.substr(m[0].length);
|
||||||
|
}
|
||||||
|
if (!close)
|
||||||
|
{
|
||||||
|
attrhash["children"] = [];
|
||||||
|
}
|
||||||
|
attrhash["key"] = r.length;
|
||||||
|
r.push(createElement(components[tag] || tag, attrhash));
|
||||||
|
if (!close)
|
||||||
|
{
|
||||||
|
stack.push(attrhash["children"]);
|
||||||
|
r = attrhash["children"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content = content.replace(/^\s+/, "").replace(/\s+$/, "");
|
||||||
|
if (content !== "")
|
||||||
|
{
|
||||||
|
r.push(htmlspecialchars_decode(content));
|
||||||
|
}
|
||||||
|
return stack[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { htmlspecialchars_decode, parse };
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "jsx-parser",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Simplified JSX parser",
|
||||||
|
"main": "jsxParser.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"jsx",
|
||||||
|
"parser"
|
||||||
|
],
|
||||||
|
"author": "Vitaliy Filippov",
|
||||||
|
"license": "LGPL-3.0"
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"tag": "div",
|
||||||
|
"attrs": {
|
||||||
|
"className": "homeGrid",
|
||||||
|
"children": [
|
||||||
|
"<text>",
|
||||||
|
{
|
||||||
|
"tag": "div",
|
||||||
|
"attrs": {
|
||||||
|
"className": "homeTop",
|
||||||
|
"children": [],
|
||||||
|
"key": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "div",
|
||||||
|
"attrs": {
|
||||||
|
"style": {
|
||||||
|
"background": "black",
|
||||||
|
"textAlign": "right",
|
||||||
|
"color": "white",
|
||||||
|
"height": "20px",
|
||||||
|
"fontSize": "15px",
|
||||||
|
"width": "100px"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"tag": {
|
||||||
|
"x": "examplecomponent"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"title": "Hello world",
|
||||||
|
"skus": " 55-12940,102-109012",
|
||||||
|
"loc": "home",
|
||||||
|
"linktext": "I AM HERE",
|
||||||
|
"linkurl": "/hello",
|
||||||
|
"children": [],
|
||||||
|
"key": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "div",
|
||||||
|
"attrs": {
|
||||||
|
"className": "homeMiddle",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"tag": "home",
|
||||||
|
"attrs": {
|
||||||
|
"children": [],
|
||||||
|
"key": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "div",
|
||||||
|
"attrs": {
|
||||||
|
"className": "homeBottom",
|
||||||
|
"children": [],
|
||||||
|
"key": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "div",
|
||||||
|
"attrs": {
|
||||||
|
"className": "homeBottom",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"tag": "examplewithchildren",
|
||||||
|
"attrs": {
|
||||||
|
"logo": "/logo.svg",
|
||||||
|
"title": "Please wait",
|
||||||
|
"linktext": "READ MORE",
|
||||||
|
"linkurl": "https://google.com/",
|
||||||
|
"moretext": "More",
|
||||||
|
"content1": "Let's go",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"tag": "enclosed",
|
||||||
|
"attrs": {
|
||||||
|
"a": "b",
|
||||||
|
"tagged": "true",
|
||||||
|
"key": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,42 @@
|
||||||
|
const { parse } = require('./jsxParser.js');
|
||||||
|
const result1 = require('./result1.json');
|
||||||
|
|
||||||
|
const cmps = {
|
||||||
|
ExampleComponent: { x: 'examplecomponent' },
|
||||||
|
home: 'home',
|
||||||
|
ExampleWithChildren: 'examplewithchildren',
|
||||||
|
enclosed: 'enclosed',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example includes:
|
||||||
|
// - nested components
|
||||||
|
// - HTML entities
|
||||||
|
// - inline styles
|
||||||
|
// - classNames
|
||||||
|
// - attribute without a value
|
||||||
|
const str = `
|
||||||
|
<div class="homeGrid">
|
||||||
|
<text>
|
||||||
|
<div class="homeTop"></div>
|
||||||
|
<div style="background: black; text-align: right; color: white; height: 20px; font-size: 15px; width: 100px">
|
||||||
|
<ExampleComponent title="Hello world" skus=" 55-12940,102-109012"
|
||||||
|
loc="home" linktext="I AM HERE" linkurl="/hello"></ExampleComponent>
|
||||||
|
</div>
|
||||||
|
<div class="homeMiddle"><home ></home></div>
|
||||||
|
<div class="homeBottom"> </div>
|
||||||
|
<div class="homeBottom">
|
||||||
|
<ExampleWithChildren logo="/logo.svg" title="Please wait"
|
||||||
|
linktext="READ MORE" linkurl="https://google.com/" moretext="More"
|
||||||
|
content1="Let's go">
|
||||||
|
<enclosed a="b" tagged />
|
||||||
|
</ExampleWithChildren>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const result = parse(str, cmps, (tag, attrs) => ({ tag, attrs }));
|
||||||
|
if (JSON.stringify(result) != JSON.stringify(result1))
|
||||||
|
{
|
||||||
|
process.stderr.write('Test failed, got:\n');
|
||||||
|
process.stderr.write(JSON.stringify(result, null, 2));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
Loading…
Reference in New Issue