UNPKG

bem

Version:
255 lines (174 loc) 5.56 kB
var DOM = require('dom-js'), QUOTE_CHAR = '"', SINGLE_QUOTE_CHAR = "'"; var parseXml = exports.parseXml = function(xml, cb) { 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); return code.length === 1 && (code[0].charAt(0) === QUOTE_CHAR || code[0].charAt(0) === SINGLE_QUOTE_CHAR ) ? code[0] : '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); }); } // TODO: js-quote() function jsQuote(s) { return '' + s.replace(/([\\\/\'\r\n])/g, '\\$1'); } /** * @param {Array} item * @returns {String} */ function jsExpander(item, _raw) { typeof _raw !== 'undefined' || (_raw = false); var currentExpander = function(item) { return jsExpander(item, _raw); }, rawExpander = function(item) { return jsExpander(item, true); }; if(!item) { // FIXME: should we throw? console.warn('[WARN]: Undefined item'); return ''; } var type = item.pop(), code; switch(type) { case 'TANKER_DYNAMIC': // [ keyset, key, [params] ] code = expandNodes(item, currentExpander); return "this.keyset('" + code[0] + "').key('" + code[1] + "', " + (code[2] || "{}") + ")"; case 'JS_DYNAMIC': // [ code ] code = '(function(params) { ' + expandNodes(item[0], rawExpander).join('') + ' }).call(this, params);'; return code; case 'XSL_DYNAMIC': // [ code ] return ''; case 'XML': // [ tag, attrs, [content] ] var tag = item[0], attrs = item[1], prop = [], a; for(a in attrs) { prop.push([a, SINGLE_QUOTE_CHAR + attrs[a] + SINGLE_QUOTE_CHAR].join('=')); }; prop = prop.length? jsQuote(' ' + prop.join(' ')) : ''; code = item[2].length? ['"<' + tag + prop + '>"', expandNodes(item[2], currentExpander), '"</' + tag + '>"'].join(' + ') : '"<' + tag + prop + '/>"'; return code; case 'PARAMS': // [ [params] ] return "{ " + expandNodes(item, currentExpander).join(', ') + " } "; case 'PARAM': // [ name, [code] ] code = expandNodes(item[1], currentExpander); return [item[0], toString.call(code) === '[object Array]' ? code.join(' + ') : code].join(': '); case 'PARAM-CALL': // [ key ] return 'params[' + item[0] + ']'; case 'TEXT': // [ text ] return _raw ? item[0] : SINGLE_QUOTE_CHAR + jsQuote(item[0]) + SINGLE_QUOTE_CHAR; default: throw new Error('Unexpected item type: ' + type); } } function isSimple(obj) { var type = typeof obj; return type === 'string' || type === 'number'; } /** * @param {Array} nodes */ function toCommonNodes(nodes) { var code = []; nodes.forEach(function(node) { if(node.name) { code.push(_node(node)); } else if(node.text) { var text = _text(node.text); if(text) code.push(text); } }); return code; } function _text(str) { if(!isSimple(str)) return ''; str = str.replace(/\n\s\s+/g, '\n '); if(!str.length) return ''; return [str, 'TEXT']; } function _node(node) { if(node.name === 'i18n:dynamic') { var attrs = node.attributes; if(attrs && attrs.key) { var keyset = _keyset(attrs), params = _params(node.children); return [keyset, attrs.key, params, 'TANKER_DYNAMIC']; } else { // custom return _dynamic(node.children); } } else if(node.name === 'i18n:param') { if(node.firstChild()) return _param(node.children[0]); } else if(node.name.indexOf(':') === -1) { return _xml(node); } } function _xml(node) { return [node.name, node.attributes, toCommonNodes(node.children), 'XML']; } 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; } function _params(nodes) { var params = []; nodes.forEach(function(node) { if(!node.name || !node.firstChild()) return; params.push([JSON.stringify(node.name.replace(/i18n:/, '')), toCommonNodes(node.children), 'PARAM']); }); params.push('PARAMS'); return params; } function _param(params) { return [JSON.stringify(params.text), 'PARAM-CALL']; } function _keyset(params) { // FIXME: get rid of Yandex.Tanker specifics return ((params.project && params.project === 'tanker') ? 'i-' + params.project + '__' : '') + params.keyset; }