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