Stable initial build of module replacement.

master
Sir Robert Burbridge 2023-04-28 17:54:20 -04:00
parent 3f1618e0c5
commit 0ad6124c29
9 changed files with 253 additions and 32 deletions

49
package-lock.json generated
View File

@ -12,7 +12,9 @@
"hast-util-from-html": "^1.0.1",
"hast-util-to-html": "^8.0.4",
"hastscript": "^7.2.0",
"unist-util-visit": "^4.1.2"
"unist-util-visit": "^4.1.2",
"xast-util-from-xml": "^3.0.0",
"xast-util-to-xml": "^3.0.2"
},
"devDependencies": {
"@babel/preset-env": "^7.16.11",
@ -2671,6 +2673,14 @@
"node": ">= 8"
}
},
"node_modules/@rgrove/parse-xml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@rgrove/parse-xml/-/parse-xml-4.1.0.tgz",
"integrity": "sha512-pBiltENdy8SfI0AeR1e5TRpS9/9Gl0eiOEt6ful2jQfzsgvZYWqsKiBWaOCLdocQuk0wS7KOHI37n0C1pnKqTw==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.25.24",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
@ -2859,6 +2869,14 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
},
"node_modules/@types/xast": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/xast/-/xast-1.0.2.tgz",
"integrity": "sha512-eIuX49CmWaXTh0hDEQTFH5MbTV+NPm0FM5gB36nDcr9x8n75t9o7wy2guS1lakkQxtUwfVDYiokqvHk3x0CC4Q==",
"dependencies": {
"@types/unist": "*"
}
},
"node_modules/@types/yargs": {
"version": "17.0.24",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
@ -9100,6 +9118,35 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/xast-util-from-xml": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xast-util-from-xml/-/xast-util-from-xml-3.0.0.tgz",
"integrity": "sha512-zEfGxnue7vHbayZnRBAjJsjV1dQG6XWKLAzrsPxX2l4WHmqGZxM76XxRblaTHvcOYMFLlnVp3DFcUAl+Qk9SNQ==",
"dependencies": {
"@rgrove/parse-xml": "^4.1.0",
"@types/xast": "^1.0.0",
"vfile-location": "^4.0.0",
"vfile-message": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/xast-util-to-xml": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/xast-util-to-xml/-/xast-util-to-xml-3.0.2.tgz",
"integrity": "sha512-lbnJEVui7Lk9W/TydInkin5OnOyTAr2b4OpK5YjXo94qqoXaR6mFE/evXfOrBev9EnD91r0FOPxrLfTT0qLITQ==",
"dependencies": {
"@types/xast": "^1.0.0",
"ccount": "^2.0.0",
"stringify-entities": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/xmlbuilder": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.0.0.tgz",

View File

@ -46,6 +46,8 @@
"hast-util-from-html": "^1.0.1",
"hast-util-to-html": "^8.0.4",
"hastscript": "^7.2.0",
"unist-util-visit": "^4.1.2"
"unist-util-visit": "^4.1.2",
"xast-util-from-xml": "^3.0.0",
"xast-util-to-xml": "^3.0.2"
}
}

View File

@ -0,0 +1,50 @@
import {visit} from 'unist-util-visit';
/**
* applyModules({ast,modules})
*
* Given an abstract syntax tree and a catalog of modules, apply the modules
* to the AST.
*
* @param {object} An object containing an abstract syntax tree (ast) and a
* catalog of modules (modules).
* @returns {ast, modules}
*/
export default async function applyModules ({ast, modules}) {
const result = {};
visit(
ast,
// A filter. True means visit this node, false means don't. Optional
// param.
node => {
if (!node?.children?.length) {
return false;
}
// Loop through this node's children. If any of them have a name that
// matches a defined module, we want to use this node (so we can
// replace the child with the component's content).
for (var i = 0; i < node.children.length; i++) {
if (Object.keys(modules).includes(node.children[i].name)) {
return true;
}
}
return false;
},
// This is the what-to-do-with-the-nodes function.
node => {
// Loop through each child. If the child's name matches a component's
// registered name, replace the child with the component's contents.
for (let c = 0; c < node.children.length; c++) {
if (Object.keys(modules).includes(node.children[c].name)) {
node.children.splice(c,1,...modules[node.children[c].name].children);
}
}
}
);
return {ast, modules};
}

View File

@ -0,0 +1,47 @@
import {visit} from 'unist-util-visit';
/**
* extractModules()
*
* Given an abstract syntax tree, remove loom modules. Return the remaining
* AST and a catalog of modules.
*
* @param {ast} ast An abstract syntax tree from XML.
* @returns { modules, ast }
*/
export default async function extractModules (ast) {
const modules = {};
visit(
ast,
node => {
if (!node?.children?.length) {
return false;
}
for (var i = 0; i < node.children.length; i++) {
if (node.children[i].name === 'loom-module') {
return true;
}
}
},
node => {
node.children = node.children.reduce(
(list,item) => {
if (item.name === 'loom-module') {
if (item.attributes?.define) {
modules[item.attributes?.define] = item;
}
} else {
list.push(item);
}
return list;
},
[]
);
}
);
return {modules, ast};
}

21
src/file-to-ast.js 100644
View File

@ -0,0 +1,21 @@
import fs from 'node:fs/promises';
import {fromXml} from 'xast-util-from-xml';
/**
* fileToAst(fn)
*
* Given a filename, parse it as XML to an abstract syntax tree.
*
* @param {string} The path to an XML file.
* @returns {ast} An abstract syntax tree of the XHTML file.
*
*
*
*/
export default async function fileToAst (fn) {
const html = await fs.readFile(fn);
const tree = fromXml(
`<LOOM-FRAGMENT>${html}</LOOM-FRAGMENT>`,
{depth: null}
);
return tree;
}

13
test.js 100644
View File

@ -0,0 +1,13 @@
import extractModules from './src/extract-modules.js';
import applyModules from './src/apply-modules.js';
import fileToAst from './src/file-to-ast.js';
import {toXml} from 'xast-util-to-xml';
fileToAst('./tmp/demo.html')
.then(extractModules)
.then(applyModules)
.then(({ast,modules}) => {
console.log(toXml(ast.children[0].children));
});

View File

@ -1,5 +1,6 @@
<loom-module define="Component">
<span>component</span>
foo
</loom-module>
<div>
@ -7,3 +8,9 @@
<span class="foo" id="bar">testing</span>
<Component/>
</div>
<div>
<h1>Title</h1>
<span class="foo" id="bar">testing</span>
<Component/>
</div>

View File

@ -1,6 +1,7 @@
import * as fs from 'fs/promises'
import { fromHtml } from 'hast-util-from-html'
import { toHtml } from 'hast-util-to-html'
import {fromXml} from 'xast-util-from-xml'
import { toXml } from 'xast-util-to-xml'
import { visit } from 'unist-util-visit'
@ -10,12 +11,12 @@ async function readHtml (fn) {
async function fileToAst () {
const html = await readHtml('./demo.html');
const tree = fromHtml(html, {fragment: true})
const tree = fromXml(`<LOOM-FRAGMENT>${html}</LOOM-FRAGMENT>`, {depth: null})
return tree;
}
async function extractModules (ast) {
const modules = [];
const modules = {};
visit(
ast,
@ -25,7 +26,7 @@ async function extractModules (ast) {
}
for (var i = 0; i < node.children.length; i++) {
if (node.children[i].tagName === 'loom-module') {
if (node.children[i].name === 'loom-module') {
return true;
}
}
@ -33,9 +34,10 @@ async function extractModules (ast) {
node => {
node.children = node.children.reduce(
(list,item) => {
if (item.tagName === 'loom-module') {
console.log("foo")
modules.push(item);
if (item.name === 'loom-module') {
if (item.attributes?.define) {
modules[item.attributes?.define] = item;
}
} else {
list.push(item);
}
@ -49,15 +51,50 @@ async function extractModules (ast) {
return {modules, ast};
}
async function applyModules ({ast, modules}) {
const result = {};
visit(
ast,
// A filter. True means visit this node, false means don't. Optional
// param.
node => {
if (!node?.children?.length) {
return false;
}
// Loop through this node's children. If any of them have a name that
// matches a defined module, we want to use this node (so we can
// replace the child with the component's content).
for (var i = 0; i < node.children.length; i++) {
if (Object.keys(modules).includes(node.children[i].name)) {
return true;
}
}
return false;
},
// This is the what-to-do-with-the-nodes function.
node => {
// Loop through each child. If the child's name matches a component's
// registered name, replace the child with the component's contents.
for (let c = 0; c < node.children.length; c++) {
if (Object.keys(modules).includes(node.children[c].name)) {
node.children.splice(c,1,...modules[node.children[c].name].children);
}
}
}
);
return {ast, modules};
}
fileToAst()
.then(ast => {
return ast;
})
.then(extractModules)
.then(applyModules)
.then(({ast,modules}) => {
console.log(JSON.stringify(ast,undefined,2));
console.log(toHtml(ast))
console.log(modules);
console.log(toXml(ast.children[0].children))
})

View File

@ -1,19 +1,16 @@
import {h, s} from 'hastscript'
console.log(
h('.foo#some-id', [
h('span', 'some text'),
h('input', {type: 'text', value: 'foo'}),
h('a.alpha', {class: 'bravo charlie', download: 'download'}, [
'delta',
'echo'
])
])
)
import {fromXml} from 'xast-util-from-xml';
const xml = `
<album id="123">
<name>Born in the U.S.A.</name>
<artist>Bruce Springsteen</artist>
<releasedate>1984-04-06</releasedate>
</album>
`
const tree = fromXml(xml);
console.dir(tree, {depth:null});
console.log(
s('svg', {xmlns: 'http://www.w3.org/2000/svg', viewbox: '0 0 500 500'}, [
s('title', 'SVG `<circle>` element'),
s('circle', {cx: 120, cy: 120, r: 100})
])
)