Fast CSS Selectors API Engine that serves as a cross-browser replacement for native CSS selection and matching functionality
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Plugin system for extending NWSAPI with custom selectors, attribute operators, and combinators. The extension framework allows developers to add new CSS selector capabilities and modify parsing behavior.
Registers a new pseudo-class or pseudo-element selector with its matching pattern and resolver function.
/**
* Registers new selector pattern and resolver
* @param name - Unique name for the selector
* @param rexp - Regular expression pattern to match selector syntax
* @param func - Resolver function that generates matching logic
*/
function registerSelector(name, rexp, func);Usage Examples:
const nwsapi = require("nwsapi");
// Register custom :control pseudo-class
nwsapi.registerSelector('Controls', /^\:(control)(.*)/i,
(function(global) {
return function(match, source, mode, callback) {
var status = true;
source = 'if(/^(button|input|select|textarea)/i.test(e.nodeName)){' + source + '}';
return { 'source': source, 'status': status };
};
})(this)
);
// Use the custom selector
const controls = nwsapi.select(':control');
// Register :contains() pseudo-class
nwsapi.registerSelector('Contains', /^\:contains\(\s*(.+)\s*\)/i,
function(match, source, mode, callback) {
const text = match[1].replace(/['"]/g, '');
const condition = `e.textContent.indexOf('${text}') !== -1`;
source = `if(${condition}){${source}}`;
return { source: source, status: true };
}
);
// Use :contains() selector
const elements = nwsapi.select('div:contains("Hello World")');Registers a new attribute operator with its symbol and resolver configuration.
/**
* Registers new attribute operator
* @param operator - Operator symbol (e.g., '!=', '*=')
* @param resolver - Resolver configuration object
*/
function registerOperator(operator, resolver);Usage Examples:
const nwsapi = require("nwsapi");
// Register not-equal operator
nwsapi.registerOperator('!=', {
p1: '^',
p2: '$',
p3: 'false'
});
// Use the custom operator
const elements = nwsapi.select('[class!="hidden"]');
// Register case-insensitive contains operator
nwsapi.registerOperator('*=i', {
p1: 'i',
p2: 'i',
p3: 'true'
});
// Use case-insensitive matching
const insensitive = nwsapi.select('[data-name*=i"john"]');Registers a new combinator symbol with its resolver logic for connecting selector parts.
/**
* Registers new combinator symbol and resolver
* @param combinator - Combinator symbol (e.g., '^', '||')
* @param resolver - JavaScript code for combinator logic
*/
function registerCombinator(combinator, resolver);Usage Examples:
const nwsapi = require("nwsapi");
// Register parent combinator (^)
nwsapi.registerCombinator('^', 'e.parentElement');
// Use parent combinator: find divs whose parent is a section
const elements = nwsapi.select('section ^ div');
// Register shadow DOM combinator
nwsapi.registerCombinator('>>>', 'e.shadowRoot && e.shadowRoot.querySelector("*")');
// Find elements inside shadow DOM
const shadowElements = nwsapi.select('div >>> span');const nwsapi = require("nwsapi");
// Register :nth-match() pseudo-class
nwsapi.registerSelector('NthMatch', /^\:nth-match\(\s*(\d+)\s*,\s*(.+)\s*\)/i,
function(match, source, mode, callback) {
const index = parseInt(match[1]) - 1; // Convert to 0-based
const selector = match[2];
source = `
var matchingElements = NW.Dom.select('${selector}', e.parentElement);
if (matchingElements[${index}] === e) {
${source}
}
`;
return { source: source, status: true };
}
);
// Use :nth-match() to find 3rd paragraph in each container
const thirdParagraphs = nwsapi.select('.container p:nth-match(3, p)');const nwsapi = require("nwsapi");
// Register numeric comparison operators
const numericOperators = {
'>': 'parseFloat(a) > parseFloat(v)',
'<': 'parseFloat(a) < parseFloat(v)',
'>=': 'parseFloat(a) >= parseFloat(v)',
'<=': 'parseFloat(a) <= parseFloat(v)'
};
Object.keys(numericOperators).forEach(op => {
nwsapi.registerOperator(op, {
p1: `(function(a,v){return ${numericOperators[op]}})`,
p2: '',
p3: 'true'
});
});
// Use numeric operators
const highPriority = nwsapi.select('[data-priority>="5"]');
const lowValues = nwsapi.select('[data-value<"100"]');const nwsapi = require("nwsapi");
// Register column combinator for tables
nwsapi.registerCombinator('||', `
(function(element) {
var table = element.closest('table');
if (!table) return null;
var cellIndex = Array.from(element.parentElement.children).indexOf(element);
var rows = table.querySelectorAll('tr');
var column = [];
for (var i = 0; i < rows.length; i++) {
if (rows[i].children[cellIndex]) {
column.push(rows[i].children[cellIndex]);
}
}
return column;
})(e)
`);
// Find all cells in the same column
const columnCells = nwsapi.select('th || td');When nwsapi-jquery.js is loaded, additional jQuery-compatible selectors become available:
// These become available after loading nwsapi-jquery.js
// Position-based selectors
const odd = nwsapi.select('tr:odd'); // Odd-indexed rows
const even = nwsapi.select('tr:even'); // Even-indexed rows
const first = nwsapi.select('p:first'); // First paragraph
const last = nwsapi.select('p:last'); // Last paragraph
// Index-based selectors
const third = nwsapi.select('li:eq(2)'); // Third list item (0-based)
const after = nwsapi.select('li:gt(2)'); // Items after index 2
const before = nwsapi.select('li:lt(2)'); // Items before index 2
const nth = nwsapi.select('li:nth(3)'); // Fourth list item// Content-based selectors
const hasImages = nwsapi.select('div:has(img)'); // Divs containing images
const parents = nwsapi.select('div:parent'); // Divs with children
const visible = nwsapi.select('div:visible'); // Visible divs
const hidden = nwsapi.select('div:hidden'); // Hidden divs
// Form control selectors
const buttons = nwsapi.select(':button'); // Button elements
const inputs = nwsapi.select(':input'); // All input elements
const textInputs = nwsapi.select(':text'); // Text inputs
const checkboxes = nwsapi.select(':checkbox'); // Checkboxes
const radios = nwsapi.select(':radio'); // Radio buttons
const files = nwsapi.select(':file'); // File inputs
const images = nwsapi.select(':image'); // Image inputs
const passwords = nwsapi.select(':password'); // Password inputs
const submits = nwsapi.select(':submit'); // Submit buttons
const resets = nwsapi.select(':reset'); // Reset buttons
// Header selector
const headers = nwsapi.select(':header'); // h1, h2, h3, h4, h5, h6When nwsapi-traversal.js is loaded, DOM traversal methods become available:
/**
* Walk up to parent elements
* @param element - Starting element
* @param expr - CSS selector or index
* @returns Matching parent element or null
*/
function up(element, expr);
/**
* Walk down to descendant elements
* @param element - Starting element
* @param expr - CSS selector or index
* @returns Matching descendant element or null
*/
function down(element, expr);
/**
* Walk to next sibling elements
* @param element - Starting element
* @param expr - CSS selector or index
* @returns Matching next sibling or null
*/
function next(element, expr);
/**
* Walk to previous sibling elements
* @param element - Starting element
* @param expr - CSS selector or index
* @returns Matching previous sibling or null
*/
function previous(element, expr);Usage Examples:
// After loading nwsapi-traversal.js
const nwsapi = require("nwsapi");
// Navigate by index
const secondParent = nwsapi.up(element, 2); // 2nd parent up
const firstChild = nwsapi.down(element, 0); // First child
const nextSibling = nwsapi.next(element, 1); // Next sibling
const prevSibling = nwsapi.previous(element, 1); // Previous sibling
// Navigate by selector
const form = nwsapi.up(element, 'form'); // Closest form ancestor
const button = nwsapi.down(element, 'button'); // First button descendant
const link = nwsapi.next(element, 'a'); // Next link sibling
const input = nwsapi.previous(element, 'input'); // Previous input siblingDirect access to the registered attribute operators registry.
/**
* Registry of attribute operators and their resolver configurations
* @type { [operator: string]: OperatorResolver }
*/
const Operators: {
[operator: string]: {
p1: string; // Pattern prefix
p2: string; // Pattern suffix
p3: string; // Match result
}
};Usage Examples:
const nwsapi = require("nwsapi");
// View all registered operators
console.log('Registered operators:', Object.keys(nwsapi.Operators));
// Check specific operator configuration
console.log('Equals operator:', nwsapi.Operators['=']);
console.log('Contains operator:', nwsapi.Operators['*=']);
// Iterate through all operators
Object.entries(nwsapi.Operators).forEach(([op, config]) => {
console.log(`Operator ${op}:`, config);
});Direct access to the registered custom selectors registry.
/**
* Registry of custom selectors with their patterns and callbacks
* @type { [name: string]: { Expression: RegExp; Callback: Function } }
*/
const Selectors: {
[name: string]: {
Expression: RegExp; // Pattern to match selector syntax
Callback: Function; // Resolver function
}
};Usage Examples:
const nwsapi = require("nwsapi");
// View all registered custom selectors
console.log('Custom selectors:', Object.keys(nwsapi.Selectors));
// Check specific selector registration
if (nwsapi.Selectors['Controls']) {
console.log('Controls selector pattern:', nwsapi.Selectors['Controls'].Expression);
}
// List all selector patterns
Object.entries(nwsapi.Selectors).forEach(([name, config]) => {
console.log(`Selector ${name}:`, config.Expression.source);
});const nwsapi = require("nwsapi");
// Organized extension registration
const customExtensions = {
selectors: {
'control': /^\:(control)(.*)/i,
'contains': /^\:contains\(\s*(.+)\s*\)/i
},
operators: {
'!=': { p1: '^', p2: '$', p3: 'false' },
'>': { p1: 'parseFloat', p2: '>', p3: 'true' }
},
combinators: {
'^': 'e.parentElement',
'>>>': 'e.shadowRoot'
}
};
// Register all extensions
function registerCustomExtensions() {
Object.keys(customExtensions.selectors).forEach(name => {
nwsapi.registerSelector(name, customExtensions.selectors[name], /* resolver */);
});
Object.keys(customExtensions.operators).forEach(op => {
nwsapi.registerOperator(op, customExtensions.operators[op]);
});
Object.keys(customExtensions.combinators).forEach(comb => {
nwsapi.registerCombinator(comb, customExtensions.combinators[comb]);
});
}
registerCustomExtensions();Install with Tessl CLI
npx tessl i tessl/npm-nwsapi