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 $scripts = jQuery(html).filter('script');
224 		var $html = jQuery(marked);
225 		var contents = joinContents($html.filter(':not(script)'));
226 		contents = protectScriptTags(contents, $scripts);
227 		$element.html(contents);
228 
229 		// Trap script errors originating from rendered tags and log it on the
230 		// console.
231 		try {
232 			$element.append($scripts);
233 		} catch (ex) {
234 			var _console = 'console'; // Because jslint is paranoid.
235 			if (typeof window[_console] === 'function') {
236 				window[_console].error(ex);
237 			}
238 		}
239 
240 		restoreScriptTagTypes($element);
241 	}
242 
243 	/**
244 	 * Merge class names from one element into another.
245 	 *
246 	 * The merge result will be a unqiue set of space-seperated class names.
247 	 *
248 	 * @param {jQuery.<HTMLElement>} $first jQuery unit set containing the DOM
249 	 *                                      whose class names are to be merged.
250 	 * @param {jQuery.<HTMLElement>} $second jQuery unit set containing the DOM
251 	 *                                       whose class names are to be merged.
252 	 * @return {string} The merge result of the merge: a unqiue set of
253 	 *                  space-seperated class names.
254 	 */
255 	function mergeClassNames($first, $second) {
256 		var first = ($first.attr('class') || '').split(' ');
257 		var second = ($second.attr('class') || '').split(' ');
258 		var names = first.concat(second).sort();
259 		var i;
260 		for (i = 1; i < names.length; i++) {
261 			if (names[i] === names[i - 1]) {
262 				names.splice(i--, 1);
263 			}
264 		}
265 		return names.join(' ');
266 	}
267 
268 	/**
269 	 * Creates a map of attributes merged from their value in $from with their
270 	 * value in $to.
271 	 *
272 	 * Class names--unlike other attributes, which are simply copied from $from
273 	 * into $to--are treaded specially to produce a unique set of
274 	 * space-seperated class names.
275 	 *
276 	 * @param {jQuery.<HTMLElement>} $to jQuery unit set containing the DOM
277 	 *                                   element which should receive the
278 	 *                                   merged attributes.
279 	 * @param {jQuery.<HTMLElement>} $from jQuery unit set containing the DOM
280 	 *                                     element whose attributes will be
281 	 *                                     merged into the other.
282 	 * @param {object<string, string>} A associate array of attributes.
283 	 */
284 	function mergeAttributes($to, $from) {
285 		var from = $from[0].attributes;
286 		var to = $to[0].attributes;
287 		var i;
288 		var attr = {};
289 		for (i = 0; i < from.length; i++) {
290 			attr[from[i].name] = ('class' === from[i].name)
291 			                   ? mergeClassNames($to, $from)
292 			                   : $from.attr(from[i].name);
293 		}
294 		return attr;
295 	}
296 
297 	/**
298 	 * Renders the given HTML string onto (not into) an DOM element.
299 	 * Does nearly the equivelent of $.replaceWith() or changing the element's
300 	 * outerHTML.
301 	 *
302 	 *             http://bugs.jquery.com/ticket/8142#comment:6
303 	 *             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
304 	 * 'All of jQuery's insertion methods use a domManip function internally  to
305 	 * clean/process elements before and after they are inserted into  the  DOM.
306 	 * One of the things the domManip function  does  is  pull  out  any  script
307 	 * elements about to  be  inserted  and  run  them  through  an  "evalScript
308 	 * routine" rather than inject them with the rest of the  DOM  fragment.  It
309 	 * inserts the scripts separately, evaluates them,  and  then  removes  them
310 	 * from the DOM.
311 	 *
312 	 * 'I believe that  one  of  the  reasons  jQuery  does  this  is  to  avoid
313 	 * "Permission Denied" errors that  can  occur  in  Internet  Explorer  when
314 	 * inserting scripts under certain circumstances. It also avoids  repeatedly
315 	 * inserting/evaluating the  same  script  (which  could  potentially  cause
316 	 * problems) if it is within a containing element that you are inserting and
317 	 * then moving around the DOM.'
318 	 *
319 	 * @param {jQuery.<HTMLElement>} $element jQuery unit set containing the
320 	 *                                        DOM element we wish to render the
321 	 *                                        given html content onto.
322 	 * @param {string} html HTML content which will become give to the element.
323 	 */
324 	function renderOnto($element, html) {
325 		insertInnerHTMLWithScriptTags($element, html);
326 		var attr = mergeAttributes($element, jQuery(html));
327 		var name;
328 		for (name in attr) {
329 			if (attr.hasOwnProperty(name)) {
330 				$element.attr(name, attr[name]);
331 			}
332 		}
333 	}
334 
335 	/**
336 	 * Removes the given chainback instance from its cached location.
337 	 *
338 	 * @param {Chainback} chainback Instance to remove from cache.
339 	 */
340 	function decache(chainback) {
341 		if (chainback._constructor.__gcncache__[chainback.__gcnhash__]) {
342 			delete chainback._constructor.__gcncache__[chainback.__gcnhash__];
343 		}
344 	}
345 
346 	GCN.uniqueId = uniqueId;
347 	GCN.escapePropertyName = escapePropertyName;
348 	GCN.renderOnto = renderOnto;
349 	GCN.decache = decache;
350 
351 }(GCN));
352