686 lines
26 KiB
JavaScript
686 lines
26 KiB
JavaScript
/*
|
||
* (c) Copyright Ascensio System SIA 2010-2023
|
||
*
|
||
* This program is a free software product. You can redistribute it and/or
|
||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
||
* version 3 as published by the Free Software Foundation. In accordance with
|
||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||
* of any third-party rights.
|
||
*
|
||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||
*
|
||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||
* street, Riga, Latvia, EU, LV-1050.
|
||
*
|
||
* The interactive user interfaces in modified source and object code versions
|
||
* of the Program must display Appropriate Legal Notices, as required under
|
||
* Section 5 of the GNU AGPL version 3.
|
||
*
|
||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||
* grant you any rights under trademark law for use of our trademarks.
|
||
*
|
||
* All the Product's GUI elements, including illustrations and icon sets, as
|
||
* well as technical writing content are licensed under the terms of the
|
||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||
*
|
||
*/
|
||
|
||
//for zlib async loading
|
||
QUnit.config.autostart = false;
|
||
$(function() {
|
||
const api = new Asc.VisioEditorApi({
|
||
'id-view': 'editor_sdk'
|
||
});
|
||
AscCommon.g_oTableId.init();
|
||
let memory = new AscCommon.CMemory();
|
||
memory.SetXmlAttributeQuote(0x27);
|
||
|
||
testFileSerizlizeUseParseCompare.skip = function testFileSkip(fileName, base64, ignoreFolders, ignoreFiles, ignoredTags, ignoredAttributes, downloadFile) {
|
||
this(fileName, base64, ignoreFolders, ignoreFiles, ignoredTags, ignoredAttributes, downloadFile, true);
|
||
}
|
||
|
||
// If test fails with error
|
||
// TypeError: Cannot read properties of null (reading 'toXml')
|
||
// then something with api saveDocumentToZip
|
||
// try to reload sax-serialize page and select one module
|
||
|
||
//todo events
|
||
setTimeout(startTests, 3000);
|
||
|
||
function startTests() {
|
||
api.InitEditor();
|
||
|
||
QUnit.start();
|
||
|
||
QUnit.module("Test main functions");
|
||
|
||
QUnit.test("Test api.OpenDocumentFromZip", function (assert)
|
||
{
|
||
const api = new Asc.VisioEditorApi({'id-view': 'editor_sdk'});
|
||
api.InitEditor();
|
||
// get Uint8Array
|
||
let vsdx = AscCommon.Base64.decode(Asc.exampleVsdx);
|
||
const openRes = api.OpenDocumentFromZip(vsdx);
|
||
assert.strictEqual(openRes, true, "Check OpenDocumentFromZip");
|
||
});
|
||
|
||
QUnit.test("Check api.saveDocumentToZip", function (assert)
|
||
{
|
||
// Read and parse vsdx file
|
||
const api = new Asc.VisioEditorApi({'id-view': 'editor_sdk'});
|
||
api.InitEditor();
|
||
let vsdx = AscCommon.Base64.decode(Asc.exampleVsdx);
|
||
const openRes = api.OpenDocumentFromZip(vsdx);
|
||
|
||
// Creating .vsdx from api and get Uint8Array to data variable
|
||
api.saveDocumentToZip(api.Document, AscCommon.c_oEditorId.Visio, function (data) {
|
||
if (data) {
|
||
assert.strictEqual(Boolean(data), true, "saveDocumentToZip returned data");
|
||
}
|
||
});
|
||
});
|
||
|
||
QUnit.test("Compare files count", function (assert)
|
||
{
|
||
// Read and parse vsdx file
|
||
const api = new Asc.VisioEditorApi({'id-view': 'editor_sdk'});
|
||
api.InitEditor();
|
||
let vsdx = AscCommon.Base64.decode(Asc.exampleVsdx);
|
||
const openRes = api.OpenDocumentFromZip(vsdx);
|
||
|
||
// Creating .vsdx and get Uint8Array to data variable
|
||
api.saveDocumentToZip(api.Document, AscCommon.c_oEditorId.Visio, function (data) {
|
||
if (data) {
|
||
// Read and parse vsdx file
|
||
const api2 = new Asc.VisioEditorApi({'id-view': 'editor_sdk'});
|
||
api2.InitEditor();
|
||
|
||
let jsZlib = new AscCommon.ZLib();
|
||
jsZlib.open(data);
|
||
|
||
assert.strictEqual(jsZlib.files.length, 23, "Parsed vsdx contains 23 xml files like initial vsdx. Files Count correctly.");
|
||
|
||
const openRes2 = api2.OpenDocumentFromZip(data);
|
||
|
||
} else {
|
||
return false;
|
||
}
|
||
});
|
||
});
|
||
|
||
// File contents (strings) before and after parse are the same but
|
||
|
||
// Exceptions:
|
||
// comments, schema tags - schema namespace, schema location,
|
||
// hidden characters: /n/t,
|
||
// quotes: double or single,
|
||
// xml declaration: UTF and utf,
|
||
// attributes order (order set like in MS but in generated file with XMLSpy and Liquid studio they are different),
|
||
// ignoring long fractional numbers eg 3.1415926535897900073734945 cuts to 3.14159265358979,
|
||
// tag auto-generated_for_wildcard,
|
||
// files SolutionXML, SolutionsXML, Validation
|
||
// and attributes and tags with any type
|
||
|
||
// in:
|
||
// <Cell N='AutoGen' U='string' E='string' F='string' V='string'>
|
||
// <RefBy T='string' ID='4730' />
|
||
// <RefBy T='string' ID='6554' />
|
||
// <RefBy T='string' ID='6015' />
|
||
// </Cell>
|
||
// line drop doesn't exist in this situation in real file. And Cell handles only one line drop
|
||
// because it is considered as text content.
|
||
|
||
// Only .xml check no .rels check or embeddings
|
||
let ignoreFiles = ["to prepare file check.txt", "theme1.xml"];
|
||
// not used anymore
|
||
// let ignoreQuotes = true;
|
||
|
||
QUnit.module("Test xml serialize use strings compare Altova generated");
|
||
|
||
testXmlFilesArchieve(Asc.altovaXml, ignoreFiles);
|
||
|
||
QUnit.module("Test xml serialize use strings compare real file");
|
||
|
||
testXmlFilesArchieve(Asc.timelineDiagrammVisioSpecificXmlOnly, ignoreFiles);
|
||
|
||
QUnit.module("Test file serialize use parse compare");
|
||
|
||
let ignoredFolders = ["docProps"];
|
||
// ignore some files related to embedded files. Embedded files not yet needed
|
||
// note: there may be missing rels beacause they are related to embedded files
|
||
ignoredFolders = ignoredFolders.concat(["media", "embeddings"]);
|
||
|
||
// In theme1.xml after parse lost <a:extLst>, <a:font> - fonts for each language only main fonts remain,
|
||
// missing <a:tileRect> and <a:effectStyle>, some effectStyles are applied (e.g. shadows)
|
||
// Maybe only theme1.xml is parsed - check it
|
||
|
||
// page[N].xml.rels - for when pages sets rels to masters
|
||
// в MS если page[N] ссылается на хотя бы один мастер то в рельсы страницы добавляются все мастеры.
|
||
// Сейчас для каждой страницы добавляются ссылки на все маcтеры без проверки
|
||
|
||
// recordset[N].xml - not realized
|
||
|
||
// master[N].xml.rels - because of rels to embedded
|
||
let ignoredFiles = ["page[N].xml.rels", "recordset[N].xml", "recordsets.xml.rels", "master[N].xml.rels"];
|
||
|
||
// Remove elements(tags) with ignoredTagsExistence from extraElements or missingElements
|
||
// So they are not considered in test result but still their children compared
|
||
|
||
// Remove Shapes because in random generated file there may be empty Shapes tag
|
||
// when in original file there was no Shapes tag
|
||
let ignoredTagsExistence = ["Shapes"];
|
||
|
||
let ignoredAttributes = ["xsi:schemaLocation", "xmlns:xsi", "xmlns", "xmlns:r", "xml:space"];
|
||
|
||
// tags order and tag values are not compared - no such functionality for now
|
||
|
||
testFileSerizlizeUseParseCompare("Example vsdx", Asc.exampleVsdx, ignoredFolders, ignoredFiles, ignoredTagsExistence, ignoredAttributes, false);
|
||
testFileSerizlizeUseParseCompare("Basic ShapesA_start", Asc.BasicShapesA_start, ignoredFolders, ignoredFiles, ignoredTagsExistence, ignoredAttributes, false);
|
||
testFileSerizlizeUseParseCompare("generatedVsdx2schema", Asc.generatedVsdx2schema, ignoredFolders, ignoredFiles, ignoredTagsExistence, ignoredAttributes, false);
|
||
testFileSerizlizeUseParseCompare("Timeline_diagram_start", Asc.Timeline_diagram_start, ignoredFolders, ignoredFiles, ignoredTagsExistence, ignoredAttributes, false);
|
||
testFileSerizlizeUseParseCompare("rows_test", Asc.rows_test, ignoredFolders, ignoredFiles, ignoredTagsExistence, ignoredAttributes, false);
|
||
|
||
// Some errors still there in module below
|
||
QUnit.module("Comparing many files use parse compare");
|
||
|
||
for (let key in Asc) {
|
||
if (key.startsWith('test_file')) {
|
||
let fileBase64 = Asc[key];
|
||
testFileSerizlizeUseParseCompare(key, fileBase64, ignoredFolders, ignoredFiles, ignoredTagsExistence, ignoredAttributes, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
function testXmlFilesArchieve(base64zip, ignoreFiles) {
|
||
let generatedXmlFiles = AscCommon.Base64.decode(base64zip);
|
||
|
||
let jsZlibOriginal = new AscCommon.ZLib();
|
||
jsZlibOriginal.open(generatedXmlFiles);
|
||
let originalFiles = jsZlibOriginal.files;
|
||
|
||
originalFiles.forEach(function(originalFilePath) {
|
||
// not used anymore
|
||
// let doRemoveLineBreaks = false;
|
||
if (!isFilePathIgnored(originalFilePath, ignoreFiles)) {
|
||
let fileUint8 = jsZlibOriginal.getFile(originalFilePath);
|
||
let fileContent = AscCommon.UTF8ArrayToString(fileUint8, 0, fileUint8.length);
|
||
|
||
let testName = "Compare " + originalFilePath;
|
||
|
||
// className === C + rootTagName(first tag name)
|
||
let rootTagNameMatchResult = fileContent.match(/<(\w+:)?(\w+)/);
|
||
if (rootTagNameMatchResult) {
|
||
let rootTagName = rootTagNameMatchResult[2];
|
||
let rootTagNameCapitalized = rootTagName.charAt(0).toUpperCase() + rootTagName.slice(1);
|
||
let componentClassName = "C" + rootTagNameCapitalized;
|
||
let componentClass = AscVisio[componentClassName];
|
||
if (componentClass) {
|
||
let componentObject = new componentClass(api);
|
||
QUnit.test(testName, function (assert) {
|
||
testFileSerializeUseStringsCompare(assert, componentObject, fileContent);
|
||
});
|
||
} else {
|
||
QUnit.test(testName, function (assert)
|
||
{
|
||
QUnit.assert.ok(componentClass, "Class for parse not found");
|
||
});
|
||
}
|
||
} else {
|
||
QUnit.test(testName, function (assert)
|
||
{
|
||
QUnit.assert.ok(false, "rootTagNameMatchResult is false");
|
||
});
|
||
}
|
||
} else {
|
||
QUnit.test(originalFilePath + " is ignored", function (assert)
|
||
{
|
||
QUnit.assert.ok(true, originalFilePath + " is ignored");
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
function testFileSerizlizeUseParseCompare(fileName, base64, ignoreFolders, ignoreFiles, ignoredTagsExistence, ignoredAttributes, downloadFile, skip) {
|
||
let testFunction = skip ? QUnit.test.skip : QUnit.test;
|
||
testFunction('File ' + fileName, function (assert)
|
||
{
|
||
// Read and parse vsdx file
|
||
const api = new Asc.VisioEditorApi({'id-view': 'editor_sdk'});
|
||
api.InitEditor();
|
||
let vsdxOriginal = AscCommon.Base64.decode(base64);
|
||
// parse
|
||
const openResOriginal = api.OpenDocumentFromZip(vsdxOriginal);
|
||
|
||
let jsZlibOriginal = new AscCommon.ZLib();
|
||
jsZlibOriginal.open(vsdxOriginal);
|
||
let originalFiles = jsZlibOriginal.files;
|
||
|
||
// serialize
|
||
api.saveDocumentToZip(api.Document, AscCommon.c_oEditorId.Visio, function (data) {
|
||
if (data) {
|
||
if(downloadFile) {
|
||
AscCommon.DownloadFileFromBytes(data, fileName, AscCommon.openXml.GetMimeType("vsdx"));
|
||
}
|
||
|
||
// Read and parse custom vsdx file
|
||
// TODO try to parse again custom file
|
||
// const api2 = new Asc.VisioEditorApi({'id-view': 'editor_sdk'});
|
||
// api2.InitEditor();
|
||
// const openResCustom = api2.OpenDocumentFromZip(data);
|
||
|
||
let jsZlibCustom = new AscCommon.ZLib();
|
||
jsZlibCustom.open(data);
|
||
let customFiles = jsZlibCustom.files;
|
||
|
||
let exceptionsMessage = format('Ignoring:\nFolders: %s\nFiles: %s\nTags: %s\nAttributes: %s',
|
||
ignoreFolders.join(', '), ignoreFiles.join(', '), ignoredTagsExistence.join(', '), ignoredAttributes.join(', '));
|
||
// \n doesnt work in success assert to split
|
||
exceptionsMessage.split('\n').forEach(function(line) { return assert.ok(true, line);})
|
||
|
||
originalFiles = originalFiles.filter(function (path) {
|
||
let folderIgnored = pathCheckFolderPresence(path, ignoreFolders);
|
||
let fileIgnored = isFilePathIgnored(path, ignoreFiles);
|
||
return !folderIgnored && !fileIgnored;
|
||
});
|
||
customFiles = customFiles.filter(function (path) {
|
||
let folderIgnored = pathCheckFolderPresence(path, ignoreFolders);
|
||
let fileIgnored = isFilePathIgnored(path, ignoreFiles);
|
||
return !folderIgnored && !fileIgnored;
|
||
});
|
||
|
||
assert.strictEqual(customFiles.length, originalFiles.length, "Parsed vsdx contains as many xml files as initial vsdx");
|
||
|
||
originalFiles = originalFiles.sort( function(a, b) { return a.localeCompare(b);});
|
||
customFiles = customFiles.sort( function(a, b) { return a.localeCompare(b);});
|
||
|
||
assert.deepEqual(customFiles, originalFiles, 'Original vsdx has the same file structire as custom vsdx');
|
||
|
||
let docOriginal = new AscCommon.openXml.OpenXmlPackage(jsZlibOriginal, null);
|
||
let docCustom = new AscCommon.openXml.OpenXmlPackage(jsZlibCustom, null);
|
||
for (let i = 0; i < originalFiles.length; i++) {
|
||
let path = originalFiles[i];
|
||
if (originalFiles[i].includes('/')) {
|
||
path = "/" + path;
|
||
}
|
||
|
||
let contentOriginal = docOriginal.getPartByUri(path).getDocumentContent();
|
||
|
||
let contentCustom;
|
||
try {
|
||
contentCustom = docCustom.getPartByUri(path).getDocumentContent();
|
||
} catch (exception) {
|
||
// check if TypeError
|
||
if (exception.name === "TypeError") {
|
||
assert.strictEqual("Cant read file", "File read", format(
|
||
"Checking %s failed. Cant read custom file, check files count and file structure checks.",
|
||
path));
|
||
continue;
|
||
}
|
||
}
|
||
contentOriginal = contentOriginal.trim();
|
||
contentCustom = contentCustom.trim();
|
||
|
||
// global js API DOMParser
|
||
const domParser = new DOMParser();
|
||
let fileDomOriginal = domParser.parseFromString(contentOriginal, "application/xml");
|
||
let fileDomCustom = domParser.parseFromString(contentCustom, "application/xml");
|
||
|
||
let compareResult = compareDOMs(fileDomOriginal, fileDomCustom);
|
||
|
||
let compareResultIgnoredTags = getCompareResultIgnoredTagsExistence(compareResult, ignoredTagsExistence);
|
||
let compareResultIgnoredAttributes = getCompareResultIgnoredAttributes(compareResultIgnoredTags, ignoredAttributes);
|
||
|
||
let differences = compareResultIgnoredAttributes.filter(function (compareObject) {
|
||
let attrDifs = compareObject.differencesInAttributes;
|
||
let wrongAttributes = attrDifs.wrongValuesAttributePairs.length ||
|
||
attrDifs.extraAttributes.length || attrDifs.missingAttributes.length;
|
||
return compareObject.missingElements.length || compareObject.extraElements.length || wrongAttributes;
|
||
});
|
||
|
||
let message = '';
|
||
if (differences.length === 0) {
|
||
message = format('Checking %s was successful. Files are equal.', path);
|
||
} else {
|
||
let differencesString = differencesToString(differences);
|
||
message = format('Checking %s was not successful.\nDifferences:\n%s', path, differencesString);
|
||
}
|
||
|
||
assert.strictEqual(differences.length, 0, message);
|
||
}
|
||
} else {
|
||
return false;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
function addLineBreaks(xmlString) {
|
||
// bad line breaks
|
||
for (let i = 0; i < 2; i++) {
|
||
xmlString = xmlString.replaceAll(/(<\w.+?)<(\w)/g, '$1\n<$2');
|
||
}
|
||
xmlString = xmlString.replaceAll('></', '>\n</');
|
||
return xmlString;
|
||
}
|
||
|
||
function removeLineBreaks(string) {
|
||
string = string.replaceAll(/[\r\n]+/g, '');
|
||
string = addLineBreaks(string);
|
||
return string;
|
||
}
|
||
|
||
function findFirstDifference(str1, str2) {
|
||
const minLength = Math.min(str1.length, str2.length);
|
||
|
||
for (let i = 0; i < minLength; i++) {
|
||
if (str1[i] !== str2[i]) {
|
||
return `Difference found at position ${i}: char ${str1.charCodeAt(i)} !== char ${str2.charCodeAt(i)}`;
|
||
}
|
||
}
|
||
|
||
if (str1.length !== str2.length) {
|
||
const longerStr = str1.length > str2.length ? str1 : str2;
|
||
return `Strings have different lengths. First difference found at position ${minLength}: ${longerStr[minLength]}`;
|
||
}
|
||
|
||
return "No difference found";
|
||
}
|
||
|
||
function testFileSerializeUseStringsCompare(assert, serializeObj, expecteedXml) {
|
||
//fromXml
|
||
let context = new AscCommon.XmlParserContext();
|
||
let zip = new AscCommon.ZLib();
|
||
let rels = new AscCommon.openXml.OpenXmlPackage(zip, null);
|
||
let reader = new StaxParser(expecteedXml, rels, context);
|
||
serializeObj.fromXml(reader);
|
||
|
||
//toXml
|
||
memory.Seek(0);
|
||
memory.context = new AscCommon.XmlWriterContext();
|
||
memory.context.clearCurrentPartDataMaps();
|
||
context.document = serializeObj;
|
||
let filePart = new AscCommon.openXml.OpenXmlPackage(zip, memory);
|
||
let data = filePart.getXmlBytes(this, serializeObj, memory);
|
||
let content = AscCommon.UTF8ArrayToString(data, 0, data.length);
|
||
|
||
//compare
|
||
let expectedContent = expecteedXml;
|
||
let resultContent = content;
|
||
let message = `Comparing ${serializeObj.constructor.name} xml stings. ${findFirstDifference(resultContent, expectedContent)}`;
|
||
assert.strictEqual(resultContent, expectedContent, message);
|
||
}
|
||
|
||
function getCompareResultIgnoredTagsExistence(compareResult, ignoredTags) {
|
||
return compareResult.map(function (compareObject) {
|
||
let newMissingElements = compareObject.missingElements.filter(function (missingElement) {
|
||
return !ignoredTags.includes(missingElement.nodeName);
|
||
});
|
||
let newExtraElements = compareObject.extraElements.filter(function (extraElement) {
|
||
return !ignoredTags.includes(extraElement.nodeName);
|
||
});
|
||
return {
|
||
tagsCompared: compareObject.tagsCompared,
|
||
missingElements: newMissingElements,
|
||
equalElements: compareObject.equalElements,
|
||
extraElements: newExtraElements,
|
||
differencesInAttributes : compareObject.differencesInAttributes
|
||
}
|
||
});
|
||
}
|
||
|
||
function getCompareResultIgnoredAttributes(compareResult, ignoredAttributes) {
|
||
return compareResult.map(function (compareObject) {
|
||
let attrDifs = compareObject.differencesInAttributes;
|
||
let newMissingAttributes = attrDifs.missingAttributes.filter(function (missingAttribute) {
|
||
return !ignoredAttributes.includes(missingAttribute.name);
|
||
});
|
||
attrDifs.missingAttributes = newMissingAttributes;
|
||
|
||
let newExtraAttributes = attrDifs.extraAttributes.filter(function (extraAttribute) {
|
||
return !ignoredAttributes.includes(extraAttribute.name);
|
||
});
|
||
attrDifs.extraAttributes = newExtraAttributes;
|
||
|
||
let newWrongValuesAttributePairs = attrDifs.wrongValuesAttributePairs.filter(function (wrongValuesAttributePair) {
|
||
return !ignoredAttributes.includes(wrongValuesAttributePair[1].name); //[1] is equal to [0] here
|
||
});
|
||
attrDifs.wrongValuesAttributePairs = newWrongValuesAttributePairs;
|
||
|
||
return {
|
||
tagsCompared: compareObject.tagsCompared,
|
||
missingElements: compareObject.missingElements,
|
||
equalElements: compareObject.equalElements,
|
||
extraElements: compareObject.extraElements,
|
||
differencesInAttributes : compareObject.differencesInAttributes // changed implicitly
|
||
}
|
||
});
|
||
}
|
||
|
||
function isFilePathIgnored(path, ignoreFiles) {
|
||
let fileName = path.split('/').pop();
|
||
let matchedAny = false;
|
||
ignoreFiles.forEach(function checkPattern(pattern) {
|
||
const regexPattern = pattern.replace('[N]', '\\d+');
|
||
const regex = new RegExp(`^${regexPattern}$`, 'i');
|
||
matchedAny = matchedAny || regex.test(fileName);
|
||
});
|
||
return matchedAny;
|
||
}
|
||
|
||
function pathCheckFolderPresence(path, folders) {
|
||
let foldersUsed = path.split('/').slice(0, -1);
|
||
let isIgnored = foldersUsed.some(function (folder) {
|
||
return folders.includes(folder);
|
||
})
|
||
return isIgnored;
|
||
}
|
||
|
||
function format () {
|
||
var args = [].slice.call(arguments);
|
||
var initial = args.shift();
|
||
|
||
function replacer (text, replacement) {
|
||
return text.replace('%s', replacement);
|
||
}
|
||
return args.reduce(replacer, initial);
|
||
}
|
||
|
||
function compareDOMs(originalNode, customNode, result) {
|
||
if (typeof result === 'undefined') {
|
||
result = [];
|
||
}
|
||
if (originalNode.children.length !== 0) { // Check if it's an element node
|
||
let originalElement = originalNode;
|
||
let customElement = customNode;
|
||
|
||
let differencesInChildren = compareTagsFlat(originalElement, customElement);
|
||
let differencesInAttributes = compareAttributes(originalElement, customElement);
|
||
result.push({
|
||
tagsCompared : originalNode.nodeName,
|
||
missingElements : differencesInChildren.missingElements,
|
||
equalElements : differencesInChildren.equalElements,
|
||
extraElements : differencesInChildren.extraElements,
|
||
differencesInAttributes : differencesInAttributes
|
||
});
|
||
|
||
let equalElements = differencesInChildren.equalElements;
|
||
// recursive cals
|
||
for (let i = 0; i < equalElements.length; i++) {
|
||
result = result.concat(compareDOMs(equalElements[i][0], equalElements[i][1]));
|
||
}
|
||
} else {
|
||
// let differencesInValues = {
|
||
// originalValue: originalNode.value,
|
||
// customValue: customNode.value,
|
||
// compareResult: originalNode.value === customNode.value
|
||
// }
|
||
// ...
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
function compareAttributes(originalElement, customElement) {
|
||
let originalAttributes;
|
||
let customAttributes;
|
||
if (originalElement.attributes) {
|
||
originalAttributes = Array.from(originalElement.attributes);
|
||
originalAttributes.sort(function (a, b) {return a.name.localeCompare(b);})
|
||
} else {
|
||
originalAttributes = [];
|
||
}
|
||
if (customElement.attributes) {
|
||
customAttributes = Array.from(customElement.attributes);
|
||
customAttributes.sort(function (a, b) {return a.name.localeCompare(b);})
|
||
} else {
|
||
customAttributes = [];
|
||
}
|
||
|
||
const missingAttributes = [];
|
||
const extraAttributes = [];
|
||
const wrongValuesAttributePairs = [];
|
||
const correctAttributePairs = [];
|
||
|
||
// Iterate through the children of originalElement and check if they are missing in customElement
|
||
originalAttributes.forEach(function (originalAttribute) {
|
||
const foundIndex = customAttributes.findIndex(function(customAttribute) {
|
||
return customAttribute.name === originalAttribute.name;
|
||
});
|
||
|
||
if (foundIndex === -1) {
|
||
missingAttributes.push(originalAttribute);
|
||
} else {
|
||
// Remove the found child from customElement to handle duplicates
|
||
const removedChild = customAttributes.splice(foundIndex, 1)[0];
|
||
if (originalAttribute.value === removedChild.value) {
|
||
correctAttributePairs.push([originalAttribute, removedChild]);
|
||
} else {
|
||
wrongValuesAttributePairs.push([originalAttribute, removedChild]);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Find extra elements present in customElement but not in originalElement
|
||
// may be some() check can be omitted
|
||
customAttributes.forEach(function (customAttribute) {
|
||
if (!originalAttributes.some(function (originalAttribute) {return originalAttribute.name === customAttribute.name;})) {
|
||
extraAttributes.push(customAttribute);
|
||
}
|
||
});
|
||
|
||
return {
|
||
missingAttributes,
|
||
extraAttributes,
|
||
wrongValuesAttributePairs,
|
||
correctAttributePairs
|
||
};
|
||
}
|
||
|
||
function compareTagsFlat(originalElement, customElement) {
|
||
// indifferent of children.length there may be extra nodes missing nodes and nodes with equal nodeNames
|
||
// eg
|
||
// Orig Custom Orig Custom
|
||
// A D D A A C
|
||
// B E E B B D
|
||
// C C
|
||
// Mis ABC Extra DE Mis DE Extra ABC Mis AB Extra CD
|
||
|
||
// A D D A A A
|
||
// B B C B B B
|
||
// C C
|
||
// Mis AC Extra D Equ B Mis D Extra AB Equ C Equ AB
|
||
|
||
// B B
|
||
// B
|
||
// Mis B (second) Extra - Equ B (first). So in that case we recursively comprate two first B
|
||
// TODO: Not checking each B values or attributes. This is a flaw.
|
||
// Bcs if 2-nd B in original and 1-st B in custom are absolutely equal then original B 1-rst is missing
|
||
// But result is 2-nd B in orig is missing and orig 1-st B is not equal to custom 1-st B
|
||
|
||
let originalElementChildren = Array.from(originalElement.children);
|
||
let customElementChildren = Array.from(customElement.children);
|
||
|
||
const missingElements = [];
|
||
const equalElements = [];
|
||
const extraElements = [];
|
||
|
||
// Iterate through the children of originalElement and check if they are missing in customElement
|
||
originalElementChildren.forEach(function (originalChild) {
|
||
const foundIndex = customElementChildren.findIndex(function(customChild) {
|
||
return customChild.nodeName === originalChild.nodeName;
|
||
});
|
||
|
||
if (foundIndex === -1) {
|
||
missingElements.push(originalChild);
|
||
} else {
|
||
// Remove the found child from customElement to handle duplicates
|
||
const removedChild = customElementChildren.splice(foundIndex, 1)[0];
|
||
equalElements.push([originalChild, removedChild]);
|
||
}
|
||
});
|
||
|
||
// Find extra elements present in customElement but not in originalElement
|
||
customElementChildren.forEach(function (customChild) {
|
||
if (!originalElementChildren.some(function (originalChild) {return originalChild.nodeName === customChild.nodeName;})) {
|
||
extraElements.push(customChild);
|
||
}
|
||
});
|
||
|
||
return {
|
||
missingElements: missingElements,
|
||
equalElements: equalElements,
|
||
extraElements: extraElements
|
||
};
|
||
}
|
||
|
||
function differencesToString(differences) {
|
||
return differences.map(function (differenceObject) {
|
||
return differenceToString(differenceObject);
|
||
}).join('\n');
|
||
}
|
||
|
||
function differenceToString(difference) {
|
||
let result = '';
|
||
result += format('\nComparing elements <%s>.', difference.tagsCompared);
|
||
|
||
if (difference.missingElements.length) {
|
||
let missingElements = difference.missingElements.map(function (el) {
|
||
return format('<%s>', el.nodeName);
|
||
} );
|
||
result += format('\nMissing in custom: %s.\n', missingElements.join(', '));
|
||
}
|
||
|
||
if (difference.extraElements.length) {
|
||
let extraElements = difference.extraElements.map(function (el) {
|
||
return format('<%s>', el.nodeName);
|
||
});
|
||
result += format('\nExtra in custom: %s.\n', extraElements.join(', '));
|
||
}
|
||
|
||
if (difference.differencesInAttributes.missingAttributes.length) {
|
||
let badAttributesElements = difference.differencesInAttributes.missingAttributes.map(function (el) {
|
||
return format('%s', el.name);
|
||
});
|
||
result += format('\nMissing attributes in custom: %s.\n', badAttributesElements.join(', '));
|
||
}
|
||
if (difference.differencesInAttributes.extraAttributes.length) {
|
||
let badAttributesElements = difference.differencesInAttributes.extraAttributes.map(function (el) {
|
||
return format('%s', el.name);
|
||
});
|
||
result += format('\nExtra attributes in custom: %s.\n', badAttributesElements.join(', '));
|
||
}
|
||
if (difference.differencesInAttributes.wrongValuesAttributePairs.length) {
|
||
let badAttributesElements = difference.differencesInAttributes.wrongValuesAttributePairs.map(function (el) {
|
||
return format('%s: %s !== %s', el[0].name, el[0].value, el[1].value);
|
||
});
|
||
result += format('\nWrong values in custom attributes:\n%s.\n', badAttributesElements.join('\n'));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
});
|