1 (function (GCN) {
  2 
  3 	'use strict';
  4 
  5 	/**
  6 	 * Only grows, never shrinks.
  7 	 *
  8 	 * @private
  9 	 * @type {number}
 10 	 */
 11 	var uniqueIdCounter = 0;
 12 
 13 	/**
 14 	 * Generates a unique id with an optional prefix.
 15 	 *
 16 	 * The returned value is only unique among other returned values,
 17 	 * not globally.
 18 	 *
 19 	 * @public
 20 	 * @param {string}
 21 	 *        Optional prefix for the id to be generated.
 22 	 * @return {string}
 23 	 *        Never the same string more than once.
 24 	 */
 25 	function uniqueId(prefix) {
 26 		return (prefix || '') + (++uniqueIdCounter);
 27 	}
 28 
 29 	/**
 30 	 * Escapes the given property name by prefixing dots with backslashes.
 31 	 *
 32 	 * @param {string} name The property name to escape.
 33 	 * @return {string} Escaed string.
 34 	 */
 35 	function escapePropertyName(name) {
 36 		return name.replace(/\./g, '\\.');
 37 	}
 38 
 39 	/**
 40 	 * A regular expression to identify executable script tags.
 41 	 *
 42 	 * @type {RegExp}
 43 	 */
 44 	var rgxpScriptType = /\/(java|ecma)script/i;
 45 
 46 
 47 	/**
 48 	 * A string to be used in regular expressions matching script tags.
 49 	 *
 50 	 * TODO: The string does not work correctly for complex cases and
 51 	 * should be improved.
 52 	 *
 53 	 * @type {string}
 54 	 */
 55 	var SCRIPT_TAG = '<script(\\s[^>]*?)?>';
 56 
 57 	/**
 58 	 * This regular expression is used to find opening script tags.
 59 	 *
 60 	 * It uses the global flag to find several matching tags, for
 61 	 * example to replace them.
 62 	 *
 63 	 * @type {RegExp}
 64 	 */
 65 	var rgxpScriptTags = new RegExp(SCRIPT_TAG, 'ig');
 66 
 67 	/**
 68 	 * This regular expression is used to find opening script tags.
 69 	 * It does not use the global flag, and can be used to test if
 70 	 * a string contains scripts at all.
 71 	 *
 72 	 * For testing strings, this expression should be used instead
 73 	 * of rgxpScriptTags, because the latter keeps an index of found
 74 	 * occurrences (due to its global flag), which causes the
 75 	 * regular expression to often fail (in Firefox).
 76 	 *
 77 	 * @type {RegExp}
 78 	 */
 79 	var rgxpScriptTag = new RegExp(SCRIPT_TAG, 'i');
 80 
 81 
 82 
 83 	/**
 84 	 * A regular expression to find script types.
 85 	 *
 86 	 * @type {RegExp}
 87 	 */
 88 	var rgxpType = new RegExp(
 89 		' type\\s*=\\s*'
 90 			+ '[\\"\\\']'
 91 			+ '([^\\"\\\']*[^\\s][^\\"\\\']*)'
 92 			+ '[\\"\\\']',
 93 		'i'
 94 	);
 95 
 96 	var rand = Math.random().toString().replace('.', '');
 97 
 98 	/**
 99 	 * Places sentinal strings in front of every executable script tag.
100 	 *
101 	 * @param {string} html HTML markup
102 	 * @return {string} HTML string with marked <script> tags.
103 	 */
104 	function markScriptTagLocations(html) {
105 		var i = 0;
106 		rgxpScriptTags.lastIndex = 0;
107 		return html.replace(rgxpScriptTags, function (str, substr, offset) {
108 			var type = substr && substr.match(rgxpType);
109 			if (!type || rgxpScriptType.test(type)) {
110 				return rand + (i++) + str;
111 			}
112 			return str;
113 		});
114 	}
115 
116 	/**
117 	 * Masks a <script> tag's `type' attribute by replacing it with a random
118 	 * string.
119 	 *
120 	 * Masking script tags is done to prevent them from being handled specially
121 	 * by jQuery, which removes javascript/ecmascript tags when appending DOM
122 	 * elements into the document, but executes them.
123 	 *
124 	 * unmakeScriptType() reverses this.
125 	 *
126 	 * @param {jQuery.<HTMLElement>} $script jQuery unit set containing the
127 	 *                                       <script> tag that is to have its
128 	 *                                       type attribute masked.
129 	 */
130 	function maskScriptType($script) {
131 		var type = $script.attr('type');
132 		$script.attr('type', rand).attr('data-origtype', type);
133 	}
134 
135 	/**
136 	 * Restores a <script> tag's original type attribute value if it had been
137 	 * masked using maskScriptType().
138 	 *
139 	 * Essentially the reverse of maskScriptType().
140 	 *
141 	 * @param {jQuery.<HTMLElement>} $script jQuery unit set containing the
142 	 *                                       <script> tag that is to have its
143 	 *                                       type attribute unmasked.
144 	 */
145 	function unmaskScriptType($script) {
146 		var orig = $script.attr('data-origtype');
147 		if (typeof orig === 'string') {
148 			$script.attr('type', orig).removeAttr('data-origtype');
149 		} else {
150 			$script.removeAttr('type');
151 		}
152 	}
153 
154 	/**
155 	 * Replaces the type attribute of <script> tags with a value that will
156 	 * protect them from being specially handled by jQuery.
157 	 *
158 	 * @param {string} html Markup
159 	 * @return {string} Markup with <script> tags protected from jQuery.
160 	 */
161 	function protectScriptTags(html, $scripts) {
162 		var i;
163 		var type;
164 		var $script;
165 		for (i = 0; i < $scripts.length; i++) {
166 			$script = $scripts.eq(i);
167 			type = $script.attr('type');
168 			if (!type || rgxpScriptType.test(type)) {
169 				maskScriptType($script);
170 				html = html.replace(rand + i, $script[0].outerHTML);
171 				unmaskScriptType($script);
172 			}
173 		}
174 		return html;
175 	}
176 
177 	/**
178 	 * Restores the type attribute for <script> tags that have been processed
179 	 * via protectScriptTags().
180 	 *
181 	 * @param {jQuery.<HTMLElement>} $element Root HTMLElement in which to
182 	 *                                        restore <script> tags.
183 	 */
184 	function restoreScriptTagTypes($element) {
185 		var $scripts = $element.find('script[type="' + rand + '"]');
186 		var $script;
187 		var i;
188 		for (i = 0; i < $scripts.length; i++) {
189 			$script = $scripts.eq(i);
190 			$script.removeClass(rand + i);
191 			unmaskScriptType($script);
192 		}
193 	}
194 
195 	/**
196 	 * Joins the innerHTML of multiple elements.
197 	 *
198 	 * @param {jQuery.<HTMLElement>} $elements
199 	 * @return {string} A concatenated string of the contents of the given set
200 	 *                  of elements.
201 	 */
202 	function joinContents($elements) {
203 		var contents = '';
204 		$elements.each(function () {
205 			contents += jQuery(this).html();
206 		});
207 		return contents;
208 	}
209 
210 	/**
211 	 * Inserts the inner HTML of the given HTML markup while preserving
212 	 * <script> tags, and still allowing jQuery to execute them.
213 	 *
214 	 * @param {jQuery.<HTMLElement>} $element
215 	 * @param {string} html
216 	 */
217 	function insertInnerHTMLWithScriptTags($element, html) {
218 		if (!rgxpScriptTag.test(html)) {
219 			$element.html(jQuery(html).contents());
220 			return;
221 		}
222 		var marked = markScriptTagLocations(html);
223 		var $html = jQuery(marked);
224 		// beware, starting with jQuery 1.9.0, the handling of scripts changed. Before 1.9.0 when doing e.g.
225 		// jQuery('<div><script></script></div>') the script tag would be moved out of the div and appended to the end.
226 		// therefore using the filter() method would give us all scripts found in the markup
227 		// Starting with jQuery 1.9.0, the script tags would remain were they are and therefore using the filter() method
228 		// would return an empty jQuery() object, unless the markup consists only of the script tag.
229 		var $scripts = jQuery(html).filter('script');
230 		var contents;
231 		// now we do a detection of the script-tag handling
232 		if ($scripts.length === 0) {
233 			// we are using jQuery >= 1.9.0, so we use .find() to extract all the script-nodes
234 			$scripts = jQuery(html).find('script');
235 			// from the $html, we now remove the script-nodes
236 			$html.find('script').remove();
237 			contents = $html.html();
238 		} else {
239 			// we are using jQuery < 1.9.0, to $html is already separated (non-script nodes and script nodes)
240 			// so with .filter() we can get all non-script nodes and join them
241 			contents = joinContents($html.filter(':not(script)'));
242 		}
243 		// when we get here, contents will consists of the original html markup with all script tags replaced by placeholders
244 		// next we will re-insert the script tags at their original place, but with the type replaced by something else than text/javascript
245 		// the purpose is that jQuery will NOT evaluate the scripts when inserting into the DOM
246 		contents = protectScriptTags(contents, $scripts);
247 		// insert the markup into the element. masked script nodes will be generated but not executed
248 		$element.html(contents);
249 
250 		// Trap script errors originating from rendered tags and log it on the
251 		// console.
252 		try {
253 			// jQuery will now execute but not really append the scripts
254 			$element.append($scripts);
255 		} catch (ex) {
256 			var _console = 'console'; // Because jslint is paranoid.
257 			if (typeof window[_console] === 'function') {
258 				window[_console].error(ex);
259 			}
260 		}
261 
262 		// finally, we unmask the script nodes to have them appear like in the original markup
263 		restoreScriptTagTypes($element);
264 	}
265 
266 	/**
267 	 * Merge class names from one element into another.
268 	 *
269 	 * The merge result will be a unqiue set of space-seperated class names.
270 	 *
271 	 * @param {jQuery.<HTMLElement>} $first jQuery unit set containing the DOM
272 	 *                                      whose class names are to be merged.
273 	 * @param {jQuery.<HTMLElement>} $second jQuery unit set containing the DOM
274 	 *                                       whose class names are to be merged.
275 	 * @return {string} The merge result of the merge: a unqiue set of
276 	 *                  space-seperated class names.
277 	 */
278 	function mergeClassNames($first, $second) {
279 		var first = ($first.attr('class') || '').split(' ');
280 		var second = ($second.attr('class') || '').split(' ');
281 		var names = first.concat(second).sort();
282 		var i;
283 		for (i = 1; i < names.length; i++) {
284 			if (names[i] === names[i - 1]) {
285 				names.splice(i--, 1);
286 			}
287 		}
288 		return names.join(' ');
289 	}
290 
291 	/**
292 	 * Creates a map of attributes merged from their value in $from with their
293 	 * value in $to.
294 	 *
295 	 * Class names--unlike other attributes, which are simply copied from $from
296 	 * into $to--are treaded specially to produce a unique set of
297 	 * space-seperated class names.
298 	 *
299 	 * @param {jQuery.<HTMLElement>} $to jQuery unit set containing the DOM
300 	 *                                   element which should receive the
301 	 *                                   merged attributes.
302 	 * @param {jQuery.<HTMLElement>} $from jQuery unit set containing the DOM
303 	 *                                     element whose attributes will be
304 	 *                                     merged into the other.
305 	 * @param {object<string, string>} A associate array of attributes.
306 	 */
307 	function mergeAttributes($to, $from) {
308 		var from = $from[0].attributes;
309 		var to = $to[0].attributes;
310 		var i;
311 		var attr = {};
312 		for (i = 0; i < from.length; i++) {
313 			attr[from[i].name] = ('class' === from[i].name)
314 			                   ? mergeClassNames($to, $from)
315 			                   : $from.attr(from[i].name);
316 		}
317 		return attr;
318 	}
319 
320 	/**
321 	 * Renders the given HTML string onto (not into) an DOM element.
322 	 * Does nearly the equivelent of $.replaceWith() or changing the element's
323 	 * outerHTML.
324 	 *
325 	 *             http://bugs.jquery.com/ticket/8142#comment:6
326 	 *             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
327 	 * 'All of jQuery's insertion methods use a domManip function internally  to
328 	 * clean/process elements before and after they are inserted into  the  DOM.
329 	 * One of the things the domManip function  does  is  pull  out  any  script
330 	 * elements about to  be  inserted  and  run  them  through  an  "evalScript
331 	 * routine" rather than inject them with the rest of the  DOM  fragment.  It
332 	 * inserts the scripts separately, evaluates them,  and  then  removes  them
333 	 * from the DOM.
334 	 *
335 	 * 'I believe that  one  of  the  reasons  jQuery  does  this  is  to  avoid
336 	 * "Permission Denied" errors that  can  occur  in  Internet  Explorer  when
337 	 * inserting scripts under certain circumstances. It also avoids  repeatedly
338 	 * inserting/evaluating the  same  script  (which  could  potentially  cause
339 	 * problems) if it is within a containing element that you are inserting and
340 	 * then moving around the DOM.'
341 	 *
342 	 * @param {jQuery.<HTMLElement>} $element jQuery unit set containing the
343 	 *                                        DOM element we wish to render the
344 	 *                                        given html content onto.
345 	 * @param {string} html HTML content which will become give to the element.
346 	 */
347 	function renderOnto($element, html) {
348 		insertInnerHTMLWithScriptTags($element, html);
349 		var attr = mergeAttributes($element, jQuery(html));
350 		var name;
351 		for (name in attr) {
352 			if (attr.hasOwnProperty(name)) {
353 				$element.attr(name, attr[name]);
354 			}
355 		}
356 	}
357 
358 	/**
359 	 * Removes the given chainback instance from its cached location.
360 	 *
361 	 * @param {Chainback} chainback Instance to remove from cache.
362 	 */
363 	function decache(chainback) {
364 		if (chainback._constructor.__gcncache__[chainback.__gcnhash__]) {
365 			delete chainback._constructor.__gcncache__[chainback.__gcnhash__];
366 		}
367 	}
368 
369 	/**
370 	 * Maps constructs, that were fetched via the Rest API, to a hashmap, using
371 	 * their keyword as the keys.
372 	 *
373 	 * @param {object<string, object>} constructs Consturcts mapped against
374 	 *                                            their id.
375 	 * @return {object<string, object>} Constructs mapped against their keys.
376 	 */
377 	function mapConstructs(constructs) {
378 		if (!constructs) {
379 			return {};
380 		}
381 		var map = {};
382 		var constructId;
383 		for (constructId in constructs) {
384 			if (constructs.hasOwnProperty(constructId)) {
385 				map[constructs[constructId].keyword] = constructs[constructId];
386 			}
387 		}
388 		return map;
389 	}
390 
391 	GCN.uniqueId = uniqueId;
392 	GCN.escapePropertyName = escapePropertyName;
393 	GCN.renderOnto = renderOnto;
394 	GCN.decache = decache;
395 	GCN.mapConstructs = mapConstructs;
396 
397 }(GCN));
398