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         utils = new core.Utils(),
 54         memberIdHash = utils.hashString(memberId),
 55         styleNameGenerator = null,
 56         frameNameGenerator = null,
 57         imageNameGenerator = null,
 58         existingFrameNames = {},
 59         existingImageNames = {};
 60 
 61     /**
 62      * @param {string} prefix Prefix to use for unique name generation
 63      * @param {function():!Object.<string,boolean>} findExistingNames
 64      * @constructor
 65      */
 66     function NameGenerator(prefix, findExistingNames) {
 67         var /**@type{!Object.<string,boolean>}*/
 68             reportedNames = {};
 69         /**
 70          * Generate a unique name
 71          * @return {string}
 72          */
 73         this.generateName = function () {
 74             var existingNames = findExistingNames(),
 75                 startIndex = 0,
 76                 name;
 77             do {
 78                 name = prefix + startIndex;
 79                 startIndex += 1;
 80             } while (reportedNames[name] || existingNames[name]);
 81             reportedNames[name] = true;
 82             return name;
 83         };
 84     }
 85 
 86     /**
 87      * Get all the style names defined in the style:style elements of the
 88      * current document including automatic styles.
 89      *
 90      * @return {!Object.<string,boolean>}
 91      */
 92     function getAllStyleNames() {
 93         var styleElements = [
 94                 odfContainer.rootElement.automaticStyles,
 95                 odfContainer.rootElement.styles
 96             ],
 97             styleNames = {};
 98 
 99         /**
100          * @param {!Element} styleListElement
101          */
102         function getStyleNames(styleListElement) {
103             var e = styleListElement.firstElementChild;
104             while (e) {
105                 if (e.namespaceURI === stylens && e.localName === "style") {
106                     styleNames[e.getAttributeNS(stylens, 'name')] = true;
107                 }
108                 e = e.nextElementSibling;
109             }
110         }
111         styleElements.forEach(getStyleNames);
112         return styleNames;
113     }
114 
115     /**
116      * Generate a unique style name across the style:style elements
117      * @return {!string}
118      */
119     this.generateStyleName = function () {
120         if (styleNameGenerator === null) {
121             styleNameGenerator = new NameGenerator(
122                 "auto" + memberIdHash + "_",
123                 function () {
124                     // TODO: can cache the existing names once we fix the todo in formatting.applyStyle
125                     return getAllStyleNames();
126                 }
127             );
128         }
129         return styleNameGenerator.generateName();
130     };
131     /**
132      * Generate a unique frame name
133      * @return {!string}
134      */
135     this.generateFrameName = function () {
136         var i, nodes, node;
137         if (frameNameGenerator === null) {
138             nodes = odfContainer.rootElement.body.getElementsByTagNameNS(drawns, 'frame');
139             for (i = 0; i < nodes.length; i += 1) {
140                 node = /**@type{!Element}*/(nodes.item(i));
141                 existingFrameNames[node.getAttributeNS(drawns, 'name')] = true;
142             }
143 
144             frameNameGenerator = new NameGenerator(
145                 "fr" + memberIdHash + "_",
146                 function () {
147                     return existingFrameNames;
148                 }
149             );
150         }
151         return frameNameGenerator.generateName();
152     };
153     /**
154      * Generate a unique image name
155      * @return {!string}
156      */
157     this.generateImageName = function () {
158         var i, path, nodes, node;
159 
160         if (imageNameGenerator === null) {
161             nodes = odfContainer.rootElement.body.getElementsByTagNameNS(drawns, 'image');
162             for (i = 0; i < nodes.length; i += 1) {
163                 node = /**@type{!Element}*/(nodes.item(i));
164                 path = node.getAttributeNS(xlinkns, 'href');
165                 path = path.substring("Pictures/".length, path.lastIndexOf('.'));
166                 existingImageNames[path] = true;
167             }
168 
169             imageNameGenerator = new NameGenerator(
170                 "img" + memberIdHash + "_",
171                 function () {
172                     return existingImageNames;
173                 }
174             );
175         }
176         return imageNameGenerator.generateName();
177     };
178 };
179