UNPKG

sbis3-node-ws

Version:

Модуль позволяет использовать ядро интерфейсного фреймворка sbis3-ws(`core.js`) и модуль работы с данными (`Source.js`) в приложении на nodejs.

444 lines (386 loc) 14.7 kB
/** * Created by Яковлев В.В. on 17.04.14. */ define('Core/ParserUtilities', function(){ "use strict"; /** * Минимальный вариант node * @class */ var Node = function(cfg){ this.startTag = cfg.startTag || ''; this.closeTag = ''; this.nodeType = cfg.nodeType; this.nodeName = cfg.nodeName; this.attributes = cfg.attributes || []; this.childNodes = cfg.childNodes; this.parentNode = cfg.parentNode; this.text = cfg.text; this._sequence = cfg._sequence || []; this._junk = cfg._junk || []; }; /** * Получить значение атрибута * @return {String} */ Node.prototype.getAttribute = function(attributeName){ for (var i = 0 , l = this.attributes.length; i < l; i++){ if (this.attributes[i].name == attributeName){ return this.attributes[i].value; } } return null; }; Node.prototype.setAttribute = function(attributeName, attributeValue){ var attr = $ws.helpers.find(this.attributes, function(attr){ return attr.name === attributeName; }); attributeValue = '' + attributeValue; if (attr) { attr.value = attributeValue; } else { this.attributes.push({ name: attributeName, value: attributeValue, _quotes: '"' }); this._sequence.push('a'); } }; /** * Получить/установить внутренний контент * Если есть параметр, то устанавливаем контент * @param {String} [] */ Node.prototype.innerHTML = function(content) { var result = '', cNode; // Это сеттер if (content !== undefined) { if (typeof(content) !== 'string') { throw new Error('Invalid parameter format'); } if (this.nodeType !== 1) { throw new Error('Only element node can change the innerHTML property'); } result = content; cNode = parse(result); this.childNodes = cNode.childNodes; return result; } // А это геттер if (this.nodeType === 3) { result += this.text; } else { for (var i = 0, l = this.childNodes.length; i < l; i++) { cNode = this.childNodes[i]; result += cNode.nodeType === 3 ? cNode.text : cNode.outerHTML(); } } return result; }; /** * Получить контент ноды текстом */ Node.prototype.outerHTML = function() { var startTag = '', closeTag = ''; if (this.nodeType == 1) { closeTag = this.closeTag ? ('</' + this.nodeName + '>') : ''; startTag += '<' + this.nodeName; $ws.helpers.reduce(this._sequence, function (state, seq) { var attr, usedQuote; if (seq == 'a') { attr = this.attributes[++state.attrIdx]; if (attr) { usedQuote = attr._quotes && attr._quotes[0] || ''; startTag += ' ' + attr.name + (attr.value ? '=' + usedQuote + (attr.value + '').replace(quotes, '&quot;') + usedQuote : ''); } } else { startTag += this._junk[++state.junkIdx]; } return state; }, {attrIdx: -1, junkIdx: -1}, this); startTag += this.startTag.indexOf('/>') != -1 ? '/>' : '>'; } return startTag + this.innerHTML() + closeTag; }; /** * Получить дочерний элемент по имени тега */ Node.prototype.getElementsByTagName = function(tagName) { var result = []; if (this.nodeType !== 3) { for (var i = 0, l = this.childNodes.length; i < l; i++) { if (this.childNodes[i].nodeName == tagName) { result.push(this.childNodes[i]); } result = result.concat(this.childNodes[i].getElementsByTagName(tagName)); } } return result; }; var voidElements = /^<(area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)/i, // rawTextElements + escapeableRawTextElement - https://html.spec.whatwg.org/multipage/syntax.html#elements-2 rawTextElements = /^<(script|style|textarea|title)/i, tagRegExp = /(<\/?[a-z]\w*(?:\s*(?:[\w\-:]+|{{[\s\S]+?}})(?:\s*=\s*(?:"[\s\S]*?"|'[\s\S]*?'|[a-z0-9.:_-]+))?)*\s*\/?>)|([^<]|<(?![a-z\/]))*/gi, attrRegExp = /\s[a-z0-9-_:]+(?:(=(?:"[\s\S]*?"|'[\s\S]*?'|[a-z0-9.:_-]+))|(?:\b(?=\s|\/|>)))/gi, startTag = /^<[a-z]/, selfClose = /\/>$/, closeTag = /^<\//, nodeName = /<\/?([a-z][a-z0-9]*)/i, attributeQuotes = /^('|")|('|")$/g, reFirstQuote = /^('|")/, conditionalComments = /<!--(\[[\s\S]*?\])-->/gi, conditionalCommentsReplacements = /<!--\/CC#(\d+)-->/gi, wsExpertComments = /<!--WS-EXPERT|WS-EXPERT-->/g, htmlComments = /<!--(?:[^\[\/][\s\S]*?)?-->/gi, quotesEncoded = /&quot;/g, quotes = /"/g; /** * Удалить условный комментарий * @param {String} markup - разметка * @param {Array} store - хранилище для комментариев * @returns {String} */ function removeConditionalComments(markup, store) { return markup.replace(conditionalComments, function(str, commentContent) { store.push(commentContent); return '<!--/CC#' + (store.length - 1) + '-->'; }); } /** * Восстановить условынй комментарий * @param {String} markup - разметка * @param {Array} store - хранилище для комментариев * @returns {String} */ function restoreConditionalComments(markup, store) { return markup.replace(conditionalCommentsReplacements, function(str, commentID) { return '<!--' + store[+commentID] + '-->'; }); } /** * Для каждого найденного по имени тега вызывает функцию * @param {String} markup - разметка * @param {String} tagname - название тега * @param {Function} fn - callback. Стреляет для каждого найденого тега * @returns {String|Array} * @private */ function _replaceTags(markup, tagname, fn) { var sTag = new RegExp('^<' + tagname), eTag = new RegExp('^<\\/'+ tagname +'\\s*>'), result = [], componentStr = [], tagsCount = 0, ccStore = [], tags, tag; markup = markup.replace(wsExpertComments, '').replace(htmlComments, ''); markup = removeConditionalComments(markup, ccStore); tags = markup.match(tagRegExp); function closeTag() { tagsCount--; if (tagsCount === 0) { result.push(fn(componentStr.join(''))); componentStr.length = 0; } } for (var i = 0, l = tags.length; i < l; i++) { tag = tags[i]; if (sTag.test(tag)) { tagsCount++; componentStr.push(tag); if (selfClose.test(tag) || voidElements.test(tag)) { closeTag(); } } else if (eTag.test(tag)) { componentStr.push(tag); closeTag(); } else { if (componentStr.length) { componentStr.push(tag); } else { result.push(tag); } } } return restoreConditionalComments(result.join(''), ccStore); } /** * Найти все теги "component" и если надо можно их заменить * @param {String} markup - разметка * @param {Function} fn - функция, в которую придет найденный тег строкой * @deprecated Используйте mapTag * @returns {String} */ function replaceComponents(markup, fn) { $ws.single.ioc.resolve('ILogger').log("ParserUtilities", "Метод replaceComponents помечен как deprecated и будет удален с 3.8. Используйте mapTag."); return mapTag('component', markup, function(elem, tagStr) { return fn(tagStr); }); } /** * Распарсить разметку. Получим ноду, где разметка будет дочерними элементами * @param {String} markup - разметка * @returns {Node} */ function parse(markup) { var tags = markup instanceof Array ? markup : markup.replace(wsExpertComments, '').replace(htmlComments, '').match(tagRegExp), result = new Node({ childNodes: [], parentNode: null }), buffer, currentObject = result, tag, firstQuote, attrName, attrValue, attrBuffer = [], attrStr = [], attributes, isSelfClosed, nodeNameParts, parseStart, sequence, junks, junk; for (var i = 0, l = tags.length; i < l; i++) { tag = tags[i]; if (!tag) { continue; } // Обработка элементов, в которых может быть только текст if (currentObject && rawTextElements.test(currentObject.startTag)) { if (closeTag.test(tag) && tag.match(nodeName)[1] == currentObject.nodeName) { // Если это наш закрывающий тэг то идем в обычный разбор } else { currentObject.childNodes.push(new Node({ nodeType: 3, text: tag, parentNode: currentObject })); continue; } } if (startTag.test(tag)) { attributes = []; isSelfClosed = tag.indexOf('/>') !== -1; nodeNameParts = tag.match(nodeName); parseStart = nodeNameParts[0].length; sequence = []; junks = []; attrRegExp.lastIndex = parseStart; while((attrStr = attrRegExp.exec(tag)) !== null) { junk = tag.substring(parseStart, attrStr.index); parseStart = attrRegExp.lastIndex; if (junk && junk.trim()) { junks.push(junk); sequence.push('j'); } attrBuffer = attrStr[0].split('='); attrName = String.trim(attrBuffer.shift()); attrValue = attrBuffer.join('='); firstQuote = attrValue.match(reFirstQuote); attributes.push({ name: attrName, value: (attrValue || '').replace(attributeQuotes, '').replace(quotesEncoded, '"'), _quotes: firstQuote && firstQuote[0] }); sequence.push('a'); } junk = tag.substring(parseStart, tag.length - (isSelfClosed ? 2 : 1)); if (junk && junk.trim()) { junks.push(junk); sequence.push('j'); } currentObject.childNodes.push(buffer = new Node({ nodeType: 1, //element node nodeName : nodeNameParts[1], attributes: attributes, _junk: junks, _sequence: sequence, childNodes: [], parentNode: currentObject, startTag: tag })); if (!selfClose.test(tag)) { currentObject = buffer; } } else if (closeTag.test(tag)) { currentObject.closeTag = tag; currentObject = currentObject.parentNode; } else { currentObject.childNodes.push(new Node({ nodeType: 3, text: tag, parentNode: currentObject })); } } return result; } /** * forEach. Для каждого тега (верхний уровень иерархии) вызовем функцию * @param {String} tagname * @param {String} markup * @param {Function} fn - function(String tag) */ function forEachTag(tagname, markup, fn) { _replaceTags(markup, tagname, function(tagStr) { var elem = parse(tagStr); for (var cI = 0, cL = elem.childNodes.length; cI < cL; cI++) { if (elem.childNodes[cI].nodeType == 1) { elem = elem.childNodes[cI]; break; } } fn(elem, tagStr); return tagStr; }); } /** * map. Для каждого тега (верхний уровень иерархии) вызовем функцию, * результат выполнения функции заменит тег * @param {String} tagname - название тега * @param {String} markup - разметка * @param {Function} fn - function(Node tag, String tag) * @returns {String} */ function mapTag(tagname, markup, fn) { return _replaceTags(markup, tagname, function(tagStr) { var elem = parse(tagStr); for (var cI = 0, cL = elem.childNodes.length; cI < cL; cI++) { if (elem.childNodes[cI].nodeType == 1) { elem = elem.childNodes[cI]; break; } } return fn(elem, tagStr); }); } /** * filter. Для каждого тега (верхний уровень иерархии) вызовем функцию, * если вернулось true, то оставим тег, иначе выкинем * @param {String} tagname - название тега * @param {String} markup - разметка * @param {Function} fn - function(Node tag, String tag) * @returns {String} */ function filterTag(tagname, markup, fn) { return _replaceTags(markup, tagname, function(tagStr) { var elem = parse(tagStr); for (var cI = 0, cL = elem.childNodes.length; cI < cL; cI++) { if (elem.childNodes[cI].nodeType == 1) { elem = elem.childNodes[cI]; break; } } return fn(elem, tagStr) ? tagStr : ''; }); } return { parse: parse, replaceComponents: replaceComponents, forEachTag: forEachTag, mapTag: mapTag, filterTag: filterTag }; });