aboutsummaryrefslogtreecommitdiff
path: root/catapult/third_party/polymer/components/shadycss/src/css-parse.js
diff options
context:
space:
mode:
Diffstat (limited to 'catapult/third_party/polymer/components/shadycss/src/css-parse.js')
-rw-r--r--catapult/third_party/polymer/components/shadycss/src/css-parse.js264
1 files changed, 264 insertions, 0 deletions
diff --git a/catapult/third_party/polymer/components/shadycss/src/css-parse.js b/catapult/third_party/polymer/components/shadycss/src/css-parse.js
new file mode 100644
index 00000000..8a8fb1cd
--- /dev/null
+++ b/catapult/third_party/polymer/components/shadycss/src/css-parse.js
@@ -0,0 +1,264 @@
+/**
+@license
+Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
+This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+Code distributed by Google as part of the polymer project is also
+subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+*/
+
+/*
+Extremely simple css parser. Intended to be not more than what we need
+and definitely not necessarily correct =).
+*/
+
+'use strict';
+
+/** @unrestricted */
+class StyleNode {
+ constructor() {
+ /** @type {number} */
+ this['start'] = 0;
+ /** @type {number} */
+ this['end'] = 0;
+ /** @type {StyleNode} */
+ this['previous'] = null;
+ /** @type {StyleNode} */
+ this['parent'] = null;
+ /** @type {Array<StyleNode>} */
+ this['rules'] = null;
+ /** @type {string} */
+ this['parsedCssText'] = '';
+ /** @type {string} */
+ this['cssText'] = '';
+ /** @type {boolean} */
+ this['atRule'] = false;
+ /** @type {number} */
+ this['type'] = 0;
+ /** @type {string} */
+ this['keyframesName'] = '';
+ /** @type {string} */
+ this['selector'] = '';
+ /** @type {string} */
+ this['parsedSelector'] = '';
+ }
+}
+
+export {StyleNode}
+
+// given a string of css, return a simple rule tree
+/**
+ * @param {string} text
+ * @return {StyleNode}
+ */
+export function parse(text) {
+ text = clean(text);
+ return parseCss(lex(text), text);
+}
+
+// remove stuff we don't care about that may hinder parsing
+/**
+ * @param {string} cssText
+ * @return {string}
+ */
+function clean(cssText) {
+ return cssText.replace(RX.comments, '').replace(RX.port, '');
+}
+
+// super simple {...} lexer that returns a node tree
+/**
+ * @param {string} text
+ * @return {StyleNode}
+ */
+function lex(text) {
+ let root = new StyleNode();
+ root['start'] = 0;
+ root['end'] = text.length
+ let n = root;
+ for (let i = 0, l = text.length; i < l; i++) {
+ if (text[i] === OPEN_BRACE) {
+ if (!n['rules']) {
+ n['rules'] = [];
+ }
+ let p = n;
+ let previous = p['rules'][p['rules'].length - 1] || null;
+ n = new StyleNode();
+ n['start'] = i + 1;
+ n['parent'] = p;
+ n['previous'] = previous;
+ p['rules'].push(n);
+ } else if (text[i] === CLOSE_BRACE) {
+ n['end'] = i + 1;
+ n = n['parent'] || root;
+ }
+ }
+ return root;
+}
+
+// add selectors/cssText to node tree
+/**
+ * @param {StyleNode} node
+ * @param {string} text
+ * @return {StyleNode}
+ */
+function parseCss(node, text) {
+ let t = text.substring(node['start'], node['end'] - 1);
+ node['parsedCssText'] = node['cssText'] = t.trim();
+ if (node['parent']) {
+ let ss = node['previous'] ? node['previous']['end'] : node['parent']['start'];
+ t = text.substring(ss, node['start'] - 1);
+ t = _expandUnicodeEscapes(t);
+ t = t.replace(RX.multipleSpaces, ' ');
+ // TODO(sorvell): ad hoc; make selector include only after last ;
+ // helps with mixin syntax
+ t = t.substring(t.lastIndexOf(';') + 1);
+ let s = node['parsedSelector'] = node['selector'] = t.trim();
+ node['atRule'] = (s.indexOf(AT_START) === 0);
+ // note, support a subset of rule types...
+ if (node['atRule']) {
+ if (s.indexOf(MEDIA_START) === 0) {
+ node['type'] = types.MEDIA_RULE;
+ } else if (s.match(RX.keyframesRule)) {
+ node['type'] = types.KEYFRAMES_RULE;
+ node['keyframesName'] =
+ node['selector'].split(RX.multipleSpaces).pop();
+ }
+ } else {
+ if (s.indexOf(VAR_START) === 0) {
+ node['type'] = types.MIXIN_RULE;
+ } else {
+ node['type'] = types.STYLE_RULE;
+ }
+ }
+ }
+ let r$ = node['rules'];
+ if (r$) {
+ for (let i = 0, l = r$.length, r;
+ (i < l) && (r = r$[i]); i++) {
+ parseCss(r, text);
+ }
+ }
+ return node;
+}
+
+/**
+ * conversion of sort unicode escapes with spaces like `\33 ` (and longer) into
+ * expanded form that doesn't require trailing space `\000033`
+ * @param {string} s
+ * @return {string}
+ */
+function _expandUnicodeEscapes(s) {
+ return s.replace(/\\([0-9a-f]{1,6})\s/gi, function() {
+ let code = arguments[1],
+ repeat = 6 - code.length;
+ while (repeat--) {
+ code = '0' + code;
+ }
+ return '\\' + code;
+ });
+}
+
+/**
+ * stringify parsed css.
+ * @param {StyleNode} node
+ * @param {boolean=} preserveProperties
+ * @param {string=} text
+ * @return {string}
+ */
+export function stringify(node, preserveProperties, text = '') {
+ // calc rule cssText
+ let cssText = '';
+ if (node['cssText'] || node['rules']) {
+ let r$ = node['rules'];
+ if (r$ && !_hasMixinRules(r$)) {
+ for (let i = 0, l = r$.length, r;
+ (i < l) && (r = r$[i]); i++) {
+ cssText = stringify(r, preserveProperties, cssText);
+ }
+ } else {
+ cssText = preserveProperties ? node['cssText'] :
+ removeCustomProps(node['cssText']);
+ cssText = cssText.trim();
+ if (cssText) {
+ cssText = ' ' + cssText + '\n';
+ }
+ }
+ }
+ // emit rule if there is cssText
+ if (cssText) {
+ if (node['selector']) {
+ text += node['selector'] + ' ' + OPEN_BRACE + '\n';
+ }
+ text += cssText;
+ if (node['selector']) {
+ text += CLOSE_BRACE + '\n\n';
+ }
+ }
+ return text;
+}
+
+/**
+ * @param {Array<StyleNode>} rules
+ * @return {boolean}
+ */
+function _hasMixinRules(rules) {
+ let r = rules[0];
+ return Boolean(r) && Boolean(r['selector']) && r['selector'].indexOf(VAR_START) === 0;
+}
+
+/**
+ * @param {string} cssText
+ * @return {string}
+ */
+function removeCustomProps(cssText) {
+ cssText = removeCustomPropAssignment(cssText);
+ return removeCustomPropApply(cssText);
+}
+
+/**
+ * @param {string} cssText
+ * @return {string}
+ */
+export function removeCustomPropAssignment(cssText) {
+ return cssText
+ .replace(RX.customProp, '')
+ .replace(RX.mixinProp, '');
+}
+
+/**
+ * @param {string} cssText
+ * @return {string}
+ */
+function removeCustomPropApply(cssText) {
+ return cssText
+ .replace(RX.mixinApply, '')
+ .replace(RX.varApply, '');
+}
+
+/** @enum {number} */
+export const types = {
+ STYLE_RULE: 1,
+ KEYFRAMES_RULE: 7,
+ MEDIA_RULE: 4,
+ MIXIN_RULE: 1000
+}
+
+const OPEN_BRACE = '{';
+const CLOSE_BRACE = '}';
+
+// helper regexp's
+const RX = {
+ comments: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
+ port: /@import[^;]*;/gim,
+ customProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,
+ mixinProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,
+ mixinApply: /@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,
+ varApply: /[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,
+ keyframesRule: /^@[^\s]*keyframes/,
+ multipleSpaces: /\s+/g
+}
+
+const VAR_START = '--';
+const MEDIA_START = '@media';
+const AT_START = '@';