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
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 %}
|