You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
5.3 KiB

4 years ago
"use strict";
var Parser = require("fastparse");
var regexpu = require("regexpu-core");
function unescape(str) {
return str.replace(/\\(.)/g, "$1");
}
function commentMatch(match, content) {
this.selector.nodes.push({
type: "comment",
content: content
});
}
function typeMatch(type) {
return function(match, name) {
this.selector.nodes.push({
type: type,
name: unescape(name)
});
};
}
function pseudoClassStartMatch(match, name) {
var newToken = {
type: "pseudo-class",
name: unescape(name),
content: ""
};
this.selector.nodes.push(newToken);
this.token = newToken;
this.brackets = 1;
return "inBrackets";
}
function nestedPseudoClassStartMatch(match, name, after) {
var newSelector = {
type: "selector",
nodes: []
};
var newToken = {
type: "nested-pseudo-class",
name: unescape(name),
nodes: [newSelector]
};
if(after) {
newSelector.before = after;
}
this.selector.nodes.push(newToken);
this.stack.push(this.root);
this.root = newToken;
this.selector = newSelector;
}
function nestedEnd(match, before) {
if(this.stack.length > 0) {
if(before) {
this.selector.after = before;
}
this.root = this.stack.pop();
this.selector = this.root.nodes[this.root.nodes.length - 1];
} else {
this.selector.nodes.push({
type: "invalid",
value: match
});
}
}
function operatorMatch(match, before, operator, after) {
var token = {
type: "operator",
operator: operator
};
if(before) {
token.before = before;
}
if(after) {
token.after = after;
}
this.selector.nodes.push(token);
}
function spacingMatch(match) {
this.selector.nodes.push({
type: "spacing",
value: match
});
}
function elementMatch(match, namespace, name) {
var newToken = {
type: "element",
name: unescape(name)
};
if(namespace) {
newToken.namespace = unescape(namespace.substr(0, namespace.length - 1));
}
this.selector.nodes.push(newToken);
}
function universalMatch(match, namespace) {
var newToken = {
type: "universal"
};
if(namespace) {
newToken.namespace = unescape(namespace.substr(0, namespace.length - 1));
}
this.selector.nodes.push(newToken);
}
function attributeMatch(match, content) {
this.selector.nodes.push({
type: "attribute",
content: content
});
}
function invalidMatch(match) {
this.selector.nodes.push({
type: "invalid",
value: match
});
}
function irrelevantSpacingStartMatch(match) {
this.selector.before = match;
}
function irrelevantSpacingEndMatch(match) {
this.selector.after = match;
}
function nextSelectorMatch(match, before, after) {
var newSelector = {
type: "selector",
nodes: []
};
if(before) {
this.selector.after = before;
}
if(after) {
newSelector.before = after;
}
this.root.nodes.push(newSelector);
this.selector = newSelector;
}
function addToCurrent(match) {
this.token.content += match;
}
function bracketStart(match) {
this.token.content += match;
this.brackets++;
}
function bracketEnd(match) {
if(--this.brackets === 0) {
return "selector";
}
this.token.content += match;
}
function getSelectors() {
// The assignment here is split to preserve the property enumeration order.
var selectors = {
"/\\*([\\s\\S]*?)\\*/": commentMatch
};
// https://www.w3.org/TR/CSS21/syndata.html#characters
// 4.1.3: identifiers (...) can contain only the characters [a-zA-Z0-9] and
// ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_)
//
// 10ffff is the maximum allowed in current Unicode
selectors[regexpu("\\.((?:\\\\.|[A-Za-z_\\-\\u{00a0}-\\u{10ffff}])(?:\\\\.|[A-Za-z_\\-0-9\\u{00a0}-\\u{10ffff}])*)", "u")] = typeMatch("class");
selectors[regexpu("#((?:\\\\.|[A-Za-z_\\-\\u{00a0}-\\u{10ffff}])(?:\\\\.|[A-Za-z_\\-0-9\\u{00a0}-\\u{10ffff}])*)", "u")] = typeMatch("id");
var selectorsSecondHalf = {
":(not|matches|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch,
":((?:\\\\.|[A-Za-z_\\-0-9])+)\\(": pseudoClassStartMatch,
":((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-class"),
"::((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-element"),
"(\\*\\|)((?:\\\\.|[A-Za-z_\\-0-9])+)": elementMatch,
"(\\*\\|)\\*": universalMatch,
"((?:\\\\.|[A-Za-z_\\-0-9])*\\|)?\\*": universalMatch,
"((?:\\\\.|[A-Za-z_\\-0-9])*\\|)?((?:\\\\.|[A-Za-z_\\-])(?:\\\\.|[A-Za-z_\\-0-9])*)": elementMatch,
"\\[([^\\]]+)\\]": attributeMatch,
"(\\s*)\\)": nestedEnd,
"(\\s*)((?:\\|\\|)|(?:>>)|[>+~])(\\s*)": operatorMatch,
"(\\s*),(\\s*)": nextSelectorMatch,
"\\s+$": irrelevantSpacingEndMatch,
"^\\s+": irrelevantSpacingStartMatch,
"\\s+": spacingMatch,
".": invalidMatch
};
var selector;
for (selector in selectorsSecondHalf) {
if (Object.prototype.hasOwnProperty.call(selectorsSecondHalf, selector)) {
selectors[selector] = selectorsSecondHalf[selector];
}
}
return selectors;
}
var parser = new Parser({
selector: getSelectors(),
inBrackets: {
"/\\*[\\s\\S]*?\\*/": addToCurrent,
"\"([^\\\\\"]|\\\\.)*\"": addToCurrent,
"'([^\\\\']|\\\\.)*'": addToCurrent,
"[^()'\"/]+": addToCurrent,
"\\(": bracketStart,
"\\)": bracketEnd,
".": addToCurrent
}
});
function parse(str) {
var selectorNode = {
type: "selector",
nodes: []
};
var rootNode = {
type: "selectors",
nodes: [
selectorNode
]
};
parser.parse("selector", str, {
stack: [],
root: rootNode,
selector: selectorNode
});
return rootNode;
}
module.exports = parse;