1 /*
  2  * 	Copyright (C) 2012-2013 DFKI GmbH
  3  * 	Deutsches Forschungszentrum fuer Kuenstliche Intelligenz
  4  * 	German Research Center for Artificial Intelligence
  5  * 	http://www.dfki.de
  6  * 
  7  * 	Permission is hereby granted, free of charge, to any person obtaining a 
  8  * 	copy of this software and associated documentation files (the 
  9  * 	"Software"), to deal in the Software without restriction, including 
 10  * 	without limitation the rights to use, copy, modify, merge, publish, 
 11  * 	distribute, sublicense, and/or sell copies of the Software, and to 
 12  * 	permit persons to whom the Software is furnished to do so, subject to 
 13  * 	the following conditions:
 14  * 
 15  * 	The above copyright notice and this permission notice shall be included 
 16  * 	in all copies or substantial portions of the Software.
 17  * 
 18  * 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 19  * 	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 20  * 	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 21  * 	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
 22  * 	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 23  * 	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 24  * 	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 25  */
 26 
 27 
 28 define (['commonUtils', 'viewConstants', 'yield', 'storageUtils' ],
 29 	//this comment is needed by jsdoc2 [copy of comment for: function Layout(...]
 30 	/**
 31 	 * The Layout class 
 32 	 * The constructor parses the layout and divides them into containers (headerContents, bodyContents, dialogsContents).
 33 	 * 
 34 	 * @param {String} name
 35 	 * 			Name of the Layout (usually the same name as the Layout's controller).
 36 	 * @param {String} definition
 37 	 * 			Layout description, i.e. the raw template code that will be processed.
 38 	 * 			May be empty: in this case the processed contents must be
 39 	 * 						  added manually (cf. parser.StorageUtils)
 40 	 * 
 41 	 * @requires if param definition is NOT empty: parser.RenderUtils (must be loaded beforehand via <code>require(["renderUtils"]...</code>)
 42 	 * 			
 43 	 * @requires if param definition is NOT empty: parser.ParseUtils (must be loaded beforehand via <code>require(["parseUtils"]...</code>)
 44 	 * 
 45 	 * @name Layout
 46 	 * @class
 47 	 */
 48 	function(
 49 		commonUtils, ViewConstants, YieldDeclaration, storageUtils
 50 ) {
 51 	
 52 /** @scope Layout.prototype *///for jsdoc2
 53 
 54 //set to @ignore in order to avoid doc-duplication in jsdoc3
 55 /**
 56  * @ignore
 57  * The Layout class 
 58  * The constructor parses the layout and divides them into containers (headerContents, bodyContents, dialogsContents).
 59  * 
 60  * @constructs Layout
 61  * @param {String} name
 62  * 			Name of the Layout (usually the same name as the Layout's controller).
 63  * @param {String} definition
 64  * 			Layout description, i.e. the raw template code that will be processed.
 65  * 			May be empty: in this case the processed contents must be
 66  * 						  added manually (cf. parser.StorageUtils)
 67  * 
 68  * @requires if param <code>definition</code> is NOT empty: parser.RenderUtils (must be loaded beforehand via <code>require(["renderUtils"]...</code>)
 69  * 			
 70  * @requires if param <code>definition</code> is NOT empty: parser.ParseUtils (must be loaded beforehand via <code>require(["parseUtils"]...</code>)
 71  * 
 72  */
 73 function Layout(name, definition, remote, ignoreMissingBody){
 74 //		console.log("[Layout] initialize '"+name+"'.");
 75 
 76 		//FIXME MODIFICATIONS for "remote content layout object":
 77 		this.remoteaccess = false;
 78 		if ((typeof remote !== "undefined") && (remote == true)){
 79 			this.remoteaccess = true;
 80 		}
 81 		
 82 		
 83 		/**
 84 	     * The definition string of the layout (ehtml-format, taken from assets/www/views/layout/*.ehtml)
 85 	     * 
 86 	     * @type Object
 87 	     * @public
 88 	     */
 89 		this.def = definition? definition.replace(commonUtils.regexHTMLComment, '') : definition;//remove HTML comments!
 90 
 91 		/**
 92 	     * The name of the layout. 
 93 	     * 
 94 	     * @type String
 95 	     * @public
 96 	     */
 97 	    this.name = name;
 98 
 99 		/**
100 	     * This variable holds the contents of the header part of the layout.
101 	     * 
102 	     * @type String
103 	     * @public
104 	     * @deprecated unused
105 	     */
106 	    this.headerContents = "";
107 
108 	    /**
109 	     * List for extracted & parsed SCRIPT, LINK and STYLE tags
110 	     * 
111 	     * @type Array<Layout.TagElement>
112 	     * @public
113 	     */
114 	    this.headerElements = [];
115 	    
116 	    /**
117 	     * The page / layout title
118 	     * 
119 	     * Will be extracted from <em>definition</em>'s TITLE-tag, if present.
120 	     * 
121 	     * @type String
122 	     * @public
123 	     */
124 	    this.title = '';
125 
126 		/**
127 	     * This variable holds the contents of the body part of the layout.
128 	     * 
129 	     * @type String
130 	     * @public
131 	     * @deprecated unused
132 	     */
133 	    this.bodyContents = "";
134 
135 		/**
136 	     * This variable holds the contents of the dialogs part of the layout.
137 	     * 
138 	     * @type String
139 	     * @public
140 	     */
141 	    this.dialogsContents = "";
142 
143 		/**
144 	     * An associative array holding the contents of the different containers: header, body, footer and dialogs
145 	     * 
146 	     * @type Array
147 	     * @public
148 	     */
149 	    this.yields = new Array();
150 	    
151 	    if(this.def){
152 		    
153 		    //console.debug('Layout<constructor>: start rendering layout for "'+this.name+'"'+(remote?' (REMOTE)':'')+', RAW: '+this.def);
154 		    var parser = require('parseUtils');
155 	    	var parseResult = parser.parse(this.def, this);
156 	    	
157 	    	var renderer = require('renderUtils');
158 	    	var renderedLayout = renderer.renderLayout(parseResult, null/*FIXME?*/);
159 
160 	    	//DISABLED: parsing a string as HTML via jQuery etc. does not work (removes head, body,... tags):
161 //	    	var doc = new DOMParser().parseFromString(renderedLayout, "text/html");
162 //	    	if(!doc){
163 //	    		doc = document.implementation.createHTMLDocument('LAYOUT');
164 //	    	}
165 //	    	var $layout = $(doc);//$.parseHTML(renderedLayout, doc);
166 //
167 //	    	var headerElements = $('head', $layout);
168 //	    	for(var i=0, size = headerElements.length; i < size; ++i){
169 //	    		this.headerContents += headerElements[i].outerHTML;
170 //	    	}
171 //
172 //	    	var bodyElement = $('body', $layout);
173 //	    	this.bodyContents = bodyElement.html();
174 //	    	var dialogElement = $('dialogs', $layout);
175 //	    	this.dialogsContents = dialogElement.html();
176 		    
177 		    
178 		    //TODO remove this (replace with real HTML parser?)
179 	
180 		    var self = this;
181 		    
182 		    //TODO these should be constants (remove to constants.js?): 
183 		    this.markerAttributeName = ViewConstants.REMOTE_RESOURCES_ATTR_NAME;
184 		    this.markerAttributeValue = ViewConstants.REMOTE_RESOURCES_ATTR_VALUE;
185 		    this.markerUseSingleQuotes = false;//<- set value enclosed in single-quotes (true) or double-quotes (false)
186 		    
187 		    //appends the marker attribute for "signaling"/marking that a
188 		    //		  script/style/link-TAG was parsed&evaluated by this layout object
189 		    var addMarkerAttribute = function(strStartOfTag){
190 		    	return strStartOfTag + ' ' 
191 		    			+ self.markerAttributeName + '='
192 		    			+ (self.markerUseSingleQuotes? '\'': '"')
193 		    			+ self.markerAttributeValue
194 		    			+ (self.markerUseSingleQuotes? '\'': '"')
195 		    			;
196 		    };
197 		    
198 		    //pure HTML:
199 		    // (1) removed all HTML comments (via RegExpr)
200 		    // (2) removed template comments (via parser/renderer)
201 		    var pureHtml = renderedLayout;
202 		    
203 		    var regExprTagContent = //match one of the following (as groups):
204 		    						 '(('+ 		//match as groups
205 		    							'('+		//(1) CDATA: this may contain anything, even a closing script-statement
206 		    								'<!\\[CDATA\\['+		//CDATA open: <![CDATA[
207 		    									'(.|[\\r\\n])*?'+ //allow anything within CDATA, but match non-greedily...
208 		    								'\\]\\]>'+			//...for the CDATA-closing statement: ]]>
209 		    													//   (i.e. the first time we encounter this, we stop)
210 		    							')'+				//close group for CDATA-matching
211 		    							
212 		    							'|[\\r\\n]'+	//(2) OR line breaks \r and \n (in any combination)
213 		//DISABLED (using . instead!):	'|[^<]'+	//(3) OR any symbol that is NOT <
214 		    							'|.'+		//(4) OR any symbol (REQUIRED for allowing <-symbol within script!
215 		    						 ')'+			//close group
216 		    						 '*'+			//match this any number of times (or even none)
217 		    						 '?'+			//do matching non-greedy (i.e. until the next pattern in the RegExpr can be matched the first time)
218 	
219 			 						 ')';			//close outer group (i.e. one group for ALL content that is matched)
220 		    
221 		    //_non-greedy_ RegExpr for
222 		    //	* any line-break: \r or \n or a combination of them
223 		    //	* any other character but < (less-symbol); note that this itself does not include line breaks
224 		    var regExprTagInternal = '('+			//open group: match as a group (i.e. give access to the matched content via an index in the RegExpr object)
225 		    							'[\\r\\n]'+	//line breaks \r and \n
226 		    						   '|'+			//OR
227 		    						    '[^<]'+		//any symbol that is NOT <
228 		    						 ')'+			//close group
229 		    						 '*'+			//match this any number of times (or even none)
230 		    						 '?';			//do matching non-greedy (i.e. until the next pattern in the RegExpr can be matched the first time)
231 		    
232 		    
233 		    //matching: <script some attributes...> some content and '<!CDATA[' with any content allowed  </script>
234 		    //      or: <script some attributes... />
235 	//	    var regExpScriptTag = /((<script([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm;// /((<script([\r\n]|[^<])*?>)((<!\[CDATA|[\r\n]|[^<])*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm;
236 		    var strRegExpScriptTag = '((<script'+regExprTagInternal+'>)'+regExprTagContent+'(</script>))'+ //DETECT    "normal" script-TAG with optional content
237 		    						 '|(<script'+regExprTagInternal+'/>)';								   //OR DETECT "self-closing" script TAG
238 		    var regExpScriptTag = new RegExp(strRegExpScriptTag,'igm');
239 		    
240 		    //change to the following RegExpr (change the ones for link/style etc too)
241 		    // from
242 		    //		/((<script([\r\n]|[^<])*?>)(([\r\n]|[^<])*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm;
243 		    // to
244 		    //		/((<script([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm
245 		    //
246 		    // -> this RegExpr additionally 
247 		    //		* respects CDATA (<![CDTATA[ ... ]]>), with any content within its boundaries, even a "closing" </script>-TAG
248 		    //		* allows opening < within the script-TAGs
249 		    // LIMITATIONS: both this and the current one do not allow a < within the script-TAGS attributes, e.g. 
250 		    //				NOT: <script data-condition=" i < 5">
251 		    //				WORKAROUND: encode the <-symbol, i.e. instead use <script data-condition=" i < 5"> 
252 		    //							==> use "<" or "<" instead of "<" in TAG attributes!
253 		    
254 		    // regExpScriptTag[0]: complete match
255 		    // regExpScriptTag[1]: script with start and end tag (if matched)
256 		    // regExpScriptTag[4]: TEXT content of script-tag (if present/matched)
257 		    // regExpScriptTag[9]: self-closing script (if matched)
258 		    var matchScriptTag = null;
259 		    
260 		    self.headerContents = '';
261 		    
262 		    var removedScriptAndLinkHmtl = new Array();
263 	//	    var matchIndex;
264 		    while(matchScriptTag = regExpScriptTag.exec(pureHtml)){
265 	//	    	matchIndex = matchScriptTag[1] ? 1 : (matchScriptTag[9]? 9 : -1);
266 		    	
267 		    	if(matchScriptTag[0]){//matchIndex != -1){
268 	//	    		console.warn("Remote: " + self.remoteaccess);
269 		    		if (self.remoteaccess) {
270 	//	    			self.headerContents += matchScriptTag[0].replace("<script", "<script loc=\"remote\"");//[matchIndex];
271 		    			self.headerContents += addMarkerAttribute('<script') + matchScriptTag[0].substring('<script'.length);
272 		    		} else {
273 		        		self.headerContents += matchScriptTag[0];
274 		    		}
275 		    		//remove script tag, and continue search
276 	//	    		pureHtml = pureHtml.substring(0,matchScriptTag.index) + pureHtml.substring(matchScriptTag.index + matchScriptTag[0].length);// pureHtml.replace(matchScriptTag[matchIndex], '');
277 	
278 		    	    removedScriptAndLinkHmtl.push({start: matchScriptTag.index, end: matchScriptTag.index + matchScriptTag[0].length});
279 
280 		    	    self.headerElements.push(new Layout.TagElement(matchScriptTag[2] || matchScriptTag[9], matchScriptTag[4], 'SCRIPT'));
281 		    	}
282 		    }
283 		    
284 		    //matching: <link some attributes...> some content and '<!CDATA[' with any content allowed </link>
285 		    //      or: <link some attributes... />
286 	//	    var regExpLinkTag = /((<link([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/link>))|(<link([\r\n]|[^<])*?\/>)/igm;
287 		    var strRegExpLinkTag = '((<link'+regExprTagInternal+'>)'+regExprTagContent+'(</link>))'+ //DETECT    "normal" script-TAG with optional content
288 			 					   '|(<link'+regExprTagInternal+'/>)';								 //OR DETECT "self-closing" script TAG
289 		    var regExpLinkTag = new RegExp(strRegExpLinkTag,'igm');
290 		    // regExpLinkTag[0]: complete match
291 		    // regExpLinkTag[1]: link with start and end tag (if matched)
292 		    // regExpLinkTag[4]: TEXT content of link-tag (if present/matched)
293 		    // regExpLinkTag[9]: self-closing link (if matched)
294 		    var matchLinkTag = null;
295 		    
296 		    while(matchLinkTag = regExpLinkTag.exec(pureHtml)){
297 	//	    	console.warn("Matchlinktag: " + matchLinkTag[0]);
298 		    	if(matchLinkTag[0]){
299 	//	    		console.warn("Remote: " + self.remoteaccess);
300 		    		if (self.remoteaccess) {
301 	//	    			self.headerContents += matchLinkTag[0].replace("<link", "<link loc=\"remote\"");
302 		    			self.headerContents += addMarkerAttribute('<link') + matchLinkTag[0].substring('<link'.length);
303 		    		} else {
304 		        		self.headerContents += matchLinkTag[0];
305 		    		}
306 		    	    removedScriptAndLinkHmtl.push({start: matchLinkTag.index, end: matchLinkTag.index + matchLinkTag[0].length});
307 
308 		    	    self.headerElements.push(new Layout.TagElement(matchLinkTag[2] || matchLinkTag[9], matchLinkTag[4], 'LINK'));
309 		    	}
310 		    }
311 		    
312 		    
313 		    //matching: <style type="text/css" some attributes...> some content and '<!CDATA[' with any content allowed </style>
314 	//	    var regExpStyleTag = /((<style([\r\n]|[^<])*?type="text\/css"([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/style>))/igm;
315 		    var strRegExpStyleTag = '((<style'+regExprTagInternal+'>)'+regExprTagContent+'(</style>))'; //DETECT only "normal" style-TAG with content
316 		    var regExpStyleTag = new RegExp(strRegExpStyleTag,'igm');
317 		    // regExpStyleTag[0]: complete match
318 		    // regExpStyleTag[1]: script with start and end tag (if matched)
319 		    // regExpStyleTag[4]: TEXT content of style-tag (if present/matched)
320 		    var matchStyleTag = null;
321 		    
322 		    while(matchStyleTag = regExpStyleTag.exec(pureHtml)){
323 	//	    	matchIndex = matchStyleTag[1] ? 1 : -1;
324 		    	
325 		    	if(matchStyleTag[0]){//matchIndex != -1){
326 	//	    		console.warn("Remote: " + self.remoteaccess);
327 		    		if (self.remoteaccess) {
328 	//	    			self.headerContents += matchStyleTag[0].replace("<style", "<style loc=\"remote\"");//[matchIndex];
329 		    			self.headerContents += addMarkerAttribute('<style') + matchStyleTag[0].substring('<style'.length);
330 		    		} else {
331 		        		self.headerContents += matchStyleTag[0];
332 		    		}
333 		    		//remove script tag, and continue search
334 	//	    		pureHtml = pureHtml.substring(0,matchStyleTag.index) + pureHtml.substring(matchStyleTag.index + matchStyleTag[0].length);// pureHtml.replace(matchStyleTag[matchIndex], '');
335 	
336 		    	    removedScriptAndLinkHmtl.push({start: matchStyleTag.index, end: matchStyleTag.index + matchStyleTag[0].length});
337 
338 		    	    self.headerElements.push(new Layout.TagElement(matchStyleTag[2], matchStyleTag[4], 'STYLE'));
339 		    	}
340 		    }
341 		    
342 		    //only need to "process" removed script/link tags, if some were found:
343 		    if(removedScriptAndLinkHmtl.length > 0){
344 		    	
345 		    	removedScriptAndLinkHmtl.sort(function(a,b){
346 		    		return a.start - b.start;
347 		    	});
348 		    	
349 		    	var cleanedHtml = new Array();
350 		    	var remPos = 0;
351 		    	var removalElement = removedScriptAndLinkHmtl[0];
352 				
353 		    	for(var i=0, size = removedScriptAndLinkHmtl.length; i < size; ++i){
354 		    		removalElement = removedScriptAndLinkHmtl[i];
355 		    		
356 		    		var text = pureHtml.substring(remPos, removalElement.start);
357 		    		cleanedHtml.push(text);
358 		    		
359 		    		remPos = removalElement.end;
360 		    	}
361 		    	//add rest of the HTML if necessary
362 			    if(removalElement.end < pureHtml.length){
363 			    	cleanedHtml.push(pureHtml.substring(removalElement.end));
364 			    }
365 			    //replace HTML with the removed/clean version:
366 			    pureHtml = cleanedHtml.join('');
367 		    }
368 		    
369 			//TODO this is needed my be of interest for further processing 
370 			// (for processing partial HTML responses) 
371 			//-> should this be part of the framework? (i.e. "public" property for Layout; TODO add to stringify()?)
372 	    	this._processedDef = pureHtml;
373 	    
374 		    //FIXME reg-expr does not detect body-TAG, if body has no content (i.e. body="<body></body>")
375 		    var regExpBodyTag = /(<body([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/body>)/igm;
376 		    // matchBodyTag[0]: complete match
377 		    // matchBodyTag[1]: body start tag
378 		    // matchBodyTag[2]: last CHAR within body start tag, before closing, e.g. "...lskdjf>" -> "f"
379 		    // matchBodyTag[3]: body text content
380 		    // matchBodyTag[4]: last CHAR within body text content, before closing, e.g. "...lsk</body>" -> "k"
381 		    // matchBodyTag[5]: body end tag
382 		    var matchBodyTag = regExpBodyTag.exec(pureHtml);
383 		    
384 		    self.bodyContents = '';
385 		    
386 		    if(matchBodyTag && matchBodyTag[3]){
387 			    self.bodyContents += matchBodyTag[3];
388 		    }
389 		    else if(!ignoreMissingBody) {
390 		    	//TODO throw error?
391 		    	console.error('Layout.<constructor>: Layout template does not contain a <body> element!');
392 		    }
393 		    
394 		    
395 		    //TEST: experimental -> "remember" attributes of body tag
396 		    //NOTE this assumes that matchBodyTag-RegExpr starts with: /(<body([\r\n]|.)*?>) ... 
397 		    if(matchBodyTag && matchBodyTag[1] && matchBodyTag[1].length > '<body>'.length){
398 		    	
399 //		    	//NOTE: 1st case should really never occur.
400 //		    	var bodyAttrEnd = matchBodyTag[1].endsWith('/>')? matchBodyTag[1].length-2 : matchBodyTag[1].length-1;
401 //		    	var bodyAttr = '<div ' + matchBodyTag[1].substring('<body'.length, bodyAttrEnd) + '</div>';
402 //		    	bodyAttr = jQuery(bodyAttr);
403 		    	
404 		    	self.bodyAttributes = Layout.getTagAttr(matchBodyTag[1]);
405 		    }
406 		    		
407 		    //Extract title-tag
408 	    	var regExpTitleTag = /(<title([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/title>)/igm;
409 		    // matchTitleTag[0]: complete match
410 		    // matchTitleTag[1]: title start tag
411 		    // matchTitleTag[2]: last CHAR within title start tag, before closing, e.g. "...lskdjf>" -> "f"
412 		    // matchTitleTag[3]: title text content
413 		    // matchTitleTag[4]: last CHAR within title text content, before closing, e.g. "...lsk</title>" -> "k"
414 		    // matchTitleTag[5]: title end tag
415 		    var matchTitleTag = regExpTitleTag.exec(pureHtml);
416 		    
417 		    if(matchTitleTag && matchTitleTag[3]){
418 			    self.title = matchTitleTag[3];
419 	    	}
420 		    
421 		    var regExpDialogsTag = /(<dialogs([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/dialogs>)/igm;
422 		    // matchDialogsTag[0]: complete match
423 		    // matchDialogsTag[1]: dialogs start tag
424 		    // matchDialogsTag[2]: last CHAR within dialogs start tag, before closing, e.g. "...lskdjf>" -> "f"
425 		    // matchDialogsTag[3]: dialogs text content
426 		    // matchDialogsTag[4]: last CHAR within dialogs text content, before closing, e.g. "...lsk</dialogs>" -> "k"
427 		    // matchDialogsTag[5]: dialogs end tag
428 		    var matchDialogsTag = regExpDialogsTag.exec(pureHtml);
429 		    
430 		    self.dialogsContents = '';
431 		    if(matchDialogsTag && matchDialogsTag[3]){
432 			    self.dialogsContents += matchDialogsTag[3];
433 		    }
434 		    
435 		    var parseBodyResult = parser.parse(this.bodyContents, this);
436 		    for(var i=0, size = parseBodyResult.yields.length; i < size ; ++i){
437 		    	this.yields.push(new YieldDeclaration(parseBodyResult.yields[i], ViewConstants.CONTENT_AREA_BODY));
438 		    }
439 		    
440 		    var parseDialogResult = parser.parse(this.dialogsContents, this);
441 		    for(var i=0, size = parseDialogResult.yields.length; i < size ; ++i){
442 		    	this.yields.push(new YieldDeclaration(parseDialogResult.yields[i], ViewConstants.CONTENT_AREA_DIALOGS));
443 		    }
444 	    
445 	    }//END: if(this.def)
446 	    
447 	}//END: Layout()
448 
449 
450 	/**
451 	 * HELPER: extracts TAG attributes into an JSON-object
452 	 * 
453 	 * @memberOf Layout
454 	 * @private
455 	 * @static
456 	 * 
457 	 * @param {String} str
458 	 * 			the start-TAG as String
459 	 * @param {Object} [target] OPTIONAL
460 	 * 			the target-object to which the extracted attributes will be attached
461 	 * 			if omitted, a new, empty object will be created
462 	 * 
463 	 * @return {Object} the object with the extracted attributes as properties
464 	 * 					(if <em>target</em> was provided, then this is the <em>target</em> object)
465 	 * 
466 	 * @example
467 	 * e.g. <body onload="on_load();" class = 'biggestFont'>
468 	 * -->
469 	 * {"onload": "on_load()", "class": "biggestFont"}
470 	 * 
471 	 */
472 	Layout.getTagAttr = function(str, target){
473 		
474 		//RegExp for:
475 		// name = "..."
476 		//or
477 		// name = '...'
478 		//
479 		//NOTE: the RegExp does not extract "single properties", as e.g. <tag required>
480 		//      ... instead, for extraction, they must be specified as follows: <tag required="...">
481 		//NOTE: values MUST be enclosed in double-quotes or quotes, "quote-less" attribute values cannot be extracted!
482 		//      ... e.g. NOT: <tag name=value>, instead: <tag name="value"> or <tag name='value'>
483 		//NOTE: the RegExp also detects escaped double-quotes and quotes respectively
484 		var regExpr = /\s+([^=]*?)\s*=\s*(("((\\"|[^"])*)")|('((\\'|[^'])*)'))/igm;
485 		var result = target || {};
486 		var match;
487 	    
488 	    while(match = regExpr.exec(str)){
489 	    	
490 	    	if(match[4]){
491 				result[match[1]] = match[4];
492 			}
493 			else if(match[7]){
494 				result[match[1]] = match[7];
495 			}
496 		}
497 		return result;
498 	};
499 	
500 	/**
501 	 * HELPER class: extract raw TAG Strings into a property-object
502 	 * 
503 	 * @public
504 	 * @constructor
505 	 * @param {String} tag
506 	 * 			the start TAG
507 	 * @param {String} content
508 	 * 			the TEXT content of the TAG (may be empty)
509 	 * @param {String} tagType
510 	 *  		the TAG type, e.g. "SCRIPT"
511 	 * 
512 	 * @returns {Layout.TagElement}
513 	 * 
514 	 * 		prop {String} tagName: the TAG type, e.g. "SCRIPT"
515 	 * 		prop {String} textContent: the TEXT content of the TAG (may be an empty String)
516 	 * 		prop EXTRACTED ATTRIBUTES: the extracted attributes form the start-TAG
517 	 * 
518 	 * 		func {String} attr(STRING name): returns the attribute-value for name (may be undefined)
519 	 * 		func {String} html(): returns the TEXT content of the TAG (may be an empty String)
520 	 * 
521 	 * 		func {Boolean} isScript(): returns TRUE if tagType is SCRIPT
522 	 * 		func {Boolean} isStyle():  returns TRUE if tagType is STYLE
523 	 * 		func {Boolean} isLink():   returns TRUE if tagType is LINK
524 	 */
525 	Layout.TagElement = function TagElement(tag, content, tagType){
526 		
527 		this.tagName = tagType;
528 		this.textContent = content || '';
529 		
530 		var tis = this;
531 		tis.attr = function(name){
532 			return this[name];
533 		};
534 		tis.html = function(){
535 			return this.textContent;
536 		};
537 		tis.isScript = function(){return this.tagName === 'SCRIPT';};
538 		tis.isStyle  = function(){return this.tagName === 'STYLE'; };
539 		tis.isLink   = function(){return this.tagName === 'LINK';  };
540 		
541 		//extract attributes as properties from TAG string:
542 		Layout.getTagAttr(tag, tis);
543 		return tis;
544 	};
545 	
546 	/**
547 	 * This methods returns an associative array holding the contents of the different containers: header, body, footer and dialogs.
548 	 * 
549 	 * @function
550 	 * @returns {Array} An associative array holding the contents of the different containers: header, body, footer and dialogs
551 	 * @public
552 	 */
553 	Layout.prototype.getYields = function(){
554 		return this.yields;
555 	};
556 
557 	/**
558 	 * This methods returns the contents of the header part of the layout.
559 	 * 
560 	 * @function
561 	 * @returns {String} The contents of the header part of the layout
562 	 * @public
563 	 */
564 	Layout.prototype.getHeaderContents = function(){
565 	    return this.headerContents;
566 	};
567 
568 	/**
569 	 * This methods returns the contents of the dialog part of the layout.
570 	 * 
571 	 * @function
572 	 * @returns {String} The contents of the dialog part of the layout
573 	 * @public
574 	 */
575 	Layout.prototype.getDialogsContents = function(){
576 		return this.dialogsContents;
577 	};
578 
579 	/**
580 	 * This methods returns the contents of the body part of the layout.
581 	 * 
582 	 * @function
583 	 * @returns {String} The contents of the body part of the layout
584 	 * @public
585 	 */
586 	Layout.prototype.getBodyContents = function(){
587 	    return this.bodyContents;
588 	};
589 
590 	/**
591 	 * Gets the name of the layout.
592 	 * 
593 	 * @function
594 	 * @returns {String} The name of the layout.
595 	 * @public
596 	 */
597 	Layout.prototype.getName = function(){
598 	    return this.name;
599 	};
600 
601 Layout.prototype.stringify = function(){
602 
603 	// "plain properties" list
604 	var propList = [
605 	     'name', 
606 	     'remoteaccess',
607 	     'def',
608 	     'headerElements',
609 	     'headerContents',
610 	     'title',
611 	     'bodyContents',
612 	     'dialogsContents',
613 	     'markerAttributeName',
614 	     'markerAttributeValue',
615 	     'markerUseSingleQuotes',
616 	     'bodyAttributes'
617 	];
618 
619 	//complex Array-properties
620 	var arrayPropList = [
621    	     'yields' //element type: YieldDeclaration (stringify-able)
622    	];
623 
624 	//function for iterating over the property-list and generating JSON-like entries in the string-buffer
625 	var appendStringified = storageUtils.appendStringified;
626 	
627 	var moduleNameString = '"'+this.name+'Layout"';
628 
629 	//TODO use requirejs mechanism? (NOTE there may occur timing problems for loading/registering the JS file, and querying the PresentationManager for it ...)
630 //	var sb = ['define('+moduleNameString+', ["storageUtils"], function(parser){ return parser.restoreObject({ classConstructor: "layout"', ','];
631 	
632 	var sb = ['require("storageUtils").restoreObject({ classConstructor: "layout"', ','];
633 		
634 	appendStringified(this, propList, sb);
635 	
636 	sb.push( 'initPublish: function(){ require("presentationManager").addLayout(this); }');
637 	sb.push(',');
638 	
639 	//non-primitives array-properties with stringify() function:
640 	appendStringified(this, arrayPropList, sb, null, function arrayValueExtractor(name, arrayValue){
641 		
642 		var buf =['['];
643 		for(var i=0, size = arrayValue.length; i < size; ++i){
644 			buf.push(arrayValue[i].stringify());
645 			buf.push(',');
646 		}
647 		//remove last comma
648 		if(arrayValue.length > 0){
649 			buf.splice( buf.length - 1, 1);
650 		}
651 		buf.push(']');
652 		
653 		return buf.join('');
654 	});
655 	
656 	
657 	//if last element is a comma, remove it
658 	if(sb[sb.length - 1] === ','){
659 		sb.splice( sb.length - 1, 1);
660 	}
661 	
662 	//TODO use requirejs mechanism? (see remark above)
663 //	sb.push(' }, true); });\n require(['//<- add require-call, so that this JS-file adds itself to the loaded dependencies in requirejs
664 //			+ moduleNameString + ']);');
665 	
666 	sb.push(' }, true, '+storageUtils.STORAGE_FILE_FORMAT_NUMBER+');');
667 	
668 	return sb.join('');
669 };
670 
671 return Layout;
672 	
673 });
674