/* * (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 * */ "use strict"; (function(window, document) { var c_oVsdxSheetStorageKind = { Cell_Type : 0, Trigger_Type : 1, Row_Type : 2, Section_Type : 3, Text_Type : 4, Data_Type : 5, ForeignData_Type : 6 }; /** * // Docs old: * // Text_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/text_type-complextypevisio-xml * @constructor * @extends CBaseFormatNoIdObject */ function Text_Type() { AscFormat.CBaseFormatNoIdObject.call(this); /** * if text is inherited (for calculate presentation field) */ this.isInherited = false; this.elements = [] // array to store elems below. see ShapeSheet element // this.cp = []; // this.pp = []; // this.tp = []; // this.fld = []; // but we can have text among elements // Page 21\r\n // 2/10\r\n // 3/10/2013 - 3/17/2013\r\n // notice \r\n. \r is CR symbol and \n is LF symbol. So \r\n gives us line drop used in .xml files we work with. // \r\n only happens after xml declaration and in text. So in text it is important, it is a part of text. // here is an example of 1 2 3 4 5 each on new line // 1\r\n2\r\n3\r\n4\r\n5\r\n\r\n // if you want to see \r\n set proper settings in your editor return this; } AscFormat.InitClass(Text_Type, AscFormat.CBaseFormatNoIdObject, AscDFH.historyitem_type_Unknown); Text_Type.prototype.kind = c_oVsdxSheetStorageKind.Text_Type; /** * // Docs old: * @returns {Data_Type} * @constructor */ function Data_Type() { AscFormat.CBaseFormatNoIdObject.call(this); this.value = null; // to serialize in function writeShapeSheetElementsXml this.tagName = null; return this; } AscFormat.InitClass(Data_Type, AscFormat.CBaseFormatNoIdObject, AscDFH.historyitem_type_Unknown); Data_Type.prototype.kind = c_oVsdxSheetStorageKind.Data_Type; /** * // Docs old: * // Элемент Rel (ForeignData_Type complexType): https://learn.microsoft.com/ru-ru/office/client-developer/visio/rel-element-foreigndata_type-complextypevisio-xml * // ForeignData_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/foreigndata_type-complextypevisio-xml * @returns {ForeignData_Type} * @constructor */ function ForeignData_Type() { AscFormat.CBaseFormatNoIdObject.call(this); this.foreignType = null; this.objectType = null; this.showAsIcon = null; this.objectWidth = null; this.objectHeight = null; this.mappingMode = null; this.extentX = null; this.extentY = null; this.compressionType = null; this.compressionLevel = null; this.rel = null; this.mediaFilename = null; this.oleFilename = null; return this; } AscFormat.InitClass(ForeignData_Type, AscFormat.CBaseFormatNoIdObject, AscDFH.historyitem_type_Unknown); ForeignData_Type.prototype.kind = c_oVsdxSheetStorageKind.ForeignData_Type; /** * // https://learn.microsoft.com/ru-ru/office/client-developer/visio/trigger_type-complextypevisio-xml * @constructor */ function Trigger_Type() { AscFormat.CBaseFormatNoIdObject.call(this); this.refBy = []; this.n = null; } AscFormat.InitClass(Trigger_Type, AscFormat.CBaseFormatNoIdObject, AscDFH.historyitem_type_Unknown); Trigger_Type.prototype.kind = c_oVsdxSheetStorageKind.Trigger_Type; /** * Abstract class. For all Cell containers: ShapeSheet_Type (Sheet_Type) descendents and * sections and rows also. * @constructor */ function SheetStorage() { AscFormat.CBaseFormatNoIdObject.call(this); // setSheetClassMembers /** * When working with shapes use getElements/setElements methods * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * elements is used bcs text can appear here maybe * @type {{}} */ this.elements = {}; /** * @type {{}} */ this.inheritedElements = {}; // elements below are stored in elements to support new schema // // 3 arrays below inherited from Sheet_Type // this.cells = []; // this.triggers = []; // this.sections = []; // also rows // // new attributes inherited from ShapeSheet_Type // this.text = null; // this.data1 = null; // this.data2 = null; // this.data3 = null; // this.foreignData = null; } AscFormat.InitClass(SheetStorage, AscFormat.CBaseFormatNoIdObject, AscDFH.historyitem_type_Unknown); /** * clone master elements (sections, rows, cells) to shapeElements. * For Sections and Rows merge is recursive: we compare inner cells by their names * @memberof SheetStorage * @param masterElements - cells rows sections * @param {string[]?} elementsToMerge - cells rows sections list we can merge * @param {boolean?} isParentInList */ SheetStorage.prototype.mergeElementArrays = function mergeElementArrays(masterElements, elementsToMerge, isParentInList) { /** * find index of cell row or section * @param {SheetStorage} elementsObject * @param elementToFind * @returns {*} */ function findObjectIn(elementsObject, elementToFind) { let objKey = AscVisio.createKeyFromSheetObject(elementToFind); let element = elementsObject.getElement(objKey); return element; } /** * if text is inherited so we consider that text fields in it have wrong values * and we recalculate values them * @param masterElement */ function setIsInheritedForText(masterElement) { if (masterElement.kind === c_oVsdxSheetStorageKind.Text_Type) { masterElement.isInherited = true; } } let mergeAll = false; if (elementsToMerge === undefined) { mergeAll = true; } for (const key in masterElements) { const masterElement = masterElements[key]; let overrideObject = findObjectIn(this, masterElement); let elementExistsAlready = overrideObject !== undefined; let isElementInList = elementsToMerge !== undefined && elementsToMerge.includes(masterElement.n); let listCheck = mergeAll || isParentInList || isElementInList; if (!elementExistsAlready) { if (listCheck) { // TODO fix order // now Section sort is realized in getSections, // rowsSort is not needed see getRow findObject call // mb lets not add cell after section // let elementCopy = clone(masterElement); setIsInheritedForText(masterElement); let elementLink = masterElement; this.inheritedElements[key] = elementLink; } } else { // merge inner elements recursive if not cell if (masterElement.kind !== c_oVsdxSheetStorageKind.Cell_Type) { // if Section or Row let shapeElement = overrideObject; if (masterElement.kind === c_oVsdxSheetStorageKind.Section_Type || masterElement.kind === c_oVsdxSheetStorageKind.Row_Type) { // for future checks isParentInList = isElementInList || isParentInList; // recursive calls overrideObject.mergeElementArrays(masterElement.getElements(), elementsToMerge, isParentInList); } } } } } /** * Abstract class for ShapeSheet_Type (Sheet_Type) descendents only. * @constructor * @extends SheetStorage */ function SheetStorageAndStyles() { // for ooxml classes // setShapeSheetClassMembers // 3 attr below inherited from Sheet_Type using old schema // or from ShapeSheet_Type using new schema this.lineStyle = null; this.fillStyle = null; this.textStyle = null; /** * @type {string | number | null} */ this.inheritedLineStyle = null; /** * @type {string | number | null} */ this.inheritedFillStyle = null; /** * @type {string | number | null} */ this.inheritedTextStyle = null; // call parent class constructor let parentClassConstructor = SheetStorage; parentClassConstructor.call(this); } // inherit parent class methods // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81_object.create SheetStorageAndStyles.prototype = Object.create(SheetStorage.prototype); SheetStorageAndStyles.prototype.constructor = SheetStorageAndStyles; /** * @memberof SheetStorageAndStyles * @return {*|string|number|null} */ SheetStorageAndStyles.prototype.getLineStyle = function getLineStyle() { if (this.lineStyle === null || this.lineStyle === undefined) { return this.inheritedLineStyle; } return this.lineStyle; } /** * @memberof SheetStorageAndStyles * @return {*|string|number|null} */ SheetStorageAndStyles.prototype.getFillStyle = function getFillStyle() { if (this.fillStyle === null || this.fillStyle === undefined) { return this.inheritedFillStyle; } return this.fillStyle; } /** * @memberof SheetStorageAndStyles * @return {*|string|number|null} */ SheetStorageAndStyles.prototype.getTextStyle = function getTextStyle() { if (this.textStyle === null || this.textStyle === undefined) { return this.inheritedTextStyle; } return this.textStyle; } // inheritance from ShapeSheetType for // StyleSheet_Type, DocumentSheet_Type, PageSheet_Type and Shape_Type // consider using with new schema and support old schema // 2.3.4.2.5 Cell_Type // N attribute MUST be unique amongst all of the Cell_Type elements of the containing Row_Type element // 2.3.4.2.81 Row_Type // If a Row_Type element specifies an N attribute, then it MUST NOT specify an IX attribute. // This attribute MUST be unique amongst all of the Row_Type elements of the containing Section_Type // element. // If a Row_Type element specifies an IX attribute, then it MUST NOT specify an N attribute. // The IX attribute of a Row_Type element MUST be unique amongst all of the Row_Type elements // of the containing Section_Type element. // 2.3.4.2.85 Section_Type // N attribute MUST be unique amongst all of the Section_Type elements of the containing Sheet_Type element // unless it is equal to "Geometry"! // IX attribute MUST be unique amongst all of the Section_Type elements with the same N attribute // of the containing Sheet_Type. // When the IX attribute is not present, the index of the element is calculated implicitly // by counting the number of preceding Section_Type elements with the same N attribute in the containing // Sheet_Type. let lastIx = 0; function createKeyFromSheetObject(object) { let key; if (object.kind === c_oVsdxSheetStorageKind.Cell_Type) { key = object.n; } else if (object.kind === c_oVsdxSheetStorageKind.Row_Type) { if (object.n !== null) { key = object.n; } else if (object.ix !== null) { key = object.ix; lastIx = object.ix; } else { key = lastIx; lastIx++; AscCommon.consoleLog("Ix key is generated to store object", object); } } else if (object.kind === c_oVsdxSheetStorageKind.Section_Type) { if (object.n === "Geometry") { key = "Geometry_" + object.ix; } else if (object.ix !== null) { key = object.ix; } else if (object.n !== null) { key = object.n; } else { AscCommon.consoleLog("Cant calculate key to store object", object); } } else if (object.kind === c_oVsdxSheetStorageKind.Text_Type) { key = "Text"; } else if (object.kind === c_oVsdxSheetStorageKind.Data_Type) { key = object.tagName; } else if (object.kind === c_oVsdxSheetStorageKind.ForeignData_Type) { key = "ForeignData"; } else if (object.kind === c_oVsdxSheetStorageKind.Trigger_Type) { key = object.n; } else { AscCommon.consoleLog("Unknown object in SheetElementsStorage", object); } return key; } /** * for ooxml read * @memberOf SheetStorage * @param tagName * @param reader */ SheetStorage.prototype.readInheritedElements = function readInheritedElements(tagName, reader) { let elem; switch (tagName) { case "Cell" : { elem = new Cell_Type(); elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } case "Trigger" : { elem = new Trigger_Type(); elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } case "Row" : { elem = new Row_Type(); elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } case "Section" : { elem = new Section_Type(); elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } case "Text" : { elem = new Text_Type(); elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } case "Data1" : { elem = new Data_Type(); elem.tagName = "Data1"; elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } case "Data2" : { elem = new Data_Type(); elem.tagName = "Data2"; elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } case "Data3" : { elem = new Data_Type(); elem.tagName = "Data3"; elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } case "ForeignData" : { elem = new ForeignData_Type(); elem.fromXml(reader); let key = createKeyFromSheetObject(elem); this.elements[key] = elem; break; } } } /** * @memberOf SheetStorageAndStyles * @param attrName * @param reader */ SheetStorageAndStyles.prototype.readInheritedAttributes = function readInheritedAttributes(attrName, reader) { switch (attrName) { case "LineStyle": { this.lineStyle = reader.GetValueUInt(this.lineStyle); break; } case "FillStyle": { this.fillStyle = reader.GetValueUInt(this.fillStyle); break; } case "TextStyle": { this.textStyle = reader.GetValueUInt(this.textStyle); break; } } } /** * @memberOf SheetStorageAndStyles * @param writer */ SheetStorageAndStyles.prototype.writeInheritedAttributes = function writeInheritedAttributes(writer) { writer.WriteXmlNullableAttributeUInt("LineStyle", this.lineStyle); writer.WriteXmlNullableAttributeUInt("FillStyle", this.fillStyle); writer.WriteXmlNullableAttributeUInt("TextStyle", this.textStyle); } //New schema // // // // // // // // // // // // // // // // // // // Consider choice we can have any order of elements with any occurrence // for elements in choice: Text, Data1, Data2, Data3, ForeignData, Cell, Section. // So store them in one array to save order and save all the elements // lets add triggers from old schema to this array too /** * @memberOf SheetStorage * @param writer */ SheetStorage.prototype.writeInheritedElements = function writeInheritedElements(writer) { for (const key in this.elements) { const elem = this.elements[key]; switch (elem.kind) { case c_oVsdxSheetStorageKind.Cell_Type: writer.WriteXmlNullable(elem, "Cell"); break; case c_oVsdxSheetStorageKind.Trigger_Type: writer.WriteXmlNullable(elem, "Trigger"); break; case c_oVsdxSheetStorageKind.Section_Type: writer.WriteXmlNullable(elem, "Section"); break; case c_oVsdxSheetStorageKind.Row_Type: writer.WriteXmlNullable(elem, "Row"); break; case c_oVsdxSheetStorageKind.Text_Type: writer.WriteXmlNullable(elem, "Text"); break; case c_oVsdxSheetStorageKind.Data_Type: writer.WriteXmlNullable(elem, elem.tagName); break; case c_oVsdxSheetStorageKind.ForeignData_Type: writer.WriteXmlNullable(elem, "ForeignData"); break; } } } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * Finds shape section by formula. Compares N with string argument. For Geometry use find sections. * @param {String} formula * @memberof SheetStorage * @returns {Section_Type | undefined} */ SheetStorage.prototype.getSection = function getSection(formula) { let section = this.inheritedElements[formula]; if (section === undefined) { section = this.elements[formula]; } return section; } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * Returns link to object not copy. * @param {String} formula * @memberof SheetStorage * @returns {Row_Type | undefined} */ SheetStorage.prototype.getRow = function getRow(formula) { let row = this.inheritedElements[formula]; if (row === undefined) { row = this.elements[formula]; } return row; } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * Finds shape cell by fromula. * * Shape can have cells directly in them or inside sections. Lets just search for cells * directly inside shapes for now. * * Returns object of shape not copy! * * visio can use formulas like Geometry1.X2 - first geometrySection, second Row, X column * but Row for example can be found by IX N. * * Cells in Section (section is like table in visio) can exist by itself, directy in section like here * https://disk.yandex.ru/d/Ud6-wmVjNnOyUA * with their names like Width, Height, Angle (for Shape Thansform section) * or they can be in Rows like here * https://disk.yandex.ru/i/4ASd_5KHYIlXKw * with names X, Y, A, B (for Geometry SplineStart Row). * * Let's search cells only directly in Section for now (if called on Section). * @param {String} formula * @memberof SheetStorage * @returns {Cell_Type | undefined} */ SheetStorage.prototype.getCell = function getCell(formula) { // Cells can have N only no IX let cell = this.inheritedElements[formula]; if (cell === undefined) { cell = this.elements[formula]; } if (cell !== undefined && !(cell instanceof Cell_Type)) { AscCommon.consoleLog("ERR: Tried to get cell but got other object!"); } return cell; } /** * Calls getCell on object and tries to parse as Number(cell.v) if cell exists otherwise return undefined. * @param {String} formula * @param {number?} defaultValue * @return {Number | undefined} number */ SheetStorage.prototype.getCellNumberValue = function (formula, defaultValue) { let cell = this.getCell(formula); let result; if (cell !== undefined && cell.v !== "Themed") { result = Number(cell.v); } else { result = undefined; } if (defaultValue !== undefined) { result = result === undefined ? defaultValue : result; } return result; } /** * Calls getCell on object and tries to parse as Number(cell.v) if cell exists otherwise return undefined. * @param {String} formula * @param {Number} pageScale * @return {Number | undefined} number */ SheetStorage.prototype.getCellNumberValueWithScale = function (formula, pageScale) { let cell = this.getCell(formula); if (cell !== undefined && cell.v !== "Themed") { return Number(cell.v) / pageScale; } else { return undefined; } } /** * Calls getCell on object and tries to parse as String(cell.v) if cell exists otherwise return undefined. * @param {String} formula * @return {String | undefined} string */ SheetStorage.prototype.getCellStringValue = function (formula) { let cell = this.getCell(formula); if (cell !== undefined && cell.v !== "Themed") { return String(cell.v); } else { return undefined; } } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * if in formula we have both ix and n we should use findSection instead. * or if we use it with number in formula * low performance function! use if can't use get section * @param {String} formula * @memberof SheetStorage * @returns {Section_Type[]} */ SheetStorage.prototype.getSections = function(formula) { // TODO check may be optimized. maybe use getGeometrySections function getSections(elements, resultArr) { if (/^\d+$/.test(formula)) { // if number AscCommon.consoleLog('strange findSections use (with number)'); for (const key in elements) { const element = elements[key]; if (element.kind === c_oVsdxSheetStorageKind.Section_Type && String(element.ix) === formula) { resultArr.push(element); } } } else { for (const key in elements) { const element = elements[key]; if (element.kind === c_oVsdxSheetStorageKind.Section_Type && element.n === formula) { resultArr.push(element); } } } } let resultArr = []; getSections(this.elements, resultArr); getSections(this.inheritedElements, resultArr); resultArr.sort(function (a, b) { return a.ix - b.ix; }); return resultArr; } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * Used with no argument to get all rows * low performance function! use if can't use get row * @memberof SheetStorage * @returns {Row_Type[]} */ SheetStorage.prototype.getRows = function() { // TODO check may be optimized. maybe use binary search for elements with maximum number as index bcs geometry // rows have Row.ix as index and it is number. let resultArr = []; function getRows(elements, resultArr) { for (const key in elements) { const element = elements[key]; if (element.kind === c_oVsdxSheetStorageKind.Row_Type) { resultArr.push(element); } } } getRows(this.elements, resultArr); getRows(this.inheritedElements, resultArr); resultArr.sort(function (a, b) { return a.ix - b.ix; }); return resultArr; } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * get elements inherited from shape sheet type and own. * low performance function! use if can't use getElement * @memberOf SheetStorage * @return {{}} */ SheetStorage.prototype.getElements = function () { return Object.assign({}, this.elements, this.inheritedElements); } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * @memberOf SheetStorage * @param {string} formula * @return {*} */ SheetStorage.prototype.getElement = function (formula) { // Cells can have N only no IX let element = this.inheritedElements[formula]; if (element === undefined) { element = this.elements[formula]; } return element; } /** * // Docs old: * // Section_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/section_type-complextypevisio-xml * @return {Section_Type} * @constructor * @extends SheetStorage */ function Section_Type() { this.n = null; this.del = null; this.ix = null; // always use getter setter methods // Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. // call parent class constructor let parentClassConstructor = SheetStorage; parentClassConstructor.call(this); return this; } // inherit parent class methods // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81_object.create Section_Type.prototype = Object.create(SheetStorage.prototype); Section_Type.prototype.constructor = Section_Type; Section_Type.prototype.kind = c_oVsdxSheetStorageKind.Section_Type; /** * // Docs old: * // Row_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/row_type-complextypevisio-xml * @return {Row_Type} * @constructor * @extends SheetStorage */ function Row_Type() { this.n = null; this.localName = null; this.ix = null; this.t = null; this.del = null; // always use getter setter methods // Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. // call parent class constructor let parentClassConstructor = SheetStorage; parentClassConstructor.call(this); return this; } // inherit parent class methods // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81_object.create Row_Type.prototype = Object.create(SheetStorage.prototype); Row_Type.prototype.constructor = Row_Type; Row_Type.prototype.kind = c_oVsdxSheetStorageKind.Row_Type; /** * Docs old: * Элемент RefBy (Cell_Type complexType): https://learn.microsoft.com/ru-ru/office/client-developer/visio/refby-element-cell_type-complextypevisio-xml * Cell_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/cell_type-complextypevisio-xml * @return {Cell_Type} * @constructor */ function Cell_Type() { AscFormat.CBaseFormatNoIdObject.call(this); // read all as strings /** * read as string * @type {string} */ this.n = null; /** * read as string * @type {string} */ this.u = null; /** * read as string * @type {string} */ this.e = null; /** * read as string * @type {string} */ this.f = null; /** * read as string * @type {string} */ this.v = null; this.refBy = []; this.textContent = null; // not same case like in Text_Type // I suppose text cant go along with inner text // There is either textContent or refBy // I dont make it like it Text_Type to // left separate attributes refBy and textContent and dont replace both by elements return this; } AscFormat.InitClass(Cell_Type, AscFormat.CBaseFormatNoIdObject, AscDFH.historyitem_type_Unknown); Cell_Type.prototype.kind = c_oVsdxSheetStorageKind.Cell_Type; /** * get String(cell.v) * @memberOf Cell_Type * @return {undefined | string} */ Cell_Type.prototype.getStringValue = function () { let cell = this; if (cell !== undefined) { return String(cell.v); } else { return undefined; } } /** * get Number(cell.v) * @memberOf Cell_Type * @return {undefined | number} */ Cell_Type.prototype.getNumberValue = function () { let cell = this; if (cell !== undefined) { return Number(cell.v); } else { return undefined; } } /** * try parse string "0" then get Boolean(cell.v) * @memberOf Cell_Type * @return {undefined | boolean} */ Cell_Type.prototype.getBooleanValue = function () { let cell = this; if (cell !== undefined) { if (typeof cell.v === "string") { if (cell.v === "1") { return true; } else if (cell.v === "0") { return false; } else { // unknown string return true; } } else { return Boolean(cell.v); } } else { return undefined; } } /** * Can parse themeval * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CTheme[]} themes * @param {{fontColor?: boolean, lineUniFill?: boolean, uniFillForegnd?: boolean}?} themeValWasUsedFor - changes * during function. use only for font Color LineColor and FillColor cells otherwise undefined * @param {boolean?} gradientEnabled - true by default * @param {number?} themedColorsRow * @return {(CUniFill | CUniColor | boolean | *)} */ Cell_Type.prototype.calculateValue = function calculateCellValue(shape, pageInfo, themes, themeValWasUsedFor, gradientEnabled, themedColorsRow) { if (this === null || this === undefined) { return undefined; } let cellValue = this.v; let cellName = this.n; let cellFunction = this.f; let returnValue; // supported cells let fillResultCells = ["LineColor", "FillForegnd", "FillBkgnd"]; let fillColorResultCells = ["Color", "GradientStopColor", "ShdwForegnd"]; let numberResultCells = ["LinePattern", "LineWeight", "GradientStopColorTrans", "GradientStopPosition", "FillGradientAngle", "EndArrowSize", "BeginArrowSize", "FillPattern", "LineCap", "ShdwPattern", "ShapeShdwOffsetX", "ShapeShdwOffsetY", "ShapeShdwShow", "ShapeShdwType", "ShapeShdwScaleFactor"]; let stringResultCells = ["EndArrow", "BeginArrow", "Font"]; let booleanResultCells = ["FillGradientEnabled", "LineGradientEnabled"]; // TODO handle 2.2.7.5 Fixed Theme if (cellValue === "Themed" || cellFunction === "THEMEVAL()") { // equal to THEMEVAL() call // add themeval support for every supported cell returnValue = AscVisio.themeval(this, shape, pageInfo, themes, undefined, undefined, gradientEnabled, themedColorsRow); if (themeValWasUsedFor) { if (cellName === "LineColor") { themeValWasUsedFor.lineUniFill = true; } else if (cellName === "FillForegnd") { themeValWasUsedFor.uniFillForegnd = true; } else if (cellName === "Color") { // for text color themeValWasUsedFor.fontColor = true; } } } else if (fillResultCells.includes(cellName) || fillColorResultCells.includes(cellName)) { let rgba = null; if (/#\w{6}/.test(cellValue)) { // check if hex rgba = AscCommon.RgbaHexToRGBA(cellValue); } else { let colorIndex = parseInt(cellValue); if (!isNaN(colorIndex)) { switch (colorIndex) { case 0: rgba = AscCommon.RgbaHexToRGBA('#000000'); break; case 1: rgba = AscCommon.RgbaHexToRGBA('#FFFFFF'); break; case 2: rgba = AscCommon.RgbaHexToRGBA('#FF0000'); break; case 3: rgba = AscCommon.RgbaHexToRGBA('#00FF00'); break; case 4: rgba = AscCommon.RgbaHexToRGBA('#0000FF'); break; case 5: rgba = AscCommon.RgbaHexToRGBA('#FFFF00'); break; case 6: rgba = AscCommon.RgbaHexToRGBA('#FF00FF'); break; case 7: rgba = AscCommon.RgbaHexToRGBA('#00FFFF'); break; case 8: rgba = AscCommon.RgbaHexToRGBA('#800000'); break; case 9: rgba = AscCommon.RgbaHexToRGBA('#008000'); break; case 10: rgba = AscCommon.RgbaHexToRGBA('#000080'); break; case 11: rgba = AscCommon.RgbaHexToRGBA('#808000'); break; case 12: rgba = AscCommon.RgbaHexToRGBA('#800080'); break; case 13: rgba = AscCommon.RgbaHexToRGBA('#008080'); break; case 14: rgba = AscCommon.RgbaHexToRGBA('#C0C0C0'); break; case 15: rgba = AscCommon.RgbaHexToRGBA('#E6E6E6'); break; case 16: rgba = AscCommon.RgbaHexToRGBA('#CDCDCD'); break; case 17: rgba = AscCommon.RgbaHexToRGBA('#B3B3B3'); break; case 18: rgba = AscCommon.RgbaHexToRGBA('#9A9A9A'); break; case 19: rgba = AscCommon.RgbaHexToRGBA('#808080'); break; case 20: rgba = AscCommon.RgbaHexToRGBA('#666666'); break; case 21: rgba = AscCommon.RgbaHexToRGBA('#4D4D4D'); break; case 22: rgba = AscCommon.RgbaHexToRGBA('#333333'); break; case 23: rgba = AscCommon.RgbaHexToRGBA('#1A1A1A'); break; default: AscCommon.consoleLog("error: unknown color index"); rgba = AscCommon.RgbaHexToRGBA('#000000'); break; } } else { AscCommon.consoleLog("error: color index is null"); rgba = AscCommon.RgbaHexToRGBA('#000000'); } } if (fillResultCells.includes(cellName)) { returnValue = AscFormat.CreateUnfilFromRGB(rgba.R, rgba.G, rgba.B); } else if (fillColorResultCells.includes(cellName)) { // for text color returnValue = AscFormat.CreateUnfilFromRGB(rgba.R, rgba.G, rgba.B).fill.color; } else { AscCommon.consoleLog("wrong calculateCellValue argument cell. Cell unsupported. return null"); return null; } } else if (numberResultCells.includes(cellName)) { let cellNumberValue = this.getNumberValue(); if (!isNaN(cellNumberValue)) { if (cellName === "GradientStopPosition") { cellNumberValue *= 100000; } else if (cellName === "FillGradientAngle") { let angleRads = cellNumberValue; let angle = null; // 20.1.10.3 ST_Angle (Angle) // This simple type represents an angle in 60,000ths of a degree. Positive angles are clockwise (i.e., towards the // positive y axis); negative angles are counter-clockwise (i.e., towards the negative y axis) // direction is considered in global transform let stAngle = angleRads / Math.PI * 180 * AscFormat.degToC; if (!isNaN(stAngle)) { angle = stAngle; } else { angle = 5400000; } cellNumberValue = angle; } else if (cellName === "LineCap") { switch (cellNumberValue) { case 0: cellNumberValue = 1; break; case 1: cellNumberValue = 0; break; case 2: cellNumberValue = 2; break; } } return cellNumberValue; } } else if (booleanResultCells.includes(cellName)) { let cellBooleanValue = this.getBooleanValue(); return cellBooleanValue; } else if (stringResultCells.includes(cellName)) { let cellStringValue = this.getStringValue(); return cellStringValue; } else { AscCommon.consoleLog("Cell was not calculated in calculate cell value"); } // code below is unused because we dont need default values here. Cell value is either parsed well or // default value is used in themeval function. // if (returnValue === null || returnValue === undefined) { // if (cellName === "LineColor" || cellName === "FillForegnd" || cellName === "FillBkgnd") { // AscCommon.consoleLog("no color found. so painting lt1."); // returnValue = AscFormat.CreateUniFillByUniColor(AscFormat.builder_CreateSchemeColor("lt1")); // } else if (cellName === "Color") { // // cellName === "Color" for text color // AscCommon.consoleLog("no text color found. so painting dk1."); // returnValue = AscFormat.builder_CreateSchemeColor("dk1"); // } else if (cellName === "GradientStopColor") { // AscCommon.consoleLog("no GradientStopColor color found. so painting lk1."); // returnValue = AscFormat.builder_CreateSchemeColor("lt1"); // } else { // AscCommon.consoleLog("no calculateCellValue result return undefined"); // } // } return returnValue; } // /** // * @memberOf Cell_Type // * @return {number} // */ // Cell_Type.prototype.getValueInMM = function () { // let res; // //todo all units // switch (this.u) { // case "DL": // case "IN": // case "IN_F": // res = parseFloat(this.v) * g_dKoef_in_to_mm; // break; // case "FT": // res = parseFloat(this.v) * 12 * g_dKoef_in_to_mm; // break; // case "F_I": // res = parseFloat(this.v); // let intPart = Math.floor(res); // res = (intPart * 12 + (res - intPart)) * g_dKoef_in_to_mm; // break; // case "KM": // res = parseFloat(this.v) * 1000000; // break; // case "M": // res = parseFloat(this.v) * 1000; // break; // case "CM": // res = parseFloat(this.v) * 10; // break; // case "MM": // res = parseFloat(this.v); // break; // default: // res = parseFloat(this.v) * g_dKoef_in_to_mm; // break; // } // return res; // }; // /** // * @memberOf Cell_Type // * @return {number} // */ // Cell_Type.prototype.getValueInInch = function () { // let res = this.getValueInMM() / g_dKoef_in_to_mm; // return res; // } /** * // Docs old: * // Useless see ShapeSheet_Type - old * // Inherites(extends) ShapeSheet_Type in new schema * @return {Shape_Type} * @constructor * @extends SheetStorageAndStyles */ function Shape_Type() { this.id = null; this.originalID = null; this.del = null; this.masterShape = null; this.uniqueID = null; this.name = null; this.nameU = null; this.isCustomName = null; this.isCustomNameU = null; this.master = null; this.type = null; /** * use get subshapes method * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * @type {Shape_Type[]} */ this.shapes = []; /** * Own shapes and inherited * @type {Shape_Type[]} */ this.inheritedShapes = []; /** * Shape_Type.prototype.toGeometryAndTextCShapes creates CShape from Shape_Type but for image as an * exception we make variable to store CImageShape in advance. We read CImageShape on parsing in * AscVisio.Shape_Type.prototype.fromXml. Because CImageShape needs StaxParser reader object for init. * @type {CImageShape} */ this.cImageShape = null; /** * see MS-VSDX 2.2.7.4.9 Connector. if true shape is connector * @type {boolean} */ this.isConnectorStyleIherited = false; // call parent class constructor let parentClassConstructor = SheetStorageAndStyles; parentClassConstructor.call(this); // return this; } // inherit parent class methods // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81_object.create Shape_Type.prototype = Object.create(SheetStorageAndStyles.prototype); Shape_Type.prototype.constructor = Shape_Type; /** * @memberOf Shape_Type * @return {*} masterId */ Shape_Type.prototype.getMasterID = function() { return this.master; } /** * get Shape properties that come from layers. * if shape has multiple layers attached only equal layer properties applied (and added to object). * @memberOf Shape_Type * @param {Page_Type} pageInfo * @return {{ [key: string]: Cell_Type }} An object mapping string identifiers to Cell_Type instances */ Shape_Type.prototype.getLayerProperties = function getLayerProperties(pageInfo) { let layerMemberString = this.getCellStringValue("LayerMember"); if (layerMemberString === undefined || layerMemberString === "") { return {}; } let layersArray = layerMemberString.split(";"); let layersInfo = pageInfo.pageSheet.getSection("Layer"); if (layersInfo === undefined) { return {}; } let previousLayerElements = undefined; /** @type {Set} */ let unEqualProperties = new Set(); layersArray.forEach(function (layerIndexString) { let layerIndex = Number(layerIndexString); let layerInfo = layersInfo.getRow(layerIndex); if (layerInfo === undefined) { return; // go to next iteration } let layerElements = layerInfo.getElements(); // Unlink original array let layerElementsCopy= {}; for (const key in layerElements) { if (layerElements.hasOwnProperty(key)) { layerElementsCopy[key] = layerElements[key]; } } // Set default color if (layerElementsCopy["Color"] === undefined) { let defaultColorCell = new Cell_Type(); defaultColorCell.n = "Color"; defaultColorCell.v = "0"; layerElementsCopy["Color"] = defaultColorCell; } if (previousLayerElements === undefined) { previousLayerElements = layerElementsCopy; } else { // compare with previous shape layer for (const cellKey in layerElementsCopy) { const cell = layerElementsCopy[cellKey]; let previousLayerCell = previousLayerElements[cell.n]; if (previousLayerCell.v !== cell.v) { unEqualProperties.add(cell.n); } } previousLayerElements = layerElementsCopy; } }); // layers have the same set of properties so lets take any of them // and remove unEqualProperties /** @type {{ [key: string]: Cell_Type }} */ let resultObject = {}; if (previousLayerElements === undefined) { return resultObject; } for (const cellKey in previousLayerElements) { const cell = previousLayerElements[cellKey]; if (!unEqualProperties.has(cell.n)) { resultObject[cell.n] = cell; } } return resultObject; } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * @memberof Shape_Type * @return {Shape_Type[]} */ Shape_Type.prototype.getSubshapes = function () { return this.shapes.concat(this.inheritedShapes); } /** * Always use it see Shape_Type.prototype.realizeMasterToShapeInheritanceRecursive js docs for explanation. * Finds shape text element. * * Returns object of shape not copy! * * @memberof Shape_Type * @returns {Text_Type | undefined} */ Shape_Type.prototype.getTextElement = function getTextElement() { let text = this.inheritedElements["Text"]; if (text === undefined) { text = this.elements["Text"]; } return text; } /** * returns this shape and subshapes array without cloning so objects are linked. * @param [resultArray = []] * @memberof Shape_Type * @return {Shape_Type[]} */ Shape_Type.prototype.collectSubshapesRecursive = function(resultArray) { if (resultArray === undefined) { resultArray = []; } // ! Don't change cell.v bcs you may change master values resultArray.push(this); let subShapes = this.getSubshapes(); for (let j = 0; j < subShapes.length; j++) { const subShape = subShapes[j]; subShape.collectSubshapesRecursive(resultArray); } return resultArray; } /** * clones masters shapes to given shape. * Uses MasterShapeAttributes to find shapes to insert. * @param {Shape_Type[]} masterSubshapes * @param masters - result from joinMastersInfoAndContents() */ Shape_Type.prototype.cloneSubshapes = function cloneSubshapes(masterSubshapes, masters) { function findIndexComparingByMasterShapeAttribute(shapeSubshapes, masterSubshape) { return shapeSubshapes.findIndex(function (element) { return element.masterShape === masterSubshape.id; }); } // If subshape has Master attribute with id of any master: call realizeMasterToShapeInheritance // If subshape has MasterShape attribute with id of any parents shape masters subshapes: // call mergeElementArrays NO RECURSION HERE // If there is a shape in master but there is no such local subshape then it should be inherited (copied) // If there is a shape in master but there is no such local subshape then it should be inherited (copied) // lets check if it exists locally only by MasterShape attribute // examples it the bottom of the function // handle subshapes MasterShape attribute let thisContext = this; masterSubshapes.forEach(function(masterSubshape) { let mergeElementIndex = findIndexComparingByMasterShapeAttribute(thisContext.getSubshapes(), masterSubshape); let elementExistsAlready = mergeElementIndex !== -1; // 2.2.5.4.1 Master-to-Shape Inheritance // "subshapes not specified in the instance are inherited from the master." (from its master) if (!elementExistsAlready) { // maybe add masterShape attribute to new shape - lets dont do it because: // of recursive iterations of inheritance we will try to inherit because we will se masterShape but // there is no need because it is copy pasted element no need in inheritance // maybe consider id to insert in ascending order thisContext.inheritedShapes.push(clone(masterSubshape)); } else { // 2.2.5.4.1 Master-to-Shape Inheritance // "if an instance contains a subshape whose ShapeSheet_Type element has a MasterShape attribute that matches // the ID attribute of a subshape of the master, the local properties specified in this subshape will // override those of the corresponding subshape in the master." // let masterElements = masterSubshape.elements; // let shapeElements = shapeSubshapes[mergeElementIndex].elements; // mergeElementArrays(masterElements); // it is done in realizeMasterToShapeInheritanceRecursive with subshapes handle } }); } /** * Realizes Master-To-Shape inheritance. * Comes through the shape recursively through all subshapes * and copies properties from their masters if master exist. * Not uses clone function. * * This function erases original shape object and after function call we cant find what props are * inherited. * * Also a memory problem. Master attributes copy to each shape. * but on the most heavy file shapeClasses after inheritance is only 217 megabytes * * TODO: * maybe like this * rewrite: just set simple links to masters for shape and subshapes in this function BUT * !!! always use function getCell/getRow/getSection/getSubshape/getElements which will search for element * in this shape first and then in master. Also use getElements or getSubshapes functions to get all * cells/rows/sections or subshapes, theese function will first merge master and shape elements/subshapes * and then return elements/subshapes. * Also use setter functions. * * @param masters * @param {Shape_Type[]?} ancestorMasterShapes * @return {Shape_Type} shape with all its props */ Shape_Type.prototype.realizeMasterInheritanceRecursively = function(masters, ancestorMasterShapes) { // 2.2.5.4.1 Master-to-Shape Inheritance // If shape has master or masterShape that have 1 top level shape // - inherit elements (sections, rows, cells) and subshapes // If shape has master or masterShape that have several top level shapes // - inherit only subshapes as shape subshapes // // subshapes inheritance: // 2.2.5.4.1 Master-to-Shape Inheritance // "if an instance contains a subshape whose ShapeSheet_Type (now it is Shape_Type) // element has a MasterShape attribute that matches the ID attribute of a subshape of the master, // the local properties specified in this subshape will override those of the corresponding subshape // in the master." // "subshapes not specified in the instance are inherited from the master." (from its master) // // So in main function come across all shapes and subshapes and call realizeMasterToShapeInheritanceRecursive // // realizeMasterToShapeInheritanceRecursive: // shape can have master or masterShape id and be it master or masterShape if master (master or masterShape) have // 1 top level shape it inherits its elements, then handle subshapes. // // handle subshapes: // come along master and check MasterShape attributes if we have no shape with this MasterShape copy it to // out shape. // What if we inherit with MasterShape id not with Master id do we check MasterShape attributes from our Shape? // - I think yes we compare subshape ids from masterShape with MasterShape ids of subshapes of shape that // is beiing inherited (check sub MasterShape ids) // We dont need to merge subshapes elements in currents step because we will make it in recursion iterations? // Its just a detail of realization but yes. For subshapes we merge elements on next steps on recursive calls. // // handle several top level subshapes: // just handle shape subshapes like above // // - can shape just consist of multiple shapes like master? // maybe dont memorize MasterShape shape. but when we inherit one level below its masterIds // - should we search for Master shape with the specified MasterShape recursively? // previously i set MasterShape as ancestorMasterShapes and didnt use collectSubshapesRecursive // to search for master but now code is code is more flexible but picture didnt change anyway // code is more readable // - should we compare nested shapes of shape and master recursively? // no bcs - realizeMasterToShapeInheritanceRecursive will call itself on subshapes so it will call // compare nested shapes recursively (mergeSubshapes) // but what if need to call merge subshapes recursively on subshapes relative to this master? // - no because if subshape at any deep have no Master/MasterShape in need no // call mergeSubshapes otherwise realizeMasterToShapeInheritanceRecursive will call mergeSubshapes because // shape have Master/MasterShape attribute. // - what about both master and masterShape inheritance? // - dont forget to inherit links to styles from master // Consider examples // // // // // // ... // // // // // ... // // // // // // // ... // // // // // ... let masterShapesToInheritFrom = []; // lets create copy of this shape work with it then return it // let thisShapeCopy = clone(this); // check Master attribute and set shapes/shape // to inherit from: masterShapesToInheritFrom and ancestorMasterShapes let topShapeMasterId = this.getMasterID(); if (topShapeMasterId !== null && topShapeMasterId !== undefined) { let topShapeMasterIndex = masters.findIndex(function(masterObject) { return masterObject.id === topShapeMasterId; }); let topShapeMaster = masters[topShapeMasterIndex]; if (topShapeMaster) { let masterShapes = topShapeMaster.content.shapes; masterShapesToInheritFrom = masterShapes; // all descendant shapes will inherit from that master ancestorMasterShapes = masterShapesToInheritFrom; } } // check MasterShape attribute and set shapes/shape // to inherit from: masterShapesToInheritFrom and ancestorMasterShapes let masterShapeId = this.masterShape; if (masterShapeId !== null && masterShapeId !== undefined) { if (ancestorMasterShapes === null || ancestorMasterShapes === undefined) { AscCommon.consoleLog("MasterShape attribute is set but Master is not set for ", this); } else { let masterIndex = -1; if (ancestorMasterShapes.length === 1) { // if master has one top level shape let masterSubshapes = ancestorMasterShapes[0].collectSubshapesRecursive(); masterIndex = masterSubshapes.findIndex(function (masterSubshape) { return masterShapeId === masterSubshape.id; }); let masterShape = masterSubshapes[masterIndex]; masterShapesToInheritFrom = [masterShape]; } else { let masterSubshapes = []; ancestorMasterShapes.forEach(function(ancestorMasterShape) { let masterSubshapesNth = ancestorMasterShape.collectSubshapesRecursive(); masterSubshapes = masterSubshapes.concat(masterSubshapesNth); }) masterIndex = masterSubshapes.findIndex(function (masterSubshape) { return masterShapeId === masterSubshape.id; }); let masterShape = masterSubshapes[masterIndex]; masterShapesToInheritFrom = [masterShape]; } if (masterIndex === -1) { AscCommon.consoleLog('For MasterShape = ', masterShapeId, 'shape not found in master. Check shape: ', this); } } } // inherit: mergeElements and clone shapes to which elements will be merged on recursive calls if (masterShapesToInheritFrom.length === 1) { let masterShapeToInheritFrom = masterShapesToInheritFrom[0]; // inherit link to styles if (!this.inheritedLineStyle) { this.inheritedLineStyle = masterShapeToInheritFrom.lineStyle; } if (!this.inheritedFillStyle) { this.inheritedFillStyle = masterShapeToInheritFrom.fillStyle; } if (!this.inheritedTextStyle) { this.inheritedTextStyle = masterShapeToInheritFrom.textStyle; } let masterElements = masterShapeToInheritFrom.getElements(); this.mergeElementArrays(masterElements); if (masterShapeToInheritFrom.type === AscVisio.SHAPE_TYPES_FOREIGN) { if (masterShapeToInheritFrom.cImageShape) { this.cImageShape = clone(masterShapeToInheritFrom.cImageShape); } } let masterSubshapes = masterShapeToInheritFrom.getSubshapes(); this.cloneSubshapes(masterSubshapes, masters); } else if (masterShapesToInheritFrom.length > 1) { // does it ever happens? // what about style inheritance? this.cloneSubshapes(masterShapesToInheritFrom, masters); } // call recursive on all subshapes let subshapes = this.getSubshapes(); subshapes.forEach(function(shape) { shape.realizeMasterInheritanceRecursively(masters, ancestorMasterShapes); }); // return thisShapeCopy; // end of method } /** * Inherits style to master/shape or style to style. Process is the same. * Inherits all style elements (sections, rows, cells). * @param {Shape_Type | StyleSheet_Type} thisArgument * @param {StyleSheet_Type[]} styles * @param {?Set} stylesWithRealizedInheritance */ function realizeStyleToSheetObjInheritanceRecursive(thisArgument, styles, stylesWithRealizedInheritance) { if (stylesWithRealizedInheritance.has(thisArgument)) { // thisArgument is style not shape and it has realized inheritance already // AscCommon.consoleLog("style has realized inheritance already. return"); return; } /** * see MS-VSDX 2.2.7.4.9 Connector. * @param {Shape_Type | StyleSheet_Type} object * @param {StyleSheet_Type} style */ function setIsConnectorStyleInherited(object, style) { object.isConnectorStyleIherited = object.isConnectorStyleIherited ? true : style.nameU === "Connector"; } if (!(thisArgument.getLineStyle() === thisArgument.getFillStyle() && thisArgument.getLineStyle() === thisArgument.getTextStyle())) { // Attribute Cell_Type elements // LineStyle Specifies Cell_Type elements related to line properties except for Cell_Type child elements // of a FillGradient Section_Type. // Line property information in shapes, masters, and styles is specified by the LineColor, LinePattern, LineWeight, // LineCap, BeginArrow, EndArrow, LineColorTrans, CompoundType, BeginArrowSize, EndArrowSize, Rounding, // LineGradientDir, LineGradientAngle, and LineGradientEnabled Cell_Type elements, and the Cell_Type // elements belonging to the LineGradient Section_Type. // FillStyle Specifies Cell_Type elements related to fill properties and effect properties // including Cell_Type child elements of a FillGradient Section_Type. // Fill property information in shapes, masters, and styles is specified by the FillForegnd, // FillForegndTrans, FillBkgnd, FillBkgndTrans, FillPattern, // FillGradientDir, FillGradientAngle, FillGradientEnabled, // RotateGradientWithShape, and UseGroupGradientCell_Type elements, // and the Cell_Type elements belonging to the FillGradient Section_Type. // Shadow effect set information in shapes, masters, and styles is specified by the ShdwForegnd, ShdwForegndTrans, // ShdwPattern, ShapeShdwType, ShapeShdwOffsetX, ShapeShdwOffsetY, ShapeShdwObliqueAngle, ShapeShdwScaleFactor, // and ShapeShdwBlur Cell_Type elements. // TextStyle Specifies Cell_Type elements related to text. // What about Quick style cells? // cells rows sections // TODO check cells inside section LineGradient let lineStyleElements = ["LineColor", "LinePattern", "LineWeight", "LineCap", "BeginArrow", "EndArrow", "LineColorTrans", "CompoundType", "BeginArrowSize", "EndArrowSize", "Rounding", "LineGradientDir", "LineGradientAngle", "LineGradientEnabled", "LineGradient", "QuickStyleLineColor", "QuickStyleLineMatrix"]; // TODO check cells inside section FillGradient let fillStyleElements = ["FillForegnd", "FillForegndTrans", "FillBkgnd", "FillBkgndTrans", "FillPattern", "FillGradientDir", "FillGradientAngle", "FillGradientEnabled", "RotateGradientWithShape", "UseGroupGradientCell_Type", "FillGradient", "ShdwForegnd", "ShdwForegndTrans", "ShdwPattern", "ShapeShdwType", "ShapeShdwOffsetX", "ShapeShdwOffsetY", "ShapeShdwObliqueAngle", "ShapeShdwScaleFactor", "ShapeShdwBlur", "QuickStyleFillColor", "QuickStyleFillMatrix"]; let textStyleElements = ["TextBkgnd", "TextDirection", "TextBkgndTrans", "LockTextEdit", "HideText", "TheText", "IsTextEditTarget", "KeepTextFlat", "ReplaceLockText", "TextPosAfterBullet", "Character", "Paragraph", "Tabs", "DefaultTabStop", "VerticalAlign", "BottomMargin", "TopMargin", "RightMargin", "LeftMargin"]; let commonElements = ["ColorSchemeIndex", "EffectSchemeIndex", "ConnectorSchemeIndex", "FontSchemeIndex", "ThemeIndex", "VariationColorIndex", "VariationStyleIndex", "EmbellishmentIndex", "QuickStyleLineColor", "QuickStyleFillColor", "QuickStyleShadowColor", "QuickStyleFontColor", "QuickStyleLineMatrix", "QuickStyleFillMatrix", "QuickStyleEffectsMatrix", "QuickStyleFontMatrix", "QuickStyleType", "QuickStyleVariation"]; lineStyleElements = lineStyleElements.concat(commonElements); fillStyleElements = fillStyleElements.concat(commonElements); textStyleElements = textStyleElements.concat(commonElements); if (thisArgument.getLineStyle() !== null) { let styleId = Number(thisArgument.getLineStyle()); let styleSheet = styles.find(function(style) { return style.id === styleId; }); setIsConnectorStyleInherited(thisArgument, styleSheet); realizeStyleToSheetObjInheritanceRecursive(styleSheet, styles, stylesWithRealizedInheritance); thisArgument.mergeElementArrays(styleSheet.getElements(), lineStyleElements); } if (thisArgument.getFillStyle() !== null) { let styleId = Number(thisArgument.getFillStyle()); let styleSheet = styles.find(function(style) { return style.id === styleId; }); setIsConnectorStyleInherited(thisArgument, styleSheet); realizeStyleToSheetObjInheritanceRecursive(styleSheet, styles, stylesWithRealizedInheritance); thisArgument.mergeElementArrays(styleSheet.getElements(), fillStyleElements); } if (thisArgument.getTextStyle() !== null) { let styleId = Number(thisArgument.getTextStyle()); let styleSheet = styles.find(function(style) { return style.id === styleId; }); setIsConnectorStyleInherited(thisArgument, styleSheet); realizeStyleToSheetObjInheritanceRecursive(styleSheet, styles, stylesWithRealizedInheritance); thisArgument.mergeElementArrays(styleSheet.getElements(), textStyleElements); } if (thisArgument.constructor === AscVisio.StyleSheet_Type) { // memorize: that style has realized inheritance stylesWithRealizedInheritance.add(thisArgument); } return; } if (thisArgument.getLineStyle() === null && thisArgument.getFillStyle() === null && thisArgument.getTextStyle() === null) { // AscCommon.consoleLog('Top parent style'); return; } // if lineStyle === textStyle === fillStyle so let's take lineStyle let styleId = Number(thisArgument.getLineStyle()); let styleSheet = styles.find(function(style) { return style.id === styleId; }); setIsConnectorStyleInherited(thisArgument, styleSheet); realizeStyleToSheetObjInheritanceRecursive(styleSheet, styles, stylesWithRealizedInheritance); thisArgument.mergeElementArrays(styleSheet.getElements()); if (thisArgument.constructor === AscVisio.StyleSheet_Type) { // memorize: that style has realized inheritance stylesWithRealizedInheritance.add(thisArgument); } } /** * Style-To-Shape inheritance * Copy all style elements (sections, rows, cells) to shape. * (Doesn't take much memory < 300MB with master inheritance for the most large files). * stylesWithRealizedInheritance was added for optimization. * Check if this shape or sub-shapes have lineStyle/fillStyle/textStyle if so realize inheritance with * recursive style inheritance * @param styles * @param {?Set} [stylesWithRealizedInheritance] * @memberOf Shape_Type */ Shape_Type.prototype.realizeStyleInheritanceRecursively = function(styles, stylesWithRealizedInheritance) { if (stylesWithRealizedInheritance === undefined) { stylesWithRealizedInheritance = new Set(); } realizeStyleToSheetObjInheritanceRecursive(this, styles, stylesWithRealizedInheritance); // call recursive on all subshapes let subshapes = this.getSubshapes(); subshapes.forEach(function(shape) { shape.realizeStyleInheritanceRecursively(styles, stylesWithRealizedInheritance); }); } /** * @memberOf Shape_Type * @return {ForeignData_Type | undefined} */ Shape_Type.prototype.getForeignDataObject = function getForeignDataObject() { let result = this.elements["ForeignData"]; if (result === undefined) { result = this.inheritedElements["ForeignData"]; } return result; // return this.elements.find(function findForeignData(element) { // return element.constructor.name === "ForeignData_Type"; // }); } /** * @memberof Shape_Type * returns index of color shape theme */ Shape_Type.prototype.calculateColorThemeIndex = function calculateColorThemeIndex(pageInfo) { let themeIndex = 0; // zero index means no theme - use default values let themeScopeCellName = this.isConnectorStyleIherited ? "ConnectorSchemeIndex" : "ColorSchemeIndex"; let shapeColorSchemeThemeIndex = this.getCellNumberValue(themeScopeCellName); if (isNaN(shapeColorSchemeThemeIndex)) { // if not found or smth // shapeColorSchemeThemeIndex = 0; // zero index means no theme themeIndex = 0; // zero index means no theme } else if (shapeColorSchemeThemeIndex === 65534) { let pageThemeIndex = pageInfo.pageSheet.getCellNumberValue(themeScopeCellName); if (!isNaN(pageThemeIndex)) { themeIndex = pageThemeIndex; } else { // it's ok sometimes // AscCommon.consoleLog("pageThemeIndexCell not found"); themeIndex = 0; } } else { themeIndex = shapeColorSchemeThemeIndex; } return themeIndex; } /** * calculate color theme index and get theme from themes. * Todo for proper cell select proper themeIndex ConnectorSchemeIndex / EffectSchemeIndex / FontSchemeIndex ... * @param {Page_Type} pageInfo * @param {CTheme[]} themes * @return {*} */ Shape_Type.prototype.getTheme = function getTheme(pageInfo, themes) { let isConnectorShape = this.isConnectorStyleIherited; let themeIndex = this.calculateColorThemeIndex(pageInfo); // find theme by themeIndex let theme = themes.find(function (theme) { // if search by theme index - theme.themeElements.themeExt.themeSchemeSchemeEnum let findThemeByElement; if (isConnectorShape && theme.themeElements.themeExt) { findThemeByElement = theme.themeElements.themeExt.themeSchemeSchemeEnum; } else if (!isConnectorShape && theme.themeElements.clrScheme.clrSchemeExtLst) { findThemeByElement = theme.themeElements.clrScheme.clrSchemeExtLst.schemeEnum; } if (!findThemeByElement) { return false; } let themeEnum = Number(findThemeByElement); return themeEnum === themeIndex; }); // themes.find didn't find anything if (theme === undefined) { AscCommon.consoleLog("Theme was not found by theme enum in themes. using themes[0]"); theme = themes[0]; } return theme; } /** * get deep copy of object with prototypes * @param object * @return {any} */ function clone(object) { function cloneWithPrototypesRecursive(copyObject, originalObject) { // Iterate over object properties recursively for (const key in originalObject) { if (originalObject.hasOwnProperty(key)) { if (typeof originalObject[key] === 'object' && originalObject[key] !== null) { // after recursive call when we set array props using copyObject[key] = originalObject[key]; // array length will change automatically copyObject[key] = Array.isArray(originalObject[key]) ? [] : Object.create(Object.getPrototypeOf(originalObject[key])); cloneWithPrototypesRecursive(copyObject[key], originalObject[key]); } else { copyObject[key] = originalObject[key]; } } } } let copy = Object.create(Object.getPrototypeOf(object)); // let copy = JSON.parse(JSON.stringify(object)); cloneWithPrototypesRecursive(copy, object); // AscCommon.consoleLog("Clone function test.\nObject before: ", object, "\nAfter: ", copy); return copy; } /** * // Docs old: * // Элемент PageSheet (Page_Type complexType): https://learn.microsoft.com/ru-ru/office/client-developer/visio/pagesheet-element-page_type-complextypevisio-xml * // Элемент Rel (Page_Type complexType): https://learn.microsoft.com/ru-ru/office/client-developer/visio/rel-element-page_type-complextypevisio-xml * // Page_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/page_type-complextypevisio-xml * @returns {Page_Type} * @constructor */ function Page_Type() { this.id = null; this.name = null; this.nameU = null; this.isCustomName = null; this.isCustomNameU = null; this.background = null; this.backPage = null; this.viewScale = null; this.viewCenterX = null; this.viewCenterY = null; this.reviewerID = null; this.associatedPage = null; this.pageSheet = null; this.rel = null; //todo objectId this.deleteLock = new AscVisio.PropLocker(undefined); return this; } AscFormat.InitClass(Page_Type, AscFormat.CBaseFormatNoIdObject, AscDFH.historyitem_type_Unknown); /** * // Docs old: * // PageSheet_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/pagesheet_type-complextypevisio-xml * // In new schema inherits from ShapeSheet_Type * @returns {PageSheet_Type} * @constructor * @extends SheetStorageAndStyles */ function PageSheet_Type() { this.uniqueID = null; // call parent class constructor let parentClassConstructor = SheetStorageAndStyles; parentClassConstructor.call(this); return this; } // inherit parent class methods // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81_object.create PageSheet_Type.prototype = Object.create(SheetStorageAndStyles.prototype); PageSheet_Type.prototype.constructor = PageSheet_Type; /** * // Docs old: * // DocumentSheet_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/documentsheet_type-complextypevisio-xml * // In new schema inherites from ShapeSheet_Type * @returns {DocumentSheet_Type} * @constructor * @extends SheetStorageAndStyles */ function DocumentSheet_Type() { this.name = null; this.nameU = null; this.isCustomName = null; this.isCustomNameU = null; this.uniqueID = null; // call parent class constructor let parentClassConstructor = SheetStorageAndStyles; parentClassConstructor.call(this); return this; } // inherit parent class methods // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81_object.create DocumentSheet_Type.prototype = Object.create(SheetStorageAndStyles.prototype); DocumentSheet_Type.prototype.constructor = DocumentSheet_Type; /** * // Docs old: * // StyleSheet_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/stylesheet_type-complextypevisio-xml * //In new schema inherites from ShapeSheet_Type * @returns {StyleSheet_Type} * @constructor * @extends SheetStorageAndStyles */ function StyleSheet_Type() { this.id = null; this.name = null; this.nameU = null; this.isCustomName = null; this.isCustomNameU = null; /** * see MS-VSDX 2.2.7.4.9 Connector. * @type {boolean} */ this.isConnectorStyleIherited = false; // call parent class constructor let parentClassConstructor = SheetStorageAndStyles; parentClassConstructor.call(this); return this; } // inherit parent class methods // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81_object.create StyleSheet_Type.prototype = Object.create(SheetStorageAndStyles.prototype); StyleSheet_Type.prototype.constructor = StyleSheet_Type; /** * // Docs old: * // Элемент Shapes (ShapeSheet_Type complexType): https://learn.microsoft.com/ru-ru/office/client-developer/visio/shapes-element-shapesheet_type-complextypevisio-xml * // ShapeSheet_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/shapesheet_type-complextypevisio-xml * @returns {ShapeSheet_Type} * @constructor * @extends SheetStorageAndStyles */ function ShapeSheet_Type() { this.id = null; this.originalID = null; this.del = null; this.masterShape = null; this.uniqueID = null; this.name = null; this.nameU = null; this.isCustomName = null; this.isCustomNameU = null; this.master = null; this.type = null; this.shapes = []; this.inheritedShapes = []; this.items = null; this.anyAttr = null; // call parent class constructor let parentClassConstructor = SheetStorageAndStyles; parentClassConstructor.call(this); return this; } // inherit parent class methods // https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81_object.create ShapeSheet_Type.prototype = Object.create(SheetStorageAndStyles.prototype); ShapeSheet_Type.prototype.constructor = ShapeSheet_Type; // Docs old: // Icon_Type complexType: https://learn.microsoft.com/ru-ru/office/client-developer/visio/icon_type-complextypevisio-xml function Icon_Type() { AscFormat.CBaseFormatNoIdObject.call(this); this.value = null; return this; } AscFormat.InitClass(Icon_Type, AscFormat.CBaseFormatNoIdObject, AscDFH.historyitem_type_Unknown); //todo move to commons function PropLocker(objectId) { this.objectId = null; this.Lock = new AscCommon.CLock(); this.Id = AscCommon.g_oIdCounter.Get_NewId(); g_oTableId.Add(this, this.Id); if(typeof objectId === "string") { this.setObjectId(objectId); } } PropLocker.prototype = { getObjectType: function() { return AscDFH.historyitem_type_PropLocker; }, setObjectId: function(id) { //todo //History.Add(new AscDFH.CChangesDrawingsString(this, AscDFH.historyitem_PropLockerSetId, this.objectId, id)); this.objectId = id; }, Get_Id: function() { return this.Id; }, Write_ToBinary2: function(w) { w.WriteLong(AscDFH.historyitem_type_PropLocker); w.WriteString2(this.Id); }, Read_FromBinary2: function(r) { this.Id = r.GetString2(); }, Refresh_RecalcData: function() {} }; //-------------------------------------------------------------export--------------------------------------------------- window['Asc'] = window['Asc'] || {}; window['AscCommon'] = window['AscCommon'] || {}; window['AscCommonWord'] = window['AscCommonWord'] || {}; window['AscCommonSlide'] = window['AscCommonSlide'] || {}; window['AscCommonExcel'] = window['AscCommonExcel'] || {}; window['AscVisio'] = window['AscVisio'] || {}; window['AscFormat'] = window['AscFormat'] || {}; window['AscWord'] = window['AscWord'] || {}; window['AscVisio'].c_oVsdxSheetStorageKind = c_oVsdxSheetStorageKind; window['AscVisio'].SheetStorageAndStyles = SheetStorageAndStyles; window['AscVisio'].Text_Type = Text_Type; window['AscVisio'].Data_Type = Data_Type; window['AscVisio'].ForeignData_Type = ForeignData_Type; window['AscVisio'].Trigger_Type = Trigger_Type; window['AscVisio'].Row_Type = Row_Type; window['AscVisio'].Cell_Type = Cell_Type; window['AscVisio'].Shape_Type = Shape_Type; window['AscVisio'].Section_Type = Section_Type; window['AscVisio'].Page_Type = Page_Type; window['AscVisio'].PageSheet_Type = PageSheet_Type; window['AscVisio'].DocumentSheet_Type = DocumentSheet_Type; window['AscVisio'].StyleSheet_Type = StyleSheet_Type; window['AscVisio'].ShapeSheet_Type = ShapeSheet_Type; window['AscVisio'].Icon_Type = Icon_Type; window['AscVisio'].PropLocker = PropLocker; window['AscVisio'].createKeyFromSheetObject = createKeyFromSheetObject; })(window, window.document);