mark-js
Version:
Mark, an unified notation for all
171 lines (151 loc) • 5.3 kB
JavaScript
const CSSselect = require("./css-select/index.js");
function isTag(node) { return typeof node === 'object' && node.constructor; } // node.constructor.name !== 'Object' // JSON is included
function getParent(node) { return node.parent(); }
function getChildren(node) {
return node[0] ? node.contents():[];
}
// DOM adaptor for mark object hierarchy
const adapter = {
// is the node a tag?
// isTag: (node:Node) => isTag:Boolean
isTag: isTag,
// does at least one of passed element nodes pass the test predicate?
// existsOne: (test:Predicate, elems:[ElementNode]) => existsOne:Boolean
existsOne: function(test, elems) {
return elems.some(function(elem){
return isTag(elem) ?
test(elem) || adapter.existsOne(test, getChildren(elem)) :
false;
});
},
// get the attribute value
// getAttributeValue: ( elem:ElementNode, name:String ) => value:String
getAttributeValue: function(elem, name) { return elem[name]; },
// get the node's children
// getChildren: (node:Node) => children:[Node]
getChildren: getChildren,
// get the name of the tag
// getName: (elem:ElementNode) => tagName:String,
getName: function(elem) { return elem.constructor.name; },
// get the parent of the node
// getParent: (node:Node) => parentNode:Node,
getParent: getParent,
// get the siblings of the node. Note that unlike jQuery's `siblings` method, this is expected to include the current node as well
// getSiblings: (node:Node) => siblings:[Node],
getSiblings: function(node) {
var parent = getParent(node);
return parent && getChildren(parent);
},
// get the text content of the node, and its children if it has any
// getText: (node:Node) => text:String,
/*
getText: function(node) {
if (typeof node === 'string') { return node; }
else if (node.constructor.name === 'Object') { return ''; } // JSON of Mark pragma
// element
var texts = [];
for (let c of node) { texts.push(getText(c)); }
return texts.join("");
},
*/
// does the element have the named attribute?
// hasAttrib: (elem:ElementNode, name:String) => hasAttrib:Boolean,
hasAttrib: function(elem, name) { return name in elem; },
// takes an array of nodes, and removes any duplicates, as well as any nodes whose ancestors are also in the array
// removeSubsets: (nodes:[Node]) => unique:[Node],
/*
removeSubsets: function(nodes) {
var idx = nodes.length, node, ancestor, replace;
// check if each node (or one of its ancestors) is already contained in the array.
while (--idx > -1) {
node = ancestor = nodes[idx];
// Temporarily remove the node under consideration
nodes[idx] = null;
replace = true;
while (ancestor) {
if (nodes.indexOf(ancestor) > -1) {
replace = false;
nodes.splice(idx, 1);
break;
}
ancestor = getParent(ancestor)
}
// If the node has been found to be unique, re-insert it.
if (replace) {
nodes[idx] = node;
}
}
return nodes;
},
*/
// finds all of the element nodes in the array that match the test predicate,
// as well as any of their children that match it
// findAll: (test:Predicate, nodes:[Node]) => elems:[ElementNode],
findAll: function findAll(test, elems) {
var result = [];
for (var elem of elems) {
if (!isTag(elem)) continue;
if (test(elem)) result.push(elem);
var childs = getChildren(elem);
if (childs) result = result.concat(findAll(test, childs));
}
return result;
},
// finds the first node in the array that matches the test predicate, or one
// of its children
// findOne: (test:Predicate, elems:[ElementNode]) => findOne:ElementNode,
/*
findOne: function findOne(test, arr) {
var elem = null;
for (var i = 0, l = arr.length; i < l && !elem; i++) {
if (test(arr[i])) {
elem = arr[i];
} else {
var childs = getChildren(arr[i]);
if (childs && childs.length > 0) {
elem = findOne(test, childs);
}
}
}
return elem;
},
*/
/*
The adapter can also optionally include an equals method, if your DOM
structure needs a custom equality test to compare two objects which refer
to the same underlying node. If not provided, `css-select` will fall back to
`a === b`.
*/
// equals: (a:Node, b:Node) => Boolean
}
let MarkSelector = function(Mark) {
let api = {
// find() is similar to jQuery find(), and diff from Array.prototype.find()
find: function(selector, options) {
// matches on children of elmt, not elmt itself
try {
return CSSselect(selector, this, {xmlMode:(options && options.caseSensitive), adapter:adapter});
}
catch (error) {
console.error(error);
return [];
}
},
matches: function(selector, options) {
// tests whether or not an element is matched by query
try {
return CSSselect.is(this, selector, {xmlMode:(options && options.caseSensitive), adapter:adapter});
}
catch (error) {
console.error(error);
return false;
}
}
}
// set the APIs
for (let a in api) {
// API functions are set to non-enumerable
Object.defineProperty(Mark.prototype, a, {value:api[a], writable:true, configurable:true});
}
};
module.exports = MarkSelector;