diff options
Diffstat (limited to 'node_modules/css-selector-tokenizer/lib/parse.js')
-rw-r--r-- | node_modules/css-selector-tokenizer/lib/parse.js | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/node_modules/css-selector-tokenizer/lib/parse.js b/node_modules/css-selector-tokenizer/lib/parse.js new file mode 100644 index 0000000..632bdfd --- /dev/null +++ b/node_modules/css-selector-tokenizer/lib/parse.js @@ -0,0 +1,239 @@ +"use strict"; + +var Parser = require("fastparse"); +var uniRegexp = require("./uni-regexp"); + +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[uniRegexp.typeMatchClass] = typeMatch("class"); + selectors[uniRegexp.typeMatchId] = 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; |