1 (function (GCN) {
  2 
  3 	'use strict';
  4 
  5 	/**
  6 	 * Only grows, never shrinks.
  7 	 * @private
  8 	 * @type {number}
  9 	 */
 10 	var uniqueIdCounter = 0;
 11 
 12 	/**
 13 	 * Generates a unique id with an optional prefix.
 14 	 *
 15 	 * The returned value is only unique among other returned values,
 16 	 * not globally.
 17 	 *
 18 	 * @public
 19 	 * @param {string}
 20 	 *        Optional prefix for the id to be generated.
 21 	 * @return {string}
 22 	 *        Never the same string more than once.
 23 	 */
 24 	function uniqueId(prefix) {
 25 		return (prefix || '') + (++uniqueIdCounter);
 26 	}
 27 
 28 	function escapePropertyName(name) {
 29 		return name.replace(/\./g, '\\.');
 30 	}
 31 
 32 	/**
 33 	 * A regular expression to identify executable script tags.
 34 	 *
 35 	 * @type {RegExp}
 36 	 */
 37 	var rgxpScriptType = /\/(java|ecma)script/i;
 38 
 39 	/**
 40 	 * A regular expression to find script tags.
 41 	 *
 42 	 * @type {RegExp}
 43 	 */
 44 	var rgxpScriptTag = new RegExp('<script(\\s[^>]*?)?>', 'ig');
 45 
 46 	/**
 47 	 * A regular expression to find script types.
 48 	 *
 49 	 * @type {RegExp}
 50 	 */
 51 	var rgxpType = new RegExp(
 52 		' type\\s*=\\s*'
 53 			+ '[\\"\\\']'
 54 			+ '([^\\"\\\']*[^\\s][^\\"\\\']*)'
 55 			+ '[\\"\\\']',
 56 		'i'
 57 	);
 58 
 59 	var rand = Math.random().toString().replace('.', '');
 60 
 61 	/**
 62 	 * Places sentinal strings in front of every executable script tag.
 63 	 *
 64 	 * @param {string} html HTML markup
 65 	 * @return {string} HTML string with marked <script> tags.
 66 	 */
 67 	function markScriptTagLocations(html) {
 68 		var i = 0;
 69 		return html.replace(rgxpScriptTag, function (str, substr, offset) {
 70 			var type = substr && substr.match(rgxpType);
 71 			if (!type || rgxpScriptType.test(type)) {
 72 				return rand + (i++) + str;
 73 			}
 74 			return str;
 75 		});
 76 	}
 77 
 78 	/**
 79 	 * Masks a <script> tag's `type' attribute by replacing it with a random
 80 	 * string.
 81 	 *
 82 	 * Masking script tags is don to prevent them from being handled specially
 83 	 * by jQuery which removes javascript/ecmascript tags when appending DOM
 84 	 * elements into the document, but executes them.
 85 	 *
 86 	 * unmakeScriptType() does the reverse of this.
 87 	 *
 88 	 * @param {jQuery.<HTMLElement>} $script jQuery unit set containing the
 89 	 *                                       <script> tag that is to have its
 90 	 *                                       type attribute masked.
 91 	 */
 92 	function maskScriptType($script) {
 93 		var type = $script.attr('type');
 94 		$script.attr('type', rand).attr('data-origtype', type);
 95 	}
 96 
 97 	/**
 98 	 * Restores a <script> tag's original type attribute value, if it had been
 99 	 * masked using maskScriptType().
100 	 *
101 	 * Essentially the reverse of maskScriptType().
102 	 *
103 	 * @param {jQuery.<HTMLElement>} $script jQuery unit set containing the
104 	 *                                       <script> tag that is to have its
105 	 *                                       type attribute unmasked.
106 	 */
107 	function unmaskScriptType($script) {
108 		var orig = $script.attr('data-origtype');
109 		if (typeof orig === 'string') {
110 			$script.attr('type', orig).removeAttr('data-origtype');
111 		} else {
112 			$script.removeAttr('type');
113 		}
114 	}
115 
116 	/**
117 	 * Replaces the type attribute of <script> tags with a value that will
118 	 * protect them from being specially handled by jQuery.
119 	 *
120 	 * @param {string} html Markup
121 	 * @return {string} Markup with <script> tags protected from jQuery.
122 	 */
123 	function protectScriptTags(html, $scripts) {
124 		var i;
125 		var $script;
126 		var type;
127 		for (i = 0; i < $scripts.length; i++) {
128 			$script = $scripts.eq(i);
129 			type = $script.attr('type');
130 			if (!type || rgxpScriptType.test(type)) {
131 				maskScriptType($script);
132 				html = html.replace(rand + i, $script[0].outerHTML);
133 				unmaskScriptType($script);
134 			}
135 		}
136 		return html;
137 	}
138 
139 	/**
140 	 * Restores the type attribute for <script> tags that have been processed
141 	 * via protectScriptTags().
142 	 *
143 	 * @param {jQuery.<HTMLElement>} $element Root HTMLElement in which to
144 	 *                                        restore <script> tags.
145 	 */
146 	function restoreScriptTagTypes($element) {
147 		var $scripts = $element.find('script[type="' + rand + '"]');
148 		var $script;
149 		var i;
150 		for (i = 0; i < $scripts.length; i++) {
151 			$script = $scripts.eq(i);
152 			$script.removeClass(rand + i);
153 			unmaskScriptType($script);
154 		}
155 	}
156 
157 	/**
158 	 * Joins the innerHTML of multiple elements.
159 	 *
160 	 * @param {jQuery.<HTMLElement>} $elements
161 	 */
162 	function joinContents($elements) {
163 		var contents = '';
164 		$elements.each(function () {
165 			contents += jQuery(this).html();
166 		});
167 		return contents;
168 	}
169 
170 	/**
171 	 * Inserts the inner HTML of the given HTML markup while preserving
172 	 * <script> tags, and still allowing jQuery to execute them.
173 	 *
174 	 * @param {jQuery.<HTMLElement>} $element
175 	 * @param {string} html
176 	 */
177 	function insertInnerHTMLWithScriptTags($element, html) {
178 		if (!rgxpScriptTag.test(html)) {
179 			$element.html(jQuery(html).contents());
180 			return;
181 		}
182 		var marked = markScriptTagLocations(html);
183 		var $scripts = jQuery(html).filter('script');
184 		var $html = jQuery(marked);
185 		var contents = joinContents($html.filter(':not(script)'));
186 		contents = protectScriptTags(contents, $scripts);
187 		$element.html(contents);
188 
189 		// Trap script errors originating from rendered tags and log it on the
190 		// console.
191 		try {
192 			$element.append($scripts);
193 		} catch (ex) {
194 			var _console = 'console'; // Avoid jslint warnings
195 			if (typeof window[_console] === 'function') {
196 				window[_console].error(ex);
197 			}
198 		}
199 
200 		restoreScriptTagTypes($element);
201 	}
202 
203 	/**
204 	 * Merge class names from one element into another.
205 	 *
206 	 * The merge result will be a unqiue set of space-seperated class names.
207 	 *
208 	 * @param {jQuery.<HTMLElement>} $first jQuery unit set containing the DOM
209 	 *                                      whose class names are to be merged.
210 	 * @param {jQuery.<HTMLElement>} $second jQuery unit set containing the DOM
211 	 *                                       whose class names are to be merged.
212 	 * @return {string} The merge result of the merge: a unqiue set of
213 	 *                  space-seperated class names.
214 	 */
215 	function mergeClassNames($first, $second) {
216 		var first = ($first.attr('class') || '').split(' ');
217 		var second = ($second.attr('class') || '').split(' ');
218 		var names = first.concat(second).sort();
219 		var i;
220 		for (i = 1; i < names.length; i++) {
221 			if (names[i] === names[i - 1]) {
222 				names.splice(i--, 1);
223 			}
224 		}
225 		return names.join(' ');
226 	}
227 
228 	/**
229 	 * Creates a map of attributes merged from their value in $from with their
230 	 * value in $to.
231 	 *
232 	 * Unlike other attributes, which are simply copied from $from into $to,
233 	 * class names are treaded specially to produce unique set of
234 	 * space-seperated class names.
235 	 *
236 	 * @param {jQuery.<HTMLElement>} $to jQuery unit set containing the DOM
237 	 *                                   element which should receive the
238 	 *                                   merged attributes.
239 	 * @param {jQuery.<HTMLElement>} $from jQuery unit set containing the DOM
240 	 *                                     element whose attributes will be
241 	 *                                     merged into the other.
242 	 * @param {object<string, string>} A associate array of attributes.
243 	 */
244 	function mergeAttributes($to, $from) {
245 		var to = $to[0].attributes;
246 		var from = $from[0].attributes;
247 		var i;
248 		var attr = {};
249 		for (i = 0; i < from.length; i++) {
250 			attr[from[i].name] = ('class' === from[i].name)
251 			                   ? mergeClassNames($to, $from)
252 			                   : $from.attr(from[i].name);
253 		}
254 		return attr;
255 	}
256 
257 	/**
258 	 * Renders the given HTML string onto (not into) an DOM element.
259 	 * Does nearly the equivelent of changes the elements' outerHTML.
260 	 *
261 	 * @param {jQuery.<HTMLElement>} $element jQuery unit set containing the
262 	 *                                        DOM element we wish to render the
263 	 *                                        given html content onto.
264 	 * @param {string} html HTML content which will become give to the element.
265 	 */
266 	function renderOnto($element, html) {
267 		insertInnerHTMLWithScriptTags($element, html);
268 		var attr = mergeAttributes($element, jQuery(html));
269 		var name;
270 		for (name in attr) {
271 			if (attr.hasOwnProperty(name)) {
272 				$element.attr(name, attr[name]);
273 			}
274 		}
275 	}
276 
277 	GCN.uniqueId = uniqueId;
278 	GCN.escapePropertyName = escapePropertyName;
279 	GCN.renderOnto = renderOnto;
280 
281 }(GCN));
282