|
parse = (function(tokenRegExp, bracketOpeningRegExp, bracketClosingRegExp) { |
|
// ... |
|
var arrayPrototype = Array.prototype; |
|
// ... |
|
var arrayFilter = arrayPrototype.filter; |
|
var arrayForEach = arrayPrototype.forEach; |
|
var arrayIndexOf = arrayPrototype.indexOf; |
|
var arrayJoin = arrayPrototype.join; |
|
var arrayPush = arrayPrototype.push; |
|
var arraySlice = arrayPrototype.slice; |
|
var arraySplice = arrayPrototype.splice; |
|
var arrayUnshift = arrayPrototype.unshift; |
|
// ... |
|
var objectGetPrototypeOf = Object.getPrototypeOf; |
|
var objectCreate = Object.create; |
|
var objectHasOwnProperty = Function.call.bind(Object.hasOwnProperty); |
|
var objectDefineProperties = Object.defineProperties; |
|
var objectDefineProperty = Object.defineProperty; |
|
// ... |
|
function noop() {} |
|
function toCSSClass(name, Class, Super, protoDescriptors, staticDescriptors) { |
|
return (objectDefineProperties((objectDefineProperties((Class = Function('f', 'return function CSS' + name + '){f.apply(this,arguments)}')(Class)), Object(staticDescriptors)).prototype = objectCreate(Object(Super).prototype)), Object(protoDescriptors)).constructor = Class); |
|
} |
|
function asValue(value) { |
|
return { |
|
configurable: true, |
|
writable: true, |
|
value: value |
|
}; |
|
} |
|
function asAccessor(getter, setter) { |
|
return { |
|
configurable: true, |
|
get: getter, |
|
set: setter |
|
}; |
|
} |
|
function cssNodeSourceSetter() { |
|
var source = { start: null, end: null }; |
|
return this instanceof CSSNode && objectDefineProperty(this, 'source', { configurable: true, get: get, set: set }) && arguments.length ? set.apply(this, arguments) : source; |
|
function get() { |
|
return source; |
|
} |
|
function set(nextSource) { |
|
var start = Object(nextSource).start; |
|
var end = Object(nextSource).end; |
|
source.start = isFinite(start) ? start : null; |
|
source.end = isFinite(end) ? end : null; |
|
return source; |
|
} |
|
} |
|
function cssNodeParentAccessor() { |
|
var parent = null; |
|
return this instanceof CSSNode && objectDefineProperty(this, 'parent', { configurable: true, get: get, set: set }) && arguments.length ? set.apply(this, arguments) : parent; |
|
function get() { |
|
return parent; |
|
} |
|
function set(nextParent, ignorePush) { |
|
if (nextParent !== parent) { |
|
if (isCSSBlock(parent)) { |
|
parent.remove(this); |
|
} |
|
if (isCSSBlock(nextParent)) { |
|
if (!ignorePush) arrayPush.call(nextParent.value, this); |
|
parent = nextParent; |
|
} else { |
|
parent = null; |
|
} |
|
} |
|
return true; |
|
} |
|
} |
|
function cssBlockValueAccessor() { |
|
var value = []; |
|
return this instanceof CSSBlock && objectDefineProperty(this, 'value', { configurable: true, get: get, set: set }) && arguments.length ? set.apply(this, arguments) : value; |
|
function get() { |
|
return value; |
|
} |
|
function set(nextValue) { |
|
arraySplice.bind(value, 0, value.length).apply(null, nextValue == null ? [] : arraySlice.call(nextValue)); |
|
return true; |
|
} |
|
} |
|
function cssNodeIndexSelfGetter() { |
|
if (isCSSNode(this)) { |
|
var parent = this.parent; |
|
if (isCSSBlock(parent)) { |
|
return arrayIndexOf.call(parent.value, this); |
|
} |
|
} |
|
return -1; |
|
} |
|
function cssNodeIndexSelfSetter(nextIndex) { |
|
var index = isFinite(nextIndex) && cssNodeIndexSelfGetter.call(this); |
|
if (index !== -1) { |
|
arraySplice.bind(parent.value, nextIndex, 0).apply(null, arraySplice.call(this, index, 1)); |
|
return true; |
|
} |
|
} |
|
function cssNodeTypeGetter() { |
|
return Object(objectGetPrototypeOf(this)).constructor.name; |
|
} |
|
// ... |
|
var CSSNode = toCSSClass( |
|
'Node(_', |
|
function(value) { |
|
this.value = value; |
|
}, |
|
Object, |
|
{ |
|
value: asValue(''), |
|
parent: asAccessor(cssNodeParentAccessor, cssNodeParentAccessor), |
|
indexSelf: asAccessor(cssNodeIndexSelfGetter, cssNodeIndexSelfSetter), |
|
removeSelf: asValue(function removeSelf() { |
|
var index = this.indexSelf; |
|
if (index !== -1) { |
|
arraySplice.call(this.parent.value, index, 1); |
|
} |
|
}), |
|
replaceSelf: asValue(function replaceSelf() { |
|
var index = cssNodeIndexSelfGetter.call(this); |
|
if (index !== -1) { |
|
arraySplice.bind(this.parent.value, index, 1).apply(null, arrayFilter.call(arguments, isCSSNode)); |
|
} |
|
}), |
|
visit: asValue(function visit(visitors) { |
|
visiting(this); |
|
return this; |
|
function visiting(node) { |
|
var visitor = visitors[cssNodeTypeGetter.call(node)]; |
|
if (typeof visitor === 'function') { |
|
visitor.call(visitors, node); |
|
} else if (typeof Object(visitor).enter === 'function') { |
|
visitor.enter.call(visitors, node); |
|
} |
|
if (node.value === Object(node.value)) { |
|
arrayForEach.call(node.value, visiting); |
|
} |
|
if (typeof Object(visitor).exit === 'function') { |
|
visitor.exit.call(visitors, node); |
|
} |
|
} |
|
}), |
|
source: asAccessor(cssNodeSourceSetter, cssNodeSourceSetter), |
|
toString: asValue(function toString() { |
|
return '' + this.value; |
|
}), |
|
type: asAccessor(cssNodeTypeGetter, noop) |
|
} |
|
); |
|
var CSSMeta = toCSSClass('Meta(_', CSSNode, CSSNode); |
|
var AtKeyword = toCSSClass('AtKeyword(_', CSSNode, CSSNode, { |
|
toString: asValue(function toString() { |
|
return '@' + this.value; |
|
}) |
|
}); |
|
var cssBlockIndexOf = function indexOf(node) { |
|
return isCSSBlock(this) && isCSSNode(node) ? arrayIndexOf.call(this.value, node) : -1; |
|
}; |
|
var cssBlockReplace = function replace(replacee) { |
|
var index = cssBlockIndexOf.call(this, replacee); |
|
if (index !== -1) { |
|
cssNodeParentAccessor.call(replacee, null, true); |
|
arraySplice.bind(this.value, index, 1).apply( |
|
null, |
|
arraySlice.call(arguments, 1).filter(function(replacer) { |
|
return cssNodeParentAccessor.call(replacer, this, true); |
|
}, this) |
|
); |
|
} |
|
return this; |
|
}; |
|
var cssBlockRemove = function remove(removee) { |
|
return cssBlockReplace.call(this, removee); |
|
}; |
|
var cssBlockAppend = function append() { |
|
if (isCSSBlock(this)) { |
|
arrayPush.apply( |
|
this.value, |
|
arrayFilter.call( |
|
arguments, |
|
function(appender) { |
|
return cssBlockValueAccessor.call(appender, this, true); |
|
}, |
|
this |
|
) |
|
); |
|
} |
|
return this; |
|
}; |
|
var cssBlockPrepend = function prepend() { |
|
if (isCSSBlock(this)) { |
|
arrayUnshift.apply( |
|
this.value, |
|
arrayFilter.call( |
|
arguments, |
|
function(prepender) { |
|
return cssBlockValueAccessor.call(prepender, this, true); |
|
}, |
|
this |
|
) |
|
); |
|
} |
|
return this; |
|
}; |
|
// ... |
|
var CSSBlock = toCSSClass( |
|
'Block(', |
|
function() { |
|
this.name = ''; |
|
this.bracket = ''; |
|
this.mirror = ''; |
|
}, |
|
CSSNode, |
|
{ |
|
name: asValue(''), |
|
bracket: asValue(''), |
|
mirror: asValue(''), |
|
value: asAccessor(cssBlockValueAccessor, cssBlockValueAccessor), |
|
toString: asValue(function toString() { |
|
return '' + this.name + this.bracket + arrayJoin.call(this.value, '') + this.mirror; |
|
}), |
|
first: asAccessor( |
|
function() { |
|
return isCSSBlock(this) && this.value[0]; |
|
}, |
|
function(first) { |
|
return cssNodeParentAccessor.call(first, this, true) && (this.value[0] = first); |
|
} |
|
), |
|
last: asAccessor( |
|
function() { |
|
return isCSSBlock(this) && this.value[this.value.length - 1]; |
|
}, |
|
function(last) { |
|
return cssNodeParentAccessor.call(last, this, true) && (this.value[this.value.length - 1] = last); |
|
} |
|
), |
|
append: asValue(cssBlockAppend), |
|
indexOf: asValue(cssBlockIndexOf), |
|
prepend: asValue(cssBlockPrepend), |
|
remove: asValue(cssBlockRemove), |
|
replace: asValue(cssBlockReplace) |
|
} |
|
); |
|
function isCSSBlock(value) { |
|
return value instanceof CSSBlock; |
|
} |
|
function isCSSNode(value) { |
|
return value instanceof CSSNode; |
|
} |
|
var CSSRoot = toCSSClass('Root(', CSSBlock, CSSBlock); |
|
var CSSFunction = toCSSClass('Function(_', CSSBlock, CSSBlock); |
|
var CSSList = [ |
|
null, |
|
toCSSClass('Comment(_', CSSNode, CSSMeta), |
|
toCSSClass('Whitespace(_', CSSNode, CSSMeta), |
|
toCSSClass('String(_', CSSNode, CSSNode), |
|
toCSSClass('Hash(_', CSSNode, CSSNode), |
|
toCSSClass( |
|
'Unit(_', |
|
function(value, unit) { |
|
CSSNode.call(this, value); |
|
this.unit = arguments.length < 2 ? '' : String(unit); |
|
}, |
|
CSSNode, |
|
{ |
|
toString: asValue(function toString() { |
|
return '' + this.value + this.unit; |
|
}) |
|
} |
|
), |
|
toCSSClass('CDC(_', CSSNode, CSSNode), |
|
toCSSClass('CDO(_', CSSNode, CSSNode), |
|
toCSSClass('Ident(_', CSSNode, CSSNode), |
|
toCSSClass('Delim(_', CSSNode, CSSNode) |
|
]; |
|
return function parse(cssText) { |
|
var root = new CSSRoot(); |
|
root.source.start = 0; |
|
var parent = root; |
|
var lastStart = 0; |
|
var lastEnd = 0; |
|
var result, thisTokenIndex, lastTokenIndex, thisValue, lastValue; |
|
while ((result = tokenRegExp.exec(cssText))) { |
|
for (thisTokenIndex = 0; result[++thisTokenIndex] === undefined; ) {} |
|
thisValue = result[0]; |
|
if (lastTokenIndex === 5 && thisTokenIndex === 8) { |
|
// unit = number + ident |
|
parent.last.unit = thisValue; |
|
} else if (lastTokenIndex === 5 && thisTokenIndex === 9 && thisValue === '%') { |
|
// unit = number + "%" |
|
parent.last.unit = thisValue; |
|
} else if (lastTokenIndex === 9 && thisTokenIndex === 8 && lastValue === '@') { |
|
// at-keyword = "@" + ident |
|
parent.last = new AtKeyword(thisValue); |
|
parent.last.source.start = lastStart; |
|
} else if (lastTokenIndex === 8 && thisTokenIndex === 9 && thisValue === '(') { |
|
// enter function = ident + "(" |
|
parent.last = new CSSFunction(); |
|
parent.last.name = lastValue; |
|
parent.last.source.start = lastStart; |
|
} else { |
|
lastStart = parent.append(new CSSList[thisTokenIndex](thisValue)).last.source.start = lastEnd; |
|
} |
|
lastTokenIndex = thisTokenIndex; |
|
lastValue = thisValue; |
|
lastEnd = parent.last.source.end = tokenRegExp.lastIndex; |
|
if (thisTokenIndex === 9 && bracketOpeningRegExp.test(thisValue)) { |
|
if (!(parent.last instanceof CSSBlock)) { |
|
parent.last = new CSSBlock(); |
|
parent.last.source.start = lastStart; |
|
} |
|
parent.last.bracket = thisValue; |
|
parent = parent.last; |
|
} else if (thisTokenIndex === 9 && bracketClosingRegExp.test(thisValue) && parent !== root) { |
|
parent.mirror = thisValue; |
|
parent.source.end = parent.value.pop().source.end; |
|
parent = parent.parent; |
|
} |
|
} |
|
root.source.end = root.value[root.value.length - 1].source.end; |
|
return root; |
|
}; |
|
})( |
|
// regex |
|
/(\x2f\x2a[^\x2a]*\x2a+(?:[^\x2f\x2a][^\x2a]*\x2a+)*\x2f)|([\x20\x09\x0a\x0c\x0d]+)|((?:(?:\x22(?:[^\x22\x5c]|\x5c.)*\x22)|(?:\x27(?:[^\x27\x5c]|\x5c.)*\x27)))|(\x23(?:[\x41-\x46\x61-\x66\x30-\x39\x5f\x2d\x80-\xff]|\x5c[^\x0a\x0c\x0d])+)|([\x2b\x2d]?(?:[\x30-\x39]+(?:\x2e[\x30-\x39]*)?|\x2e[\x30-\x39]+)(?:[\x45\x65]\x2d?[\x30-\x39]+)?)|(\x2d\x2d\x3e)|(\x3c\x21\x2d\x2d)|((?:-(?:[\x41-\x5a\x61-\x7a\x80-\xff\x5a]|\x2d|\[^\x0a\x0c\x0d])|[\x41-\x5a\x61-\x7a\x80-\xff\x5a]|\[^\x0a\x0c\x0d])(?:[\x41-\x5a\x61-\x7a\x80-\xff\x5a\x30-\x39\x2d]|\[^\x0a\x0c\x0d])*)|(\x5c[^\x0a\x0c\x0d]|.)/g, |
|
/[\x28\x5b\x7b]/, |
|
/[\x29\x5d\x7d]/ |
|
); |