1 /**
  2  * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global odf, runtime, core, Node*/
 26 
 27 /**
 28  * Helper object for generating unique object names. Each name is only reported once per instance,
 29  * irrespective of whether it is actually then inserted into the dom tree in the odfContainer.
 30  *
 31  * There is expected to be a single instance of the object name generator created per session. This is necessary
 32  * to close a potential race condition when generating unique names for operations. As there is no guarantee
 33  * when a given op is executed, it is insufficient to simply rely on all previously generated names to be now present
 34  * in the document definitions. To cope with this, the names generated by this instance are also cached for
 35  * the lifetime of this object.
 36  *
 37  * Failure to do this could result in a situation like the following
 38  * 1. SessionController generates new OpAddStyle & adds to session's queue
 39  * 2. SessionController generates another OpAddStyle & adds to session's queue
 40  *
 41  * At step 2, as the session's queue implementation has no requirement that it immediately executes the operation from
 42  * step 1, it is likely that the style created in step 1 is not yet present in the document DOM.
 43  *
 44  * @param {!odf.OdfContainer} odfContainer
 45  * @param {!string} memberId
 46  * @constructor
 47  */
 48 odf.ObjectNameGenerator = function ObjectNameGenerator(odfContainer, memberId) {
 49     "use strict";
 50     var stylens = odf.Namespaces.stylens,
 51         drawns = odf.Namespaces.drawns,
 52         xlinkns = odf.Namespaces.xlinkns,
 53         domUtils = new core.DomUtils(),
 54         utils = new core.Utils(),
 55         memberIdHash = utils.hashString(memberId),
 56         styleNameGenerator = null,
 57         frameNameGenerator = null,
 58         imageNameGenerator = null,
 59         existingFrameNames = {},
 60         existingImageNames = {};
 61 
 62     /**
 63      * @param {string} prefix Prefix to use for unique name generation
 64      * @param {function():!Object.<string,boolean>} findExistingNames
 65      * @constructor
 66      */
 67     function NameGenerator(prefix, findExistingNames) {
 68         var /**@type{!Object.<string,boolean>}*/
 69             reportedNames = {};
 70         /**
 71          * Generate a unique name
 72          * @return {string}
 73          */
 74         this.generateName = function () {
 75             var existingNames = findExistingNames(),
 76                 startIndex = 0,
 77                 name;
 78             do {
 79                 name = prefix + startIndex;
 80                 startIndex += 1;
 81             } while (reportedNames[name] || existingNames[name]);
 82             reportedNames[name] = true;
 83             return name;
 84         };
 85     }
 86 
 87     /**
 88      * Get all the style names defined in the style:style elements of the
 89      * current document including automatic styles.
 90      *
 91      * @return {!Object.<string,boolean>}
 92      */
 93     function getAllStyleNames() {
 94         var styleElements = [
 95                 odfContainer.rootElement.automaticStyles,
 96                 odfContainer.rootElement.styles
 97             ],
 98             styleNames = {};
 99 
100         /**
101          * @param {!Element} styleListElement
102          */
103         function getStyleNames(styleListElement) {
104             var e = styleListElement.firstElementChild;
105             while (e) {
106                 if (e.namespaceURI === stylens && e.localName === "style") {
107                     styleNames[e.getAttributeNS(stylens, 'name')] = true;
108                 }
109                 e = e.nextElementSibling;
110             }
111         }
112         styleElements.forEach(getStyleNames);
113         return styleNames;
114     }
115 
116     /**
117      * Generate a unique style name across the style:style elements
118      * @return {!string}
119      */
120     this.generateStyleName = function () {
121         if (styleNameGenerator === null) {
122             styleNameGenerator = new NameGenerator(
123                 "auto" + memberIdHash + "_",
124                 function () {
125                     // TODO: can cache the existing names once we fix the todo in formatting.applyStyle
126                     return getAllStyleNames();
127                 }
128             );
129         }
130         return styleNameGenerator.generateName();
131     };
132     /**
133      * Generate a unique frame name
134      * @return {!string}
135      */
136     this.generateFrameName = function () {
137         if (frameNameGenerator === null) {
138             var nodes = domUtils.getElementsByTagNameNS(odfContainer.rootElement.body, drawns, 'frame');
139             nodes.forEach(function (frame) {
140                 existingFrameNames[frame.getAttributeNS(drawns, 'name')] = true;
141             });
142 
143             frameNameGenerator = new NameGenerator(
144                 "fr" + memberIdHash + "_",
145                 function () {
146                     return existingFrameNames;
147                 }
148             );
149         }
150         return frameNameGenerator.generateName();
151     };
152     /**
153      * Generate a unique image name
154      * @return {!string}
155      */
156     this.generateImageName = function () {
157         if (imageNameGenerator === null) {
158             var nodes = domUtils.getElementsByTagNameNS(odfContainer.rootElement.body, drawns, 'image');
159             nodes.forEach(function (image) {
160                 var path = image.getAttributeNS(xlinkns, 'href');
161                 path = path.substring("Pictures/".length, path.lastIndexOf('.'));
162                 existingImageNames[path] = true;
163             });
164 
165             imageNameGenerator = new NameGenerator(
166                 "img" + memberIdHash + "_",
167                 function () {
168                     return existingImageNames;
169                 }
170             );
171         }
172         return imageNameGenerator.generateName();
173     };
174 };
175