sbis3-node-ws
Version:
Модуль позволяет использовать ядро интерфейсного фреймворка sbis3-ws(`core.js`) и модуль работы с данными (`Source.js`) в приложении на nodejs.
444 lines (386 loc) • 14.7 kB
JavaScript
/**
* 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, '"') + 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 = /"/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
};
});