@binary-constructions/semantic-map
Version:
A markup wrapper for the Leaflet map API
101 lines (85 loc) • 3.12 kB
JavaScript
const ELEMENT_PREFIX="semantic-map";
const ATTRIBUTE_PREFIX="data-semantic-map-";
/**
* @typedef ASTNode
* @type {object}
* @property {Node} node The DOM node.
* @property {string[]} classes The semantic-map classes of `node`.
* @property {object} attributes A dictionary object with the
* semantic-map attributes of `node`. The dictionary is indexed by
* the attribute names with "data-semantic-map-" stripped.
*/
/**
* @typedef AST
* @type {object}
* @property {ASTNode|undefined} self The current node.
* @property {ASTNode[]|undefined} subs The list of sub nodes.
*/
/**
* Turn `node` and its sub-nodes into an abstract syntax tree that
* only contains information relevant for semantic-map.
* @param {Node} node The DOM node for which to create the AST.
* @returns {ASTNode} The `ASTNode` for `node`.
*
* This function works recursively. While sub-nodes are only included
* in the returned AST if they have either semantic-map specific
* classes or attributes (or both), the topmost node always refers to
* the argument `node` itself and may have its `self` property be
* `undefined`.
*/
function createAstInner(node, filter) {
if (!filter(node)) {
return undefined;
}
const classes = node.classList
? Array.from(node.classList).filter(c => c.startsWith(ELEMENT_PREFIX))
: [];
const attributes = node.attributes
? Array.from(node.attributes).filter(a => a.name.startsWith(ATTRIBUTE_PREFIX)).reduce(
(as, a) => {
let value = node.getAttribute(a.name);
if (value !== undefined) {
value = value.trim();
if (value !== "") {
as[a.name.slice(18)] = value;
} else {
console.warn("ignoring empty attribute '" + a.name + "'");
}
}
return as;
}, {})
: {};
const subs = [];
let sub_node = node.firstChild;
while (sub_node) {
const sub = createAstInner(sub_node, filter);
if (sub) {
if (sub.self) {
subs.push(sub);
} else if (sub.subs) {
subs.push(...sub.subs);
}
}
sub_node = sub_node.nextSibling;
}
return {
self: (classes.length || Object.keys(attributes).length) ? { node, classes, attributes } : undefined,
subs: subs.length ? subs : undefined
};
}
/**
* Turn `node` and its sub-nodes into a list of abstract
* syntax trees that only contains information relevant for
* semantic-map.
* @param {Node} node The DOM node for which to create the AST.
* @param {} filter TODO: document
* @returns {ASTNode[]|undefined} A list of all the `ASTNode`s
* found for `node`, or `undefined` if none.
*
* Unlike `createAstInner()` the ASTs returned by this function only
* contain nodes with `self` defined.
*/
export function createAst(node, filter) {
const ast = createAstInner(node, filter);
return ast.self ? [ ast ] : ast.subs;
}