UNPKG

enb

Version:

Faster BEM/BEViS assembler

409 lines (315 loc) 8.33 kB
/** * --- (C) Original BEM Tools * * Инструментарий для работы с танкером. * Заимствованный. */ var DOM = require('dom-js'); var QUOTE_CHAR = '"'; var SINGLE_QUOTE_CHAR = '\''; var isArray = Array.isArray; var isJson = function (obj) { try { if (isString(obj)) { var jsonStart = obj.trim().charAt(0); if (jsonStart === '{' || jsonStart === '[') { return JSON.parse(obj); } } return false; } catch (e) { return false; } }; var isString = function (str) { return typeof str === 'string'; }; var isSimple = function (obj) { var type = typeof obj; return type === 'string' || type === 'number'; }; var parseXml = exports.parseXml = function (xml, cb) { if (!isSimple(xml)) { xml = JSON.stringify(xml); } try { new DOM.DomJS().parse('<root>' + xml + '</root>', function (err, dom) { if (err) { return; } cb(dom.children); }); } catch (e) { parseXml(DOM.escape(xml), cb); } }; var domToJs = exports.domToJs = function (nodes) { var code = expandNodes(toCommonNodes(nodes), jsExpander); if (code.length === 0) { return '\'\''; } else { var firstLine = code[0]; var firstChar = firstLine.charAt(0); if (code.length === 1 && (firstChar === QUOTE_CHAR || firstChar === SINGLE_QUOTE_CHAR)) { return firstLine; } else { return 'function (params) { return ' + code.join(' + ') + ' }'; } } }; exports.xmlToJs = function (xml, cb) { parseXml(xml, function (nodes) { cb(domToJs(nodes)); }); }; /** * @param {Array|String|Number} node * @param {Function} expanderFn */ function expandNodes(node, expanderFn) { if (isSimple(node)) { return node; } return node.map(function (item) { return isSimple(item) ? item : expanderFn(item); }); } /** * @param {Array} node * @returns {String} */ function jsExpander(node, _raw) { if (_raw == null) { _raw = false; } var currentExpander = function (node) { return jsExpander(node, _raw); }; var rawExpander = function (node) { return jsExpander(node, true); }; if (!node) { // FIXME: should we throw? console.warn('[WARN]: Undefined item'); return ''; } var nodeclass = node.pop(); var code; switch (nodeclass) { case 'TANKER_DYNAMIC': // [ keyset, key, [params] ] code = expandNodes(node, currentExpander); return 'this.keyset(\'' + code[0] + '\').key(\'' + code[1] + '\', ' + (code[2] || '{}') + ')'; case 'JS_DYNAMIC': // [ code ] code = '(function(params) { ' + expandNodes(node[0], rawExpander).join('') + ' }).call(this, params);'; return code; case 'XSL_DYNAMIC': // [ code ] return ''; case 'XML': // [ tag, attrs, [content] ] var tag = node[0]; var attrs = node[1]; var prop = []; var s = ''; var t; var c; code = []; for (c in attrs) { prop.push([c, SINGLE_QUOTE_CHAR + attrs[c] + SINGLE_QUOTE_CHAR].join('=')); } prop = prop.length ? jsQuote(' ' + prop.join(' ')) : ''; c = ''; t = '"<' + tag + prop; if (node[2].length) { s = ' + '; t += '>"'; code.push(t); c = expandNodes(node[2], currentExpander); isArray(c) || (c = [c]); [].push.apply(code, c); code.push(t = '"</' + tag + '>"'); } else { t += '/>"'; code.push(t); } return code.join(s); case 'PARAMS': // [ [params] ] return '{ ' + expandNodes(node, currentExpander).join(', ') + ' } '; case 'PARAM': // [ name, [code] ] code = expandNodes(node[1], currentExpander); return [node[0], isArray(code) ? code.join(' + ') : code].join(': '); case 'PARAM-CALL': // [ key ] return 'params[' + node[0] + ']'; case 'TEXT': // [ text ] return _raw ? node[0] : SINGLE_QUOTE_CHAR + jsQuote(node[0]) + SINGLE_QUOTE_CHAR; default: throw new Error('Unexpected item type: ' + nodeclass); } } /** * @param {Node[]} nodes * @returns {AST} */ function toCommonNodes(nodes) { var code = []; nodes.forEach(function (node) { if (node.name) { code.push(_node(node)); } else if (node.text) { var text = _json(node.text); if (text) { code.push(text); } } }); return code; } /** * @param {Object|Node} str * @returns {AST|String} */ function _text(str) { if (!isSimple(str)) { return ''; } str = str.replace(/\n\s\s+/g, '\n '); if (!str.length) { return ''; } return [str, 'TEXT']; } /** * @returns {AST} */ function _empty() { return ['', 'TEXT']; } /** * @param {Node} node * @returns {AST} */ function _node(node) { var name = node.name; switch (name) { case 'i18n:dynamic': var attrs = node.attributes; if (attrs && attrs.key) { // tanker dynamic var keyset = _keyset(attrs); var params = _params(node.children); return [keyset, attrs.key, params, 'TANKER_DYNAMIC']; } // custom return _dynamic(node.children); case 'i18n:param': if (node.firstChild()) { return _paramCall(node.children[0]); } } if (!~name.indexOf(':')) { return _xml(node); } } /** * @param {Node} node * @returns {AST} */ function _xml(node) { return [node.name, node.attributes, toCommonNodes(node.children), 'XML']; } /** * @param {Array|Object} nodes * @returns {AST} */ function _json(nodes) { var json; if (!(json = isJson(nodes))) { return _text(nodes); } if (isArray(json)) { // FIXME: array should always produce `plural_adv`? // FIXME: `none` value for json var params = [ ['"count"', [ ['"count"', 'PARAM-CALL'] ], 'PARAM'] ] .concat(['one', 'some', 'many', 'none'].map(function (p, i) { return [quotify(p), quotify(json[i] || ''), 'PARAM']; })); params.push('PARAMS'); // FIXME: hardcode return ['i-tanker__dynamic', 'plural_adv', params, 'TANKER_DYNAMIC']; } return _text(nodes.toString()); } /** * @param {Node[]} nodes * @returns {AST} */ function _dynamic(nodes) { var code = []; nodes.forEach(function (node) { var name = node.name; if (name === 'i18n:js') { code.push([toCommonNodes(node.children), 'JS_DYNAMIC']); //} else if (name === 'i18n:xsl') { // code.push([toCommonNodes(node.children), 'XSL_DYNAMIC']); } }); return code.length === 1 ? code[0] : code; } /** * @param {Node[]} nodes * @returns {AST} */ function _params(nodes) { var params = []; nodes.forEach(function (node) { if (!node.name) { return; } params.push(_param(node)); }); params.push('PARAMS'); return params; } /** * @param {Node} param * @returns {AST} */ function _param(param) { var name = param.name.replace(/i18n:/, ''); var val = toCommonNodes(param.children); val.length || val.push(_empty()); return [JSON.stringify(name), val, 'PARAM']; } /** * @param {Node} param * @returns {AST} */ function _paramCall(param) { return [JSON.stringify(param.text), 'PARAM-CALL']; } /** * @param {Node} param * @returns {String} */ function _keyset(param) { // FIXME: get rid of Yandex.Tanker specifics return ((param.project && param.project === 'tanker') ? 'i-' + param.project + '__' : '') + param.keyset; } /** Helpers **/ function jsQuote(s) { return '' + s.replace(/([\\\/\'\r\n])/g, '\\$1'); } function quotify(str) { if (isString(str)) { return QUOTE_CHAR + str + QUOTE_CHAR; } return str; }