Файлы:
validate-xml.js
format-xml.js
escape-xml.js
convert-xml-to-dom.js
convert-object-to-dom-xml.js
convert-dom-to-xml.js
convert-dom-xml-to-object.js
Файл validate-xml.js
function validateXML (xml, options) {
var xmlFragment = false; // false - проверяемый код это целый xml, true - проверяемый код не целый xml, а только его часть
if (options && options.hasOwnProperty('xmlFragment')) {
xmlFragment = options.xmlFragment;
}
var regTab = /[\n\t\r]+/g
, regCommentAndCdata = /<!(?:--(?:[^-]|-[^-])*--|\[CDATA\[(?:[^\]]|\][^\]]|\]+[^\>\]])*]{2,})>/g
, regInstruction = /<\?.*?\?>/
, regDocType = /<\!DocType.*?>/i
, regOutTagTextBegin = /^\s*[^<\s]+/
, regEntityFull = /&(?:#(?:x[a-f\d]{1,4}|\d{2,5})|[a-z][\w\-]*);/gi
, regAttribute = /(<[a-z_][\w:-]*)((?:\s+[a-z_][\w:-]*\s*=\s*(?:'[^<>']*'|"[^<>"]*"))*)\s*(\/?>)/gi
, regAttributeUnique = /([a-z_][\w:-]*)\s*=\s*(?:'[^<>']*'|"[^<>"]*")/gi
, regAttributeMatch = /[a-z_][\w:-]*/gi
, regSingleTag = /<[a-z_][\w:-]*\/>/gi
, regDoubleTag = /<([a-zA-Z_][\w:-]*)>[^<]*<\/\1\s*>/g;
if (xml) {
// Вырезаем табуляцию и переносы строк
xml = xml.replace(regTab, ' ');
// Вырезаем комментарии и CDATA
xml = xml.replace(regCommentAndCdata, '');
if (xml.indexOf('<!--') !== -1) {return false;}
if (xml.indexOf(']]>') !== -1) {return false;}
// Вырезаем инструкции
if (!xmlFragment) {
xml = xml.replace(regInstruction, '');
if (xml.search(regInstruction) !== -1) {return false;}
}
// Вырезаем DocType
if (!xmlFragment) {xml = xml.replace(regDocType, '');}
if (xml.search(regDocType) !== -1) {return false;}
// Ищем текст в начале и в конце строки, выходящий за пределы тегов
if (!xmlFragment) {
if (xml.search(regOutTagTextBegin) !== -1) {return false;}
// Конец строки
var valueLength = xml.length
, isSpace = true;
do {
valueLength--;
if (xml.charAt(valueLength) !== ' ') {isSpace = false;}
} while (isSpace && valueLength > 0);
if (!isSpace && xml.charAt(valueLength) !== '>') {
return false;
} else if (valueLength === 0) {
return false;
}
}
// Вырезаем Entities
xml = xml.replace(regEntityFull, '');
if (xml.indexOf('&') !== -1) {return false;}
// Вырезаем аттрибуты и проверяем на дублирование
var attributeUnique = true;
xml = xml.replace(regAttribute, function ($0, $1, $2 ,$3) {
$2 = $2.replace(regAttributeUnique, '$1');
var attribute = $2.match(regAttributeMatch);
if (attribute) {
var matchCount = attribute.length;
if (matchCount > 1) {
var i = 0
, j;
while (attributeUnique && i < matchCount - 1) {
j = i + 1;
while (attributeUnique && j < matchCount) {
if (attribute[i] !== attribute[j]) {
j++;
} else {
attributeUnique = false;
}
}
i++;
}
}
}
return $1 + $3;
});
if (!attributeUnique) {return false;}
// Параметр для вырезания тэгов
var tagReplaceTo = '';
if (!xmlFragment) {tagReplaceTo = '&';}
// Вырезаем одинарные тэги
xml = xml.replace(regSingleTag, tagReplaceTo);
// Вырезаем двойные тэги
var previousLen
, len = 0;
do {
previousLen = len;
xml = xml.replace(regDoubleTag, tagReplaceTo);
len = xml.length;
} while (len !== previousLen);
if (!xmlFragment) {
if (xml.indexOf(tagReplaceTo) !== xml.lastIndexOf(tagReplaceTo)) {return false;}
}
if (xml.indexOf('<') !== -1) {return false;}
return true;
} else { // Пустая строка вместо XML
if (!xmlFragment) {
return false;
} else {
return true;
}
}
}
// Test
var xmlString = '<a id="a"><b id="b">hey!</b></a>';
console.log(validateXML(xmlString, {xmlFragment: true})); // true | false
Файл format-xml.js
// Функция делает простое форматирование XML и
// удаляет перенос на другую строку внутри текста, расположенного между тэгами
function formatXML (xml) {
xml = String(xml); // привести к строке на случай, если xml - это объект
xml = xml.replace(/\r|\n/g, ''); // удалить уже существующие символы перехода на следующую строку \r и \n в том числе внутри текста, расположенного между тэгами
xml = xml.replace(/(>)\s*(<)(\/*)/g, '$1\r\n$2$3'); // удалить пробелы между тэгами (<tag> </tag>), заменив их на символы \r\n, итоговый результат: >\r\n</
var formattedXML = ''
, xmlParts = xml.split('\r\n') // разбить содержимое XML на части
, xmlPartsLength = xmlParts.length
, i
, j
, pad = 0
, indent
, padding;
for (i = 0; i < xmlPartsLength; i++) {
indent = 0;
if (xmlParts[i].match(/.+<\/\w[^>]*>$/)) {
// Пример: some text</tag>
// любой символ встречается 1 или более раз,
// за ним идут символы </ и далее одна буква,
// после этого любой набор символов, кроме символа >
// и в конце идет закрывающая скобка >
indent = 0;
} else if (xmlParts[i].match(/^<\/\w/)) {
// Пример: </a
// начинается с </ и одной буквы
if (pad !== 0) {
pad -= 1;
}
} else if (xmlParts[i].match( /^<\w[^>]*[^\/]>.*$/ )) {
// Пример: <tag>some text
// начинается с < и одной буквы,
// далее идет любой набор символов, кроме символа >,
// после чего идет любой набор символов, кроме символа /,
// затем идет символ >
// после этого идут любые сиволы до конца строки
indent = 1;
} else {
indent = 0;
}
padding = '';
for (j = 0; j < pad; j++) {
padding += ' '; // 4 пробела можно заменить на Tab: padding += '\t';
}
formattedXML += padding + xmlParts[i] + '\r\n';
pad += indent;
}
return formattedXML;
}
// Test
var xmlString = '<a id="a"><b id="b">hey!</b></a>';
console.log(formatXML(xmlString));
// Результ:
// <a id="a">
// <b id="b">hey!</b>
// </a>
// Функция делает более сложную проверку и форматирование XML и
// не удаляет перенос на другую строку внутри текста, расположенного между тэгами, оставляя его на разных строках
function alternativeFormatXML (xml) {
xml = String(xml); // привести к строке на случай, если xml - это объект
xml = xml.replace(/(>)\s*(<)(\/*)/g, '$1\n$2$3'); // удалить пробелы между тэгами (<tag> </tag>), заменив их на символ \n, итоговый результат: >\n</
xml = xml.replace(/ *(.*) +\n/g, '$1\n'); // вставить символ \n после последовательности | some text \n|, итоговый результат: | some text \n\n|
xml = xml.replace(/(<.+>)(.+\n)/g, '$1\n$2'); // вставить символ \n между тэгом и текстом, итоговый результат: <tag>\nsome text\n
var formattedXML = ''
, transitions = {// 4 типа тэгов: single, closing, opening, other (text, doctype, comment) - всего 4*4 = 16 вариантов transitions
'single->single': 0
, 'single->closing': -1
, 'single->opening': 0
, 'single->other': 0
, 'closing->single': 0
, 'closing->closing': -1
, 'closing->opening': 0
, 'closing->other': 0
, 'opening->single': 1
, 'opening->closing': 0
, 'opening->opening': 1
, 'opening->other': 1
, 'other->single': 0
, 'other->closing': -1
, 'other->opening': 0
, 'other->other': 0
}
, i
, j
, lines = xml.split('\n')
, linesLength = lines.length
, line
, type
, fromTo
, lastType = 'other'
, indent = 0
, padding;
for (i = 0; i < linesLength; i++) {
line = lines[i];
if (/<.+\/>/.test(line)) {type = 'single'; // эта линия содержит одиночный (single) тэг, например: <br />
} else if (/<\/.+>/.test(line)) {type = 'closing'; // эта линия содержит закрывающий (closing) тэг, например: </a>
} else if (/<[^!].*>/.test(line)) {type = 'opening'; // эта линия содержит открывающий (opening) тэг, но это не что-то вроде <!something>, например: <a>
} else {type = 'other'; // эта линия содержит text или тэг doctype, comment, например: <!--
}
fromTo = lastType + '->' + type;
lastType = type;
indent += transitions[fromTo];
padding = '';
for (j = 0; j < indent; j++) {
padding += ' '; // 4 пробела можно заменить на Tab: padding += '\t';
}
if (fromTo === 'opening->closing') {
formattedXML = formattedXML.substr(0, formattedXML.length - 1) + line + '\n'; // substr() удаляет разрыв строки (\n) оставшийся от предыдущего цикла
} else {
formattedXML += padding + line + '\n';
}
}
return formattedXML;
}
// Test
var xmlString = '<a id="a"><b id="b">h\ne\ny\n!</b></a>';
console.log(alternativeFormatXML(xmlString));
// Результ:
// <a id="a">
// <b id="b">
// h
// e
// y
// !</b>
// </a>
Файл escape-xml.js
function escapeXML (xml) {
// Замена символов: &, <, >, \n и пробелы на escape-последовательности
return xml.replace(/&/g,'&')
.replace(/</g,'<')
.replace(/>/g,'>')
.replace(/ /g, ' ')
.replace(/\n/g,'<br />');
}
// Test
var xmlString = '<a id="a"><b id="b">hey!</b></a>';
console.log(escapeXML(xmlString));
Файл convert-xml-to-dom.js
function convertXMLtoDOM (data) {
if (typeof data !== 'string') {throw new Error('XML must be a string.');}
if (data === '') {throw new Error('XML can\'t be an empty string.');}
var xmlDOM;
try {
if (window.DOMParser) { // Standard
xmlDOM = new DOMParser().parseFromString(data, 'text/xml');
} else { // IE
xmlDOM = new ActiveXObject('Microsoft.XMLDOM');
xmlDOM.async = 'false';
xmlDOM.loadXML(data);
}
} catch (e) {
xmlDOM = undefined;
}
if (
!xmlDOM
|| !xmlDOM.documentElement
|| xmlDOM.getElementsByTagName('parsererror').length
) {
throw new Error('Invalid XML: ' + data);
}
return xmlDOM;
}
// Test
var xmlString = '<a id="a"><b id="b">hey!</b></a>'
, xmlDOM = convertXMLtoDOM(xmlString);
console.log(xmlDOM.documentElement.nodeName); // Root element
Файл convert-object-to-dom-xml.js
// Create XML from Object
function createXML (oObjTree) {
function loadObjTree (oParentEl, oParentObj) {
var vValue, oChild;
if (oParentObj.constructor === String || oParentObj.constructor === Number || oParentObj.constructor === Boolean) {
oParentEl.appendChild(oNewDoc.createTextNode(oParentObj.toString())); /* verbosity level is 0 or 1 */
if (oParentObj === oParentObj.valueOf()) { return; }
} else if (oParentObj.constructor === Date) {
oParentEl.appendChild(oNewDoc.createTextNode(oParentObj.toGMTString()));
}
for (var sName in oParentObj) {
if (isFinite(sName)) { continue; } /* verbosity level is 0 */
vValue = oParentObj[sName];
if (sName === "keyValue") {
if (vValue !== null && vValue !== true) { oParentEl.appendChild(oNewDoc.createTextNode(vValue.constructor === Date ? vValue.toGMTString() : String(vValue))); }
} else if (sName === "keyAttributes") { /* verbosity level is 3 */
for (var sAttrib in vValue) { oParentEl.setAttribute(sAttrib, vValue[sAttrib]); }
} else if (sName.charAt(0) === "@") {
oParentEl.setAttribute(sName.slice(1), vValue);
} else if (vValue.constructor === Array) {
for (var nItem = 0; nItem < vValue.length; nItem++) {
oChild = oNewDoc.createElement(sName);
loadObjTree(oChild, vValue[nItem]);
oParentEl.appendChild(oChild);
}
} else {
oChild = oNewDoc.createElement(sName);
if (vValue instanceof Object) {
loadObjTree(oChild, vValue);
} else if (vValue !== null && vValue !== true) {
oChild.appendChild(oNewDoc.createTextNode(vValue.toString()));
}
oParentEl.appendChild(oChild);
}
}
}
const oNewDoc = document.implementation.createDocument("", "", null);
loadObjTree(oNewDoc, oObjTree);
return oNewDoc;
}
var newDoc = createXML(myObject);
console.log((new XMLSerializer()).serializeToString(newDoc));
Файл convert-dom-to-xml.js
function convertDOMtoXML (domElement) {
var serializer;
if (window.XMLSerializer) {
serializer = new XMLSerializer();
} else {
throw new Error('No XML serializer found.');
}
return serializer.serializeToString(domElement);
}
// Test
console.log(convertDOMtoXML(document));
var inputElement = document.createElement('input');
console.log(convertDOMtoXML(inputElement));
Файл convert-dom-xml-to-object.js
function convertDomXMLtoObject (domXMLelement) {
var object = {};
if (domXMLelement.nodeType === 1) { // element
// Attributes
if (domXMLelement.attributes.length > 0) {
object['@attributes'] = {};
var attribute;
for (var i = 0, len = domXMLelement.attributes.length; i < len; i++) {
attribute = domXMLelement.attributes.item(i);
object['@attributes'][attribute.nodeName] = attribute.nodeValue; // пример: @attributes = {class: 'menu'}
}
}
} else if (domXMLelement.nodeType === 3) { // text
// Text
object = domXMLelement.nodeValue;
}
// Children
if (domXMLelement.hasChildNodes()) {
var item
, nodeName;
for(var j = 0, len = domXMLelement.childNodes.length; j < len; j++) {
item = domXMLelement.childNodes.item(j);
nodeName = item.nodeName;
if (typeof object[nodeName] === 'undefined') {
object[nodeName] = convertDomXMLtoObject(item);
} else {
if (typeof object[nodeName].push === 'undefined') {
object[nodeName] = [];
var old = object[nodeName];
object[nodeName].push(old);
}
object[nodeName].push(convertDomXMLtoObject(item));
}
}
}
return object;
}
// Test
console.log(convertDomXMLtoObject(document));
Комментариев нет:
Отправить комментарий