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.

183 lines
4.8 KiB

@{%
const flattenDeep = require('lodash.flattendeep');
const appendItem = (a, b) => d => d[a].concat([d[b]]);
const appendItemChar = (a, b) => d => d[a].concat(d[b]);
const flatten = d => {
d = d.filter((r) => { return r !== null; });
return flattenDeep(d);
};
const combinatorMap = {
' ': 'descendantCombinator',
'+': 'adjacentSiblingCombinator',
'>': 'childCombinator',
'~': 'generalSiblingCombinator'
};
const concatUsingCombinator = d => {
return (
Array.isArray(d[0]) ? d[0] : [d[0]]
)
.concat({
type: combinatorMap[d[2]]
})
.concat(d[4]);
};
const parseAsNumber = (d, i, reject) => {
const joined = flattenDeep(d).join('');
const parsed = parseFloat(joined);
if (isNaN(parsed)) {
return reject
}
return parsed
}
const parseAsBoolean = (d, i, reject) => {
if (d[0] === 'true') return true;
if (d[0] === 'false') return false;
return reject;
}
const parseFalsyPrimitive = (d, i, reject) => {
if (d[0] === 'NaN') return NaN;
if (d[0] === 'undefined') return undefined;
if (d[0] === 'null') return null;
return reject
}
%}
combinator ->
selector
| combinator _ [>+~ ] _ selector {% concatUsingCombinator %}
selector -> selectorBody {% d => ({type: 'selector', body: d[0]}) %}
selectorBody ->
typeSelector:? simpleSelector:* {% (d, i, reject) => { const selectors = flatten(d); if (!selectors.length) return reject; return selectors; } %}
| universalSelector simpleSelector:* {% flatten %}
simpleSelector ->
idSelector
| classSelector
| attributeValueSelector
| attributePresenceSelector
| pseudoClassSelector
| pseudoElementSelector
typeSelector -> attributeName {% d => ({type: 'typeSelector', name: d[0]}) %}
# see http://stackoverflow.com/a/449000/368691
className -> "-":? [_a-zA-Z] [_a-zA-Z0-9-]:* {% d => (d[0] || '') + d[1] + d[2].join('') %}
attributeName -> [_a-z()A-Z] [_a-zA-Z()0-9-]:* {% d => d[0] + d[1].join('') %}
classSelector -> "." className {% d => ({type: 'classSelector', name: d[1]}) %}
# The selector used for ID name does not permit all valid HTML5 ID names.
# In HTML5 ID value can be any string that does not include a space.
# @see https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#the-id-attribute
#
# I have not seen special characters being used in ID.
# Therefore, for simplicity, attributeName regex is used here.
#
# If we were to respect HTML5 spec, we'd need to accomodate all special characters,
# including [>+~:#. ].
idSelector -> "#" attributeName {% d => ({type: 'idSelector', name: d[1]}) %}
universalSelector -> "*" {% d => ({type: 'universalSelector'}) %}
attributePresenceSelector -> "[" attributeName "]" {% d => ({type: 'attributePresenceSelector', name: d[1]}) %}
attributeOperator ->
"=" |
"~=" |
"|=" |
"^=" |
"$=" |
"*="
attributeValueSelector -> "[" attributeName attributeOperator attributeValue "]"
{%
d => ({
type: 'attributeValueSelector',
name: d[1],
value: d[3],
operator: d[2][0]
})
%}
attributeValue ->
falsyPrimitiveStrings {% id %}
| numericValue {% id %}
| sqstring {% id %}
| dqstring {% id %}
falsyPrimitiveStrings ->
"false" {% parseAsBoolean %}
| "true" {% parseAsBoolean %}
| "NaN" {% parseFalsyPrimitive %}
| "null" {% parseFalsyPrimitive %}
| "undefined" {% parseFalsyPrimitive %}
numericValue ->
# implicit positive floats
int "." int {% parseAsNumber %}
# explicit positive floats
| "+" int "." int {% parseAsNumber %}
# negative floats
| "-" int "." int {% parseAsNumber %}
# negative ints
| "-" int {% parseAsNumber %}
# implicit positive ints
| int {% parseAsNumber %}
# explicit positive ints
| "+" int {% parseAsNumber %}
# implicit positive Infinity
| "Infinity" {% parseAsNumber %}
# explicit positive Infinity
| "+Infinity" {% parseAsNumber %}
# negative Infinity
| "-Infinity" {% parseAsNumber %}
int -> [0-9]:+
classParameters ->
null
| classParameter
| classParameters "," _ classParameter {% appendItem(0, 3) %}
classParameter ->
[^()"', ]:+ {% d => d[0].join('') %}
| sqstring {% id %}
| dqstring {% id %}
pseudoElementSelector ->
"::" pseudoClassSelectorName {% d => ({type: 'pseudoElementSelector', name: d[1]}) %}
pseudoClassSelector ->
":" pseudoClassSelectorName {% d => ({type: 'pseudoClassSelector', name: d[1]}) %}
| ":" pseudoClassSelectorName "(" classParameters ")" {% d => ({type: 'pseudoClassSelector', name: d[1], parameters: d[3]}) %}
pseudoClassSelectorName ->
[a-zA-Z] [a-zA-Z0-9-_]:+ {% d => d[0] + d[1].join('') %}
dqstring ->
"\"" dstrchar:* "\"" {% d => d[1].join('') %}
dstrchar ->
[^"] {% id %}
| "\\\"" {% d => '"' %}
sqstring ->
"'" sstrchar:* "'" {% d => d[1].join('') %}
sstrchar ->
[^'] {% id %}
| "\\'" {% d => '\'' %}
_ ->
[ ]:* {% d => null %}