|
|
- "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;
|