convert-cssxpath
Version:
Convert CSS into XPath
158 lines (145 loc) • 5.2 kB
JavaScript
/**
* `asterisk(string)` matches parts of CSS that are represented in xPath as asterisk
*/
function asterisk(string) {
const CSS_ASTERISK_PATTERN = /^\*(?![a-z0-9_\-*\\.#])/i;
const matches = CSS_ASTERISK_PATTERN.exec(string);
if (!!matches) return {
fullGroup: matches[0]
};
}
/**
* `element(string)` matches the pieces of an initial CSS selector and returns a parsed set of fields.
* e.g., #id, .class or body
*
* It also parses out the piped namespace piece: https://www.w3.org/TR/css3-selectors/#univnmsp
*/
function element(string) {
const CSS_ELEMENT_PATTERN = /^([#.]?)((_{1,2}|-{1,2})?[a-z]+[a-z0-9\\*_-]*)((\|)([a-z0-9\\*_-]*))?/i;
// ^([#.]?)(((?<=\.)\d*)?(_{1,2}|-{1,2})?[a-z]+[a-z0-9\\*_-]*)((\|)([a-z0-9\\*_-]*))?
// RegEx with lookBehind. Not supported in js. Need to divide to 2 checks
const matches = CSS_ELEMENT_PATTERN.exec(string);
if (!!matches) {
const retVal = {
fullGroup: matches[0],
fullNamespaceGroup: matches[4],
namespace: matches[6], // TODO: this is backwards, handle namespace standardization here.
};
// either "#" or "." to indicate id or class selector
if (matches[1] === '#' || matches[1] === '.') {
retVal.specialSelectorType = matches[1];
retVal.specialSelectorValue = matches[2];
} else if (matches[1] === '') {
retVal.elementName = matches[2];
}
return retVal;
}
}
/**
* `attributePresence(string)` matches the pieces of a CSS selector that represent a attribute presence requirement.
* e.g., [disabled], [x-anything-here]
*/
function attributePresence(string) {
const CSS_ATTRIBUTE_PRESENCE_PATTERN = /^\[\s*((?!\d)[a-z0-9_\-]+)\s*\]/i;
const matches = CSS_ATTRIBUTE_PRESENCE_PATTERN.exec(string);
if (!!matches) return {
fullGroup: matches[0],
attributeName: matches[1],
};
}
/**
* `attributeValue(string)` matches the pieces of a CSS selector that represent a attribute selector.
* e.g., [disabled='disabled'], [class~='alphaghettis'], [type != 'number']
*/
function attributeValue(string) {
const CSS_ATTRIBUTE_VALUE_PATTERNS = {
UNQUOTED_VALUE_PATTERN: /^\[\s*((?!\d)[a-z0-9_-]+)\s*([$^!*|~]?=)\s*([^"'=\s<>`]+)\s*\]/i,
SINGLE_QUOTED_VALUE_PATTERN: /^\[\s*((?!\d)[a-z0-9_-]+)\s*([$^!*|~]?=)\s*'([^']+)'\s*\]/i,
DOUBLE_QUOTED_VALUE_PATTERN: /^\[\s*((?!\d)[a-z0-9_-]+)\s*([$^!*|~]?=)\s*"([^"]+)"\s*\]/i
};
function findMatches(string, obj) {
for (let key of Object.keys(obj)) {
let matches = obj[key].exec(string);
if (!!matches) return matches
}
}
const matches = findMatches(string, CSS_ATTRIBUTE_VALUE_PATTERNS);
if (!!matches) return {
fullGroup: matches[0],
field: matches[1],
value: matches[3],
isContains: (matches[2] === '~=' || matches[2] === '*='),
isStartsWith: (matches[2] === '|=' || matches[2] === '^='),
isEndsWith: (matches[2] === '$='),
/**
*
* CSS does not support "!=" (not equal) operator.
* To express it in CSS you must use a tricky combination:
* xPath //*[@class!='value'] === CSS: :not([class='value'])[class]
* So decided to support "!=" as useful extra function.
*
*/
isNotEqual: matches[2] === '!='
};
}
/**
* `pseudo(string)` matches first part of supported pseudo selectors
*/
function pseudo(string) {
const CSS_PSEUDO_PATTERN = /^(:?:[a-z-]+)(\(\s*((?![\s*]+)[^()]+?)?\s*\))?/i;
const matches = CSS_PSEUDO_PATTERN.exec(string);
if (!!matches) return {
fullGroup: matches[0],
type: matches[1],
brackets: matches[2],
argument: matches[3] // Caution! Can be undefined
};
}
/**
* validate (1n+0) based argument in position pseudo classes
*/
function nthArgument(string) {
const CSS_NTH_ARGUMENT = /^([-]?\d+)*$|^([-]?\d+)*n([+-][\d]+)?$|^(odd)$|^(even)$/;
const matches = CSS_NTH_ARGUMENT.exec(string);
if (!!matches) return {
fullGroup: matches[0],
number: matches[1],
mod: matches[2],
pos: matches[3],
odd: matches[4],
even: matches[5]
}
}
/**
* `combinator(string)` matches the pieces of a CSS selector that represent a combinator.
* e.g., +, ~ or >
*/
function combinator(string) {
const CSS_COMBINATOR_PATTERN = /(^[\s*]*(([>+~\s]){1}?\s*))[\[*a-z0-9#.:_-]+/i;
const matches = CSS_COMBINATOR_PATTERN.exec(string);
if (!!matches) return {
fullGroup: matches[1],
type: matches[3]
};
}
/**
* `comma(string)` matches commas in a CSS selector; used for disjunction.
*/
function comma(string) {
const COMMA_PATTERN = /(^\s*(([,]){1}?\s*))[:[]*[*a-z0-9#._-]+/i;
const matches = COMMA_PATTERN.exec(string);
if (!!matches) return {
fullGroup: matches[1],
type: matches[3]
};
}
module.exports = {
asterisk,
element,
attributePresence,
attributeValue,
combinator,
comma,
pseudo,
nthArgument
};