diff --git a/common/HistoryCommon.js b/common/HistoryCommon.js index b9c8e8db81..20a686be59 100644 --- a/common/HistoryCommon.js +++ b/common/HistoryCommon.js @@ -1438,7 +1438,10 @@ window['AscDFH'].historyitem_type_Endnotes = 69 << 16; window['AscDFH'].historyitem_type_ParagraphPermStart = 70 << 16; window['AscDFH'].historyitem_type_ParagraphPermEnd = 71 << 16; - + window['AscDFH'].historyitem_type_CustomXML_Add = 72 << 16; + window['AscDFH'].historyitem_type_ChangeCustomXML = 73 << 16; + window['AscDFH'].historyitem_type_CustomXML_Remove = 74 << 16; + window['AscDFH'].historyitem_type_CommonShape = 1000 << 16; // Этот класс добавлен для элементов, у которых нет конкретного класса window['AscDFH'].historyitem_type_ColorMod = 1001 << 16; @@ -4625,6 +4628,7 @@ window['AscDFH'].historydescription_OForm_RoleFilled = 0x01c6; window['AscDFH'].historydescription_OForm_CompletePreparation = 0x01c7; window['AscDFH'].historydescription_Presentation_SetPreserveSlideMaster = 0x01c8; + window['AscDFH'].historydescription_Document_AddCustomXML = 0x01c9; // pdf window['AscDFH'].historydescription_Pdf_AddAnnot = 0x29a; diff --git a/common/TableId.js b/common/TableId.js index d624a86983..f6dfd507c6 100644 --- a/common/TableId.js +++ b/common/TableId.js @@ -353,6 +353,7 @@ this.m_oFactoryClass[AscDFH.historyitem_type_CEffectProperties ] = AscFormat.CEffectProperties; this.m_oFactoryClass[AscDFH.historyitem_type_ParagraphPermStart] = AscWord.ParagraphPermStart; this.m_oFactoryClass[AscDFH.historyitem_type_ParagraphPermEnd ] = AscWord.ParagraphPermEnd; + this.m_oFactoryClass[AscDFH.historyitem_type_CustomXML_Add ] = AscWord.CustomXml; if (window['AscCommonSlide']) { diff --git a/configs/word.json b/configs/word.json index d26978da61..89b2d8ef1c 100644 --- a/configs/word.json +++ b/configs/word.json @@ -330,6 +330,7 @@ "word/Editor/custom-xml/custom-xml.js", "word/Editor/custom-xml/custom-xml-manager.js", "word/Editor/custom-xml/data-binding.js", + "word/Editor/custom-xml/custom-xml-changes.js", "word/Editor/Merge.js", "word/Drawing/Graphics.js", diff --git a/word/Editor/Serialize2.js b/word/Editor/Serialize2.js index 98d9516d30..3315a47fd4 100644 --- a/word/Editor/Serialize2.js +++ b/word/Editor/Serialize2.js @@ -6859,9 +6859,6 @@ function BinaryDocumentTableWriter(memory, doc, oMapCommentId, oNumIdMap, copyPa } if (undefined !== val.storeItemCheckSum) { - //let strCustomXmlContent = this.Document.customXml.getContentByDataBinding(val); - //val.recalculateCheckSum(strCustomXmlContent); - this.memory.WriteByte(c_oSerSdt.StoreItemCheckSum); this.memory.WriteString2(val.storeItemCheckSum); } @@ -7755,7 +7752,7 @@ function BinaryCustomsTableWriter(memory, doc, customXmlManager) var oThis = this; for(var i = 0; i < customXml.uri.length; ++i){ this.bs.WriteItem(c_oSerCustoms.Uri, function () { - oThis.memory.WriteString3(customXml.uri[i]); + oThis.memory.WriteString3(customXml.getNamespaceUri()); }); } if (null !== customXml.itemId) { @@ -16299,7 +16296,7 @@ function Binary_CustomsTableReader(doc, oReadResult, stream) { this.ReadCustomContent = function(type, length, custom) { var res = c_oSerConstants.ReadOk; if (c_oSerCustoms.Uri === type) { - custom.uri.push(this.stream.GetString2LE(length)); + custom.setNamespaceUri(this.stream.GetString2LE(length)); } else if (c_oSerCustoms.ItemId === type) { custom.itemId = this.stream.GetString2LE(length); } else if (c_oSerCustoms.ContentA === type) { diff --git a/word/Editor/StructuredDocumentTags/InlineLevel.js b/word/Editor/StructuredDocumentTags/InlineLevel.js index 1d14d04acb..d2785858db 100644 --- a/word/Editor/StructuredDocumentTags/InlineLevel.js +++ b/word/Editor/StructuredDocumentTags/InlineLevel.js @@ -3769,6 +3769,11 @@ CInlineLevelSdt.prototype.CorrectSingleLineFormContent = function() } } }; +CInlineLevelSdt.prototype.GetDataBinding = function() +{ + return this.Pr.DataBinding; +}; + //--------------------------------------------------------export-------------------------------------------------------- window['AscCommonWord'] = window['AscCommonWord'] || {}; diff --git a/word/Editor/StructuredDocumentTags/SdtBase.js b/word/Editor/StructuredDocumentTags/SdtBase.js index a548d3d10e..babb029c55 100644 --- a/word/Editor/StructuredDocumentTags/SdtBase.js +++ b/word/Editor/StructuredDocumentTags/SdtBase.js @@ -1186,21 +1186,27 @@ CSdtBase.prototype.checkDataBinding = function() { let logicDocument = this.GetLogicDocument(); if (!logicDocument || !this.Pr.DataBinding) - return; + return false; let customXmlManager = logicDocument.getCustomXmlManager(); if (!customXmlManager || !customXmlManager.isSupported()) - return; + return false; let content = customXmlManager.getContentByDataBinding(this.Pr.DataBinding, this); if (!content) - return; - + return false + + if (content.attribute) + content = content.content.attributes[content.attribute] + else + content = content.getText(); + + if (this.IsPicture()) { let allDrawings = this.GetAllDrawingObjects(); if (!allDrawings.length) - return; + return false; let drawing = allDrawings[0]; let imageData = "data:image/jpeg;base64," + content; @@ -1221,12 +1227,14 @@ CSdtBase.prototype.checkDataBinding = function() let checkBoxPr = this.Pr.CheckBox.Copy(); checkBoxPr.SetChecked(true); this.SetCheckBoxPr(checkBoxPr) + this.private_UpdateCheckBoxContent(); } else if (content === "false" || content === "0") { let checkBoxPr = this.Pr.CheckBox.Copy(); checkBoxPr.SetChecked(false); this.SetCheckBoxPr(checkBoxPr) + this.private_UpdateCheckBoxContent() } } else if (this.IsDatePicker()) @@ -1245,7 +1253,7 @@ CSdtBase.prototype.checkDataBinding = function() this.private_UpdateDatePickerContent(); } } - else if (this.IsDropDownList() || this.IsComboBox() || this.Pr.Text === true) + else if (this.IsDropDownList() || this.IsComboBox() || ((this instanceof CBlockLevelSdt && this.Pr.Text === true) || (this instanceof CInlineLevelSdt))) { if (typeof content === "string") this.SetInnerText(content); @@ -1254,8 +1262,15 @@ CSdtBase.prototype.checkDataBinding = function() { let customXmlManager = logicDocument.getCustomXmlManager(); let arrContent = customXmlManager.proceedLinearXMl(content); - this.fillContentWithDataBinding(arrContent); + + let strContent = arrContent[0].GetText().trim(); + if (strContent === "" && content.length > 0) + this.SetInnerText(content); + else + this.fillContentWithDataBinding(arrContent); } + + return true; }; CSdtBase.prototype.canFillWithComplexDataBindingContent = function() { diff --git a/word/Editor/custom-xml/custom-xml-changes.js b/word/Editor/custom-xml/custom-xml-changes.js new file mode 100644 index 0000000000..08facaa84b --- /dev/null +++ b/word/Editor/custom-xml/custom-xml-changes.js @@ -0,0 +1,207 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * 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"; + +AscDFH.changesFactory[AscDFH.historyitem_type_CustomXML_Add] = CChangesCustomXmlAdd; +AscDFH.changesFactory[AscDFH.historyitem_type_CustomXML_Remove] = CChangesCustomXmlRemove; + +AscDFH.changesFactory[AscDFH.historyitem_type_ChangeCustomXML] = CChangesCustomXMLChange; + +AscDFH.changesRelationMap[AscDFH.historyitem_type_ChangeCustomXML] = [ + AscDFH.historyitem_type_ChangeCustomXML +]; + +AscDFH.changesRelationMap[AscDFH.historyitem_type_CustomXML_Add] = [ + AscDFH.historyitem_type_CustomXML_Add, + AscDFH.historyitem_type_CustomXML_Remove +]; + +AscDFH.changesRelationMap[AscDFH.historyitem_type_CustomXML_Remove] = [ + AscDFH.historyitem_type_CustomXML_Add, + AscDFH.historyitem_type_CustomXML_Remove +]; + +/** + * @constructor + * @extends {AscDFH.CChangesBaseProperty} + */ +function CChangesCustomXMLChange(Class, Old, New) +{ + AscDFH.CChangesBaseProperty.call(this, Class, Old, New); +} +CChangesCustomXMLChange.prototype = Object.create(AscDFH.CChangesBaseProperty.prototype); +CChangesCustomXMLChange.prototype.constructor = CChangesCustomXMLChange; +CChangesCustomXMLChange.prototype.Type = AscDFH.historyitem_type_ChangeCustomXML; +CChangesCustomXMLChange.prototype.WriteToBinary = function(Writer) +{ + // Variable : New data + // Variable : Old data + this.New.Write_ToBinary2(Writer); + this.Old.Write_ToBinary2(Writer); +}; +CChangesCustomXMLChange.prototype.ReadFromBinary = function(Reader) +{ + // Variable : New data + // Variable : Old data + this.New = new AscWord.CustomXmlContent(undefined, undefined, this.Class); + this.Old = new AscWord.CustomXmlContent(); + this.New.Read_FromBinary2(Reader); + this.Old.Read_FromBinary2(Reader); +}; +CChangesCustomXMLChange.prototype.private_SetValue = function(Value) +{ + this.Class.content = Value; + + if (this.Class.oContentLink) + this.Class.oContentLink.checkDataBinding(); +}; + +/** + * @constructor + * @extends {AscDFH.CChangesBase} + */ +function CChangesCustomXmlAdd(Class, Id, xml) +{ + AscDFH.CChangesBase.call(this, Class); + + this.Id = Id; + this.xml = xml; +} +CChangesCustomXmlAdd.prototype = Object.create(AscDFH.CChangesBase.prototype); +CChangesCustomXmlAdd.prototype.constructor = CChangesCustomXmlAdd; +CChangesCustomXmlAdd.prototype.Type = AscDFH.historyitem_type_CustomXML_Add; +CChangesCustomXmlAdd.prototype.Undo = function() +{ + let oXml = this.Class.m_arrXmlById[this.Id]; + if (oXml) + { + delete this.Class.m_arrXmlById[this.Id]; + for (let i = 0; i < this.Class.xml.length; i++) + { + if (this.Class.xml[i] === oXml) + { + this.Class.xml.splice(i, 1); + } + } + } +}; +CChangesCustomXmlAdd.prototype.Redo = function() +{ + this.Class.m_arrXmlById[this.Id] = this.xml; + this.Class.xml.push(this.xml); +}; +CChangesCustomXmlAdd.prototype.WriteToBinary = function(Writer) +{ + // String : Id customXML + Writer.WriteString2(this.Id); +}; +CChangesCustomXmlAdd.prototype.ReadFromBinary = function(Reader) +{ + // String : Id customXML + this.Id = Reader.GetString2(); + this.xml = AscCommon.g_oTableId.Get_ById(this.Id); +}; +CChangesCustomXmlAdd.prototype.CreateReverseChange = function() +{ + return new CChangesCustomXmlRemove(this.Class, this.Id, this.xml); +}; +CChangesCustomXmlAdd.prototype.Merge = function(oChange) +{ + if (this.Class !== oChange.Class) + return true; + + if ((AscDFH.historyitem_type_CustomXML_Add === oChange.Type || AscDFH.historyitem_type_CustomXML_Remove === oChange.Type) && this.Id === oChange.Id) + return false; + + return true; +}; + +/** + * @constructor + * @extends {AscDFH.CChangesBase} + */ +function CChangesCustomXmlRemove(Class, Id, xml) +{ + AscDFH.CChangesBase.call(this, Class); + + this.Id = Id; + this.xml = xml; +} +CChangesCustomXmlRemove.prototype = Object.create(AscDFH.CChangesBase.prototype); +CChangesCustomXmlRemove.prototype.constructor = CChangesCustomXmlRemove; +CChangesCustomXmlRemove.prototype.Type = AscDFH.historyitem_type_CustomXML_Remove; +CChangesCustomXmlRemove.prototype.Undo = function() +{ + this.Class.m_arrXmlById[this.Id] = this.xml; + this.Class.xml.push(this.xml); +}; +CChangesCustomXmlRemove.prototype.Redo = function() +{ + let xml = this.Class.m_arrXmlById[this.Id]; + if (xml) + { + delete this.Class.m_arrXmlById[this.Id]; + for (let i = 0; i < this.Class.xml.length; i++) + { + if (this.Class.xml[i] === xml) + { + this.Class.xml.splice(i, 1); + } + } + } +}; +CChangesCustomXmlRemove.prototype.WriteToBinary = function(Writer) +{ + // String : Id customXML + Writer.WriteString2(this.Id); +}; +CChangesCustomXmlRemove.prototype.ReadFromBinary = function(Reader) +{ + // String : Id customXML + this.Id = Reader.GetString2(); + this.xml = AscCommon.g_oTableId.Get_ById(this.Id); +}; +CChangesCustomXmlRemove.prototype.CreateReverseChange = function() +{ + return new CChangesCustomXmlAdd(this.Class, this.Id, this.xml); +}; +CChangesCustomXmlRemove.prototype.Merge = function(oChange) +{ + if (this.Class !== oChange.Class) + return true; + + if ((AscDFH.historyitem_type_CustomXML_Add === oChange.Type || AscDFH.historyitem_type_CustomXML_Remove === oChange.Type) && this.Id === oChange.Id) + return false; + + return true; +}; diff --git a/word/Editor/custom-xml/custom-xml-manager.js b/word/Editor/custom-xml/custom-xml-manager.js index b354a429a6..8771b7140f 100644 --- a/word/Editor/custom-xml/custom-xml-manager.js +++ b/word/Editor/custom-xml/custom-xml-manager.js @@ -41,14 +41,68 @@ */ function CustomXmlManager(document) { + this.Id = AscCommon.g_oIdCounter.Get_NewId(); this.document = document; this.xml = []; + this.m_arrXmlById = {}; + + AscCommon.g_oTableId.Add(this, this.Id); } - CustomXmlManager.prototype.add = function(customXml) + CustomXmlManager.prototype.Get_Id = function() { - // TODO: Надо будет сделать этот метод с сохранением в историю, когда - // будем реализовывать возможность добавления таких xml во время работы - this.xml.push(customXml); + return this.Id; + }; + CustomXmlManager.prototype.add = function(oCustomXml) + { + let sId = oCustomXml.GetId(); + AscCommon.History.Add(new CChangesCustomXmlAdd(this, sId, oCustomXml)); + + this.m_arrXmlById[sId] = oCustomXml; + this.xml.push(oCustomXml); + }; + CustomXmlManager.prototype.isXmlExist = function(uid, prefix) + { + for (let nXmlCounter = 0; nXmlCounter < this.getCount(); nXmlCounter++) + { + let customXml = this.getCustomXml(nXmlCounter); + if (uid === customXml.itemId || customXml.checkUrl(prefix)) + return true; + } + return false; + }; + CustomXmlManager.prototype.getExactXml = function (uId, prefix) + { + for (let nXmlCounter = 0; nXmlCounter < this.getCount(); nXmlCounter++) + { + let customXml = this.getCustomXml(nXmlCounter); + if (uId === customXml.itemId || customXml.checkUrl(prefix)) + return customXml; + } + }; + CustomXmlManager.prototype.getXmlByNamespace = function (namespace) + { + let arrXml = []; + for (let nXmlCounter = 0; nXmlCounter < this.getCount(); nXmlCounter++) + { + let customXml = this.getCustomXml(nXmlCounter); + if (customXml.checkUrl(namespace)) + arrXml.push(customXml); + } + return arrXml; + }; + CustomXmlManager.prototype.deleteExactXml = function (uId, prefix) + { + for (let nXmlCounter = 0; nXmlCounter < this.getCount(); nXmlCounter++) + { + let oCustomXml = this.getCustomXml(nXmlCounter); + if (uId === oCustomXml.itemId || oCustomXml.checkUrl(prefix)) + { + AscCommon.History.Add(new CChangesCustomXmlRemove(this, oCustomXml.Id, oCustomXml)); + this.xml.splice(nXmlCounter, 1); + return true; + } + } + return false; }; CustomXmlManager.prototype.getCount = function() { @@ -58,70 +112,11 @@ { return this.xml[index]; }; - - /** - * Find element/attribute of CustomXMl by xpath string - * @param root {AscWord.CustomXmlContent} - * @param xpath {string} - * @return {{attribute: string, content: AscWord.CustomXmlContent}} - */ - CustomXmlManager.prototype.findElementByXPath = function (root, xpath) + CustomXmlManager.prototype.createCustomXml = function(content, uri) { - let arrParts = xpath.split('/'); - let currentElement = root; - - arrParts.shift(); // Убираем пустой первый элемент - - for (let i = 0; i < arrParts.length; i++) - { - let namespaceAndTag, - index, - tagName, - part = arrParts[i]; - - if (part.includes("@")) - { - let strAttributeName = part.slice(1); - return { - content: currentElement, - attribute: strAttributeName, - }; - } - else if (part.includes("[")) - { - namespaceAndTag = part.split('[')[0]; - let partBeforeCloseBracket = part.split(']')[0]; - index = partBeforeCloseBracket.slice(-1) - 1; - } - else - { - namespaceAndTag = part; - index = 0; - } - - tagName = namespaceAndTag.includes(":") - ? namespaceAndTag.split(':')[1] - : namespaceAndTag; - - let matchingChildren = currentElement.content.filter(function (child) { - let arr = child.name.split(":"); - - if (arr.length > 1) - return arr[1] === tagName; - else - return arr[0] === tagName; - }); - - if (matchingChildren.length <= index) - break; // Элемент не найден - - currentElement = matchingChildren[index]; - } - - return { - content: currentElement, - attribute: undefined, - }; + let oXML = new AscWord.CustomXml(this, false, uri ? [uri] : null, content); + this.add(oXML); + return oXML; }; /** * Get custom xml data of content control by data binding property @@ -139,13 +134,10 @@ if (dataBinding.storeItemID === customXml.itemId || customXml.checkUrl(dataBinding.prefixMappings)) { let xPath = dataBinding.xpath; - let oFindEl = this.findElementByXPath(customXml.content, xPath); - let content = oFindEl.content; - let strAttribute = oFindEl.attribute; + let arrFind = customXml.findElementByXPath(xPath); - return (undefined !== strAttribute) - ? content.attribute[strAttribute] - : content.textContent; + if (arrFind.length) + return arrFind[0]; } } }; @@ -163,14 +155,10 @@ if (dataBinding.storeItemID === customXml.itemId) { let xPath = dataBinding.xpath; - let oFindEl = this.findElementByXPath(customXml.content, xPath); - let oContent = oFindEl.content; - let strAttribute = oFindEl.attribute; - - if (strAttribute) - oContent.setAttribute(strAttribute, data); - else - oContent.setTextContent(data); + let arrFind = customXml.findElementByXPath(xPath); + + if (arrFind.length) + arrFind[0].setTextContent(data); } } }; @@ -196,12 +184,7 @@ else this.setContentByDataBinding(dataBinding, contentControl.GetInnerText()); }; - /** - * Write linear xml data of content control in CustomXML - * @param oCC {CBlockLevelSdt} - */ - CustomXmlManager.prototype.updateRichTextCustomXML = function (oCC) - { + CustomXmlManager.prototype.GetRichTextContentToWrite = function (oCC, fCallback) { function replaceSubstring(originalString, startPoint, endPoint, insertionString) { if (startPoint < 0 || endPoint >= originalString.length || startPoint > endPoint) @@ -213,7 +196,7 @@ return prefix + insertionString + suffix; } - AscCommon.ExecuteNoHistory(function() { + return AscCommon.ExecuteNoHistory(function() { let doc = new AscWord.CDocument(null, false); let oSdtContent = oCC.GetContent().Copy(); let jsZlib = new AscCommon.ZLib(); @@ -276,9 +259,32 @@ outputUString += ""; outputUString = outputUString.replaceAll("<", "<"); outputUString = outputUString.replaceAll(">", ">"); - this.setContentByDataBinding(oCC.Pr.DataBinding, outputUString); + + if (fCallback) + fCallback(outputUString, this); + + return outputUString; }, this.document, this, []); }; + CustomXmlManager.prototype.GetTextContentToWrite = function (contentControl) { + if (!this.isSupported()) + return; + + if (contentControl instanceof AscWord.CBlockLevelSdt) + return this.GetRichTextContentToWrite(contentControl); + else if (contentControl.GetInnerText) + return contentControl.GetInnerText(); + }; + /** + * Write linear xml data of content control in CustomXML + * @param oCC {CBlockLevelSdt} + */ + CustomXmlManager.prototype.updateRichTextCustomXML = function (oCC) + { + this.GetRichTextContentToWrite(oCC, function (resultStr, oThis) { + oThis.setContentByDataBinding(oCC.Pr.DataBinding, resultStr); + }) + }; /** * Proceed linear xml from CustomXMl attribute or element for fill content control * @param strLinearXML {string} @@ -298,7 +304,8 @@ strLinearXML = strLinearXML.replaceAll("&", "&"); strLinearXML = strLinearXML.replaceAll(""", "\""); strLinearXML = strLinearXML.replaceAll("'", "'"); - + strLinearXML = strLinearXML.replaceAll("'", "\""); + let zLib = new AscCommon.ZLib; zLib.create(); zLib.addFile('[Content_Types].xml', AscCommon.Utf8.encode('' + @@ -424,6 +431,10 @@ CustomXmlManager.prototype.isSupported = function() { return window['Asc'] && window['Asc']['Addons'] && true === window['Asc']['Addons']['ooxml']; + }; + CustomXmlManager.prototype.Refresh_RecalcData = function(Data) + { + }; //--------------------------------------------------------export---------------------------------------------------- AscWord.CustomXmlManager = CustomXmlManager; diff --git a/word/Editor/custom-xml/custom-xml.js b/word/Editor/custom-xml/custom-xml.js index 2bd517edaa..e9d3959c08 100644 --- a/word/Editor/custom-xml/custom-xml.js +++ b/word/Editor/custom-xml/custom-xml.js @@ -43,20 +43,125 @@ * Класс представляющий CustomXML * @constructor */ - function CustomXml(uri, itemId, content, oContentLink) + function CustomXml(oParent, itemId, nsManager, content) { - this.uri = uri ? uri : []; - this.itemId = itemId ? itemId : ""; - this.content = content ? content : null; - this.oContentLink = oContentLink ? oContentLink : null; + this.Id = AscCommon.g_oIdCounter.Get_NewId(); + this.Parent = oParent; + this.itemId = itemId ? itemId : this.setItemId(); + this.content = null; + this.nsManager = (nsManager && nsManager instanceof CustomXmlPrefixMappings) + ? nsManager + : new CustomXmlPrefixMappings(nsManager); + + this.addContentByXMLString(content); + + AscCommon.g_oTableId.Add(this, this.Id); } + CustomXml.prototype.Copy = function () + { + let strXml = this.getText(); + let oCopy = new CustomXml( + this.Parent, + this.itemId, + this.nsManager, + undefined + ); + + oCopy.addContentByXMLString(strXml); + + return oCopy; + }; + CustomXml.prototype.Delete = function () + { + if (this.Parent) + { + this.Parent.deleteExactXml(this.itemId); + return true; + } + return false; + }; + CustomXml.prototype.Get_Id = function () + { + return this.Id; + }; + CustomXml.prototype.GetId = function() + { + return this.Id; + }; + CustomXml.prototype.Refresh_RecalcData = function(Data) + { + // Ничего не делаем (если что просто будет перерисовка) + }; + CustomXml.prototype.Write_ToBinary2 = function(Writer) + { + Writer.WriteLong(AscDFH.historyitem_type_CustomXML_Add); + + // String : Id + // Long : Количество элементов + // Array of Strings : массив с Id элементов + + Writer.WriteString2(this.Id); + Writer.WriteString2(this.itemId); + Writer.WriteString2(this.getText()); + this.nsManager.Write_ToBinary2(Writer); + }; + CustomXml.prototype.Read_FromBinary2 = function(Reader) + { + // String : Id + // Long : Количество элементов + // Array of Strings : массив с Id элементов + this.Id = Reader.GetString2(); + this.itemId = Reader.GetString2(); + this.addContentByXMLString(Reader.GetString2()); + + this.Parent = editor.WordControl.m_oLogicDocument.getCustomXmlManager(); + this.nsManager.Read_FromBinary2(Reader); + }; + CustomXml.prototype.Write_ToBinary = function(Writer) + { + this.Write_ToBinary2(Writer); + }; + CustomXml.prototype.Read_FromBinary = function (Reader) { + this.Read_FromBinary2(Reader); + }; + /** + * Set UID of CustomXML + * @param {uId} itemId + */ + CustomXml.prototype.setItemId = function () + { + return AscCommon.CreateGUID(); + }; + /** + * Set UID of CustomXML by given data + * @param itemUId {string} + */ + CustomXml.prototype.setItemIdManually = function (itemUId) + { + this.itemId = itemUId; + }; + /** + * Add given uri to CustomXMl uri list + * @param uri {string} + */ + CustomXml.prototype.addNamespace = function(prefix, ns) + { + if (!prefix) + prefix = "defaultNamespace"; + + this.nsManager.addNamespace(prefix, ns); + return true; + }; /** * Get CustomXML data by string * @return {string} */ CustomXml.prototype.getText = function () { + if (this.content) return this.content.getStringFromBuffer(); + else + return ""; }; /** * Find url in uri array @@ -67,12 +172,7 @@ if (!str) return false; - for (let i = 0; i < this.uri.length; i++) - { - if (str.includes(this.uri[i])) - return true; - } - return false; + return this.nsManager.getPrefix(str) !== undefined; } /** * Add content of CustomXML @@ -89,99 +189,309 @@ }; CustomXml.prototype.addContentByXMLString = function (strCustomXml) { - let nXmlHeaderStart = strCustomXml.indexOf('', nXmlHeaderStart); - let strXmlHeader = null; - if (nXmlHeaderStart !== -1 && nXmlHeaderEnd !== -1) + if (strCustomXml === undefined) + return; + if (strCustomXml instanceof CustomXmlContent) + strCustomXml = content.getStringFromBuffer(); + + this.content = CustomXmlCreateContent(strCustomXml, this); + }; + CustomXml.prototype.findElementByXPath = function (xpath) + { + // add namespace support in the future + + let arrParts = xpath.split('/'); + let currentElement = this.content; + let arrResult = []; + arrParts.shift(); + + for (let i = 0; i < arrParts.length; i++) { - strXmlHeader = strCustomXml.substring(nXmlHeaderStart, nXmlHeaderEnd + "?>".length); - strCustomXml = strCustomXml.substring(nXmlHeaderEnd + '?>'.length, strCustomXml.length); - } + let namespaceAndTag, + index, + tagName, + part = arrParts[i]; - let oStax = new StaxParser(strCustomXml), - rootContent = new CustomXmlContent(null); - - if (strXmlHeader !== null) - rootContent.xmlQuestionHeader = strXmlHeader; - - while (oStax.Read()) - { - switch (oStax.GetEventType()) { - case EasySAXEvent.CHARACTERS: - rootContent.addTextContent(oStax.text); - break; - case EasySAXEvent.END_ELEMENT: - rootContent = rootContent.getParent(); - break; - case EasySAXEvent.START_ELEMENT: - let name = oStax.GetName(); - let childElement = rootContent.addContent(name); - - while (oStax.MoveToNextAttribute()) - { - let attributeName = oStax.GetName(); - let attributeValue = oStax.GetValue(); - childElement.addAttribute(attributeName, attributeValue); - } - - rootContent = childElement; - break; + if (part.includes("*")) + { + currentElement.childNodes.forEach(function (node) {arrResult.push(node)}) + return arrResult; } + + if (part.includes("@")) + { + let strAttributeName = part.slice(1); + + return { + content: currentElement, + attribute: strAttributeName + } + } + else if (part.includes("[")) + { + namespaceAndTag = part.split('[')[0]; + let partBeforeCloseBracket = part.split(']')[0]; + index = partBeforeCloseBracket.slice(-1) - 1; + } + else + { + namespaceAndTag = part; + index = 0; + } + + tagName = namespaceAndTag.includes(":") + ? namespaceAndTag.split(':')[1] + : namespaceAndTag; + + let matchingChildren = currentElement.childNodes.filter(function (child) { + let arr = child.nodeName.split(":"); + + if (arr.length > 1) + return arr[1] === tagName; + else + return arr[0] === tagName; + }); + + if (matchingChildren.length <= index) + break; + + currentElement = matchingChildren[index]; } - this.content = rootContent; - } - + arrResult.push(currentElement); + return arrResult; + }; + CustomXml.prototype.deleteAttribute = function(xPath, name) + { + return this.Change(function (){ + let nodes = this.findElementByXPath(xPath); + if (nodes.length) + { + let el = nodes[0]; + return el.deleteAttribute(name); + } + return false; + }, this); + }; + CustomXml.prototype.insertAttribute = function(xPath, name, value) + { + return this.Change(function (){ + let nodes = this.findElementByXPath(xPath); + if (nodes.length) + { + let el = nodes[0]; + if (!el.attributes[name]) + return el.setAttribute(name, value); + } + return false; + }, this); + }; + CustomXml.prototype.updateAttribute = function(xPath, name, value) + { + return this.Change(function (){ + let nodes = this.findElementByXPath(xPath); + if (nodes.length) + { + let el = nodes[0]; + if (el.attributes[name]) + return el.setAttribute(name, value); + } + return false; + }, this); + }; + CustomXml.prototype.deleteElement = function (xPath) + { + return this.Change(function (){ + let nodes = this.findElementByXPath(xPath); + if (nodes.length) + { + let el = nodes[0]; + return el.delete(); + } + return false; + }, this); + }; + CustomXml.prototype.updateElement = function(xPath, xmlStr) + { + return this.Change(function (){ + let nodes = this.findElementByXPath(xPath); + if (nodes.length) + { + let el = nodes[0]; + el.setXml(xmlStr); + return true; + } + return false; + }, this); + }; + CustomXml.prototype.insertElement = function (xPath, xmlStr, index) + { + return this.Change(function (){ + let nodes = this.findElementByXPath(xPath); + if (nodes.length) + { + let el = nodes[0]; + el.addElement(xmlStr, index); + return true; + } + return false; + }, this); + }; + CustomXml.prototype.beforeChange = function () + { + this.lastContent = this.Copy(); + }; + CustomXml.prototype.afterChange = function () + { + let strLast = this.lastContent.getText(); + let strCurrentData = this.getText(); + + if (strLast !== strCurrentData) + AscCommon.History.Add(new CChangesCustomXMLChange(this, this.lastContent.content, this.content)); + + this.lastContent = undefined; + }; + CustomXml.prototype.Change = function (f, oThis) + { + this.beforeChange(); + let data = f.apply(oThis); + this.afterChange(); + return data; + }; + CustomXml.prototype.setNamespaceUri = function(ns) + { + this.nsManager.setNamespaceUri(ns); + }; + CustomXml.prototype.getNamespaceUri = function() + { + return this.nsManager.getNamespaceUri(); + }; + /** * @constructor */ - function CustomXmlContent(parent, name) + function CustomXmlContent(oParentNode, oNodeName, xml) { - this.parent = parent; - this.name = name ? name : ""; - this.content = []; - this.attribute = {}; - this.textContent = ""; + this.parentNode = oParentNode; + this.nodeName = oNodeName ? oNodeName : ""; + this.childNodes = []; + this.attributes = {}; + this.text = ""; this.xmlQuestionHeader = null; + this.xml = xml; + this.getNamespace = function () + { + const parts = this.nodeName.split(":"); + let prefix = parts.length === 2 ? parts[0] : null; + + if (prefix) + return this.xml.nsManager.getNamespace(prefix); + + return ""; + } + this.getNodeName = function () + { + return this.nodeName; + }; + this.getText = function () + { + return this.text; + }; + this.getChildNodesCount = function() + { + return this.childNodes.length; + }; + this.deleteChild = function (childNode) + { + this.childNodes = this.childNodes.filter(function (item) {return item !== childNode}); + }; + this.delete = function () + { + this.parentNode.deleteChild(this); + }; this.addAttribute = function (name, value) { - this.attribute[name] = value; + this.attributes[name] = value; }; - this.addContent = function (name) + this.getAttribute = function (name) { - let newItem = new CustomXmlContent(this, name); + return this.attributes[name]; + }; + this.addContent = function(name) + { + let newItem = new CustomXmlContent(this, name, this.xml); - this.content.push(newItem); + this.childNodes.push(newItem); return newItem; }; + this.addElement = function(xmlStr, index) + { + let newItem = new CustomXmlContent(this, name, this.xml); + newItem.setXml(xmlStr); + + if (index !== undefined) + this.childNodes.splice(index, 0, newItem); + else + this.childNodes.splice(this.childNodes.length, 0, newItem); + }; + this.setAttribute = function (attribute, value) + { + this.attributes[attribute] = value; + }; + this.deleteAttribute = function(name) + { + if (this.attributes[name]) + { + delete this.attributes[name]; + return true; + } + + return false; + }; this.getParent = function () { - if (this.parent) - return this.parent; + if (this.parentNode) + return this.parentNode; return null; }; this.addTextContent = function (text) { if (text !== "") - this.textContent += text; + this.text += text; }; this.setTextContent = function (str) { - this.textContent = str; + this.text = str; + return true; }; - this.setAttribute = function (attribute, value) + this.getInnerText = function () { - this.attribute[attribute] = value; - }; + let result = []; + + function GetText(node) + { + if (node.text) + result.push(node.text); + + for (let i = 0; i < node.childNodes.length; i++) + { + GetText(node.childNodes[i]); + } + } + + GetText(this); + + return result.join(""); + } this.getBuffer = function () { let writer = new AscCommon.CMemory(); function Write(content) { - if (content.name === "" && content.textContent === "" && content.content.length === 0) + if (content.nodeName === "" && content.text === "" && content.childNodes.length === 0) { writer.WriteXmlString(""); return; @@ -189,55 +499,82 @@ let current = null; - if (!content.name) + if (!content.nodeName) { if (content.xmlQuestionHeader !== null) writer.WriteXmlString(content.xmlQuestionHeader + "\n"); - current = content.content[0]; + current = content.childNodes[0]; } else { current = content; } - writer.WriteXmlNodeStart(current.name); + writer.WriteXmlNodeStart(current.nodeName); - let atr = Object.keys(current.attribute) + let atr = Object.keys(current.attributes) for (let i = 0; i < atr.length; i++) { let cur = atr[i]; - writer.WriteXmlAttributeStringEncode(cur, current.attribute[cur]); + writer.WriteXmlAttributeStringEncode(cur, current.attributes[cur]); } writer.WriteXmlAttributesEnd(); - for (let i = 0; i < current.content.length; i++) + for (let i = 0; i < current.childNodes.length; i++) { - Write(current.content[i]); + Write(current.childNodes[i]); } - if (current.textContent) - writer.WriteXmlString(current.textContent.toString().trim()); + if (current.text) + writer.WriteXmlString(current.text.toString().trim()); - writer.WriteXmlNodeEnd(current.name); + writer.WriteXmlNodeEnd(current.nodeName); } Write(this); return writer; }; - this.getStringFromBuffer = function () + this.getStringFromBuffer = function (isOnlyInner) { - let buffer = this.getBuffer(); + let buffer = this.getBuffer(isOnlyInner); let str = fromUtf8(buffer.GetData()); str = str.replaceAll(""", "\""); str = str.replaceAll("&", "&"); return str; }; + this.setXml = function(strXml) + { + let content = CustomXmlCreateContent(strXml, this.xml); + let data = content.childNodes[0]; + + this.nodeName = data.nodeName; + this.childNodes = data.childNodes; + this.attributes = data.attributes; + this.text = data.text; + + if (data.xmlQuestionHeader) + this.xmlQuestionHeader = data.xmlQuestionHeader; + }; + this.Write_ToBinary2 = function (Writer) + { + Writer.WriteString2( this.getStringFromBuffer() ); + }; + this.Read_FromBinary2 = function (Reader) + { + let oContent = AscWord.CustomXmlCreateContent(Reader.GetString2(), this.xml); + + this.parentNode = oContent.parentNode; + this.name = oContent.name; + this.childNodes = oContent.childNodes; + this.attributes = oContent.attributes; + this.textContent = oContent.textContent; + this.xmlQuestionHeader = oContent.xmlQuestionHeader; + }; } - - + // TODO: Временно вынес метод сюда, потом перенести надо будет // разница с AscCommon.UTF8ArrayToString, что тут на 0-символе не останавливаемся function fromUtf8(buffer, start, len) @@ -279,8 +616,139 @@ } return result; } + function CustomXmlCreateContent(strCustomXml, xml) + { + function getPrefix(xmlnsAttrName) { + if (xmlnsAttrName.startsWith("xmlns:")) + return xmlnsAttrName.slice(6); + return null; + } + + let nXmlHeaderStart = strCustomXml.indexOf('', nXmlHeaderStart); + let strXmlHeader = null; + if (nXmlHeaderStart !== -1 && nXmlHeaderEnd !== -1) + { + strXmlHeader = strCustomXml.substring(nXmlHeaderStart, nXmlHeaderEnd + "?>".length); + strCustomXml = strCustomXml.substring(nXmlHeaderEnd + '?>'.length, strCustomXml.length); + } + + let oStax = new StaxParser(strCustomXml), + rootContent = new CustomXmlContent(null, null, xml); + + if (strXmlHeader !== null) + rootContent.xmlQuestionHeader = strXmlHeader; + + while (oStax.Read()) + { + switch (oStax.GetEventType()) { + case EasySAXEvent.CHARACTERS: + rootContent.addTextContent(oStax.text); + break; + case EasySAXEvent.END_ELEMENT: + rootContent = rootContent.getParent(); + break; + case EasySAXEvent.START_ELEMENT: + let name = oStax.GetName(); + let childElement = rootContent.addContent(name); + + while (oStax.MoveToNextAttribute()) + { + let attributeName = oStax.GetName(); + let attributeValue = oStax.GetValue(); + + if (attributeName === 'xmlns' && rootContent.xml) + rootContent.xml.addNamespace("defaultNamespace", attributeValue); + + let prefix = getPrefix(attributeName); + if (prefix && rootContent.xml) + rootContent.xml.addNamespace(prefix, attributeValue); + + childElement.addAttribute(attributeName, attributeValue); + } + rootContent = childElement;break; + } + } + return rootContent; + } + function CustomXmlPrefixMappings() + { + this.urls = {}; + this.prefix = {}; + this.namespaceUri = ""; + + this.setNamespaceUri = function(ns) + { + this.namespaceUri = ns; + }; + this.getNamespaceUri = function() + { + return this.namespaceUri; + }; + this.addNamespace = function (prefix, ns) + { + if (ns !== "" && this.namespaceUri === "") + this.namespaceUri = ns; + + if (prefix && ns) + { + let prevPrefix = this.urls[ns]; + + this.urls[ns] = prefix; + + if (prevPrefix) + delete this.prefix[prevPrefix]; + + this.prefix[prefix] = ns; + } + }; + this.getNamespace = function(prefix) + { + return this.prefix[prefix]; + }; + this.getPrefix = function(urls) + { + return this.urls[urls]; + }; + this.Write_ToBinary2 = function(Writer) + { + Writer.WriteString2(this.namespaceUri); + + let urls = Object.keys(this.urls); + let Count = urls.length; + Writer.WriteLong(Count); + + for (let Index = 0; Index < Count; Index++) + Writer.WriteString2(urls[Index]); + + Writer.WriteLong(Count); + for (let Index = 0; Index < Count; Index++) + Writer.WriteString2(this.urls[urls[Index]]); + }; + this.Read_FromBinary2 = function(Reader) + { + this.namespaceUri = Reader.GetString2() + let url = []; + + var Count = Reader.GetLong(); + for (var Index = 0; Index < Count; Index++) + { + url.push(Reader.GetString2()); + this.urls[url[url.length - 1]] = ""; + } + + var Count = Reader.GetLong(); + for (var Index = 0; Index < Count; Index++) + { + let prefix = Reader.GetString2() + this.prefix[prefix] = url[Index]; + this.urls[url[Index]] = prefix; + } + }; + } //--------------------------------------------------------export---------------------------------------------------- - AscWord.CustomXml = CustomXml; - AscWord.CustomXmlContent = CustomXmlContent; + AscWord.CustomXml = CustomXml; + AscWord.CustomXmlContent = CustomXmlContent; + AscWord.CustomXmlCreateContent = CustomXmlCreateContent; })();