1 /*!
  2  * templateLayout.compiler
  3  * Copyright (c) 2011 Pablo Escalada
  4  * MIT Licensed
  5  */
  6 (function (templateLayout) {
  7     var compiler, log, rootTemplate;
  8     log = wef.logger("templateLayout.compiler");
  9     log.info("load compiler module");
 10 
 11     function parseDisplay(displayValue) {
 12         /*
 13          * Name:    ‘display’
 14          * New value:    <display-type>? && [ [ <string> [ / <row-height> ]? ]+ ] <col-width>*
 15          * Percentages:    N/A
 16          * Computed value:    specified value
 17          *
 18          * The <display-type> is one of the following keywords. An omitted keyword is equivalent to ‘block’.
 19          * <display-type> = inline | block | list-item | inline-block | table | inline-table
 20          * | table-row-group | table-header-group | table-footer-group | table-row
 21          * | table-column-group | table-column | table-cell | table-caption | none
 22          *
 23          * Each <string> consist of one or more at signs (“@”), letters (or digits, see <letter> below),
 24          * periods (“.”) and spaces
 25          *
 26          * Each <row-height> sets the height of the preceding row. The default is ‘auto’.
 27          * The values can be as follows:
 28          * <length> An explicit height for that row. Negative values make the template illegal. If the length is
 29          * expressed in ‘gr’ units, these refer to the inherited grid, not the grid defined by the template itself.
 30          * auto The row's height is determined by its contents.
 31          * * (asterisk) All rows with an asterisk will be of equal height.
 32          *
 33          * Each <col-width> can be one of the following:
 34          * <length> An explicit width for that column. Negative values make the template illegal.
 35          * * (asterisk.) All columns with a ‘*’ have the same width. See the algorithm below.
 36          * max-content, min-content, minmax(p,q), fit-content
 37          */
 38         log.info("compiling display...");
 39         log.debug("display source: ", displayValue);
 40         /**
 41          * @namespace Preprocessed template display info
 42          * @name DisplayMetadata
 43          */
 44         var displayMetadata =
 45         /**
 46          * @lends DisplayMetadata#
 47          */
 48         {
 49             /**
 50              * Display type. Currently unused
 51              * @type string
 52              */
 53             displayType:undefined,
 54             /**
 55              * Array of rows. strings are cleaned
 56              * @type string[]
 57              */
 58             grid:[],
 59             /**
 60              * Array of columnns widths
 61              * @type string[]
 62              */
 63             widths:[]
 64         },
 65             allRefExp = /\s*(none|inline)?\s*(:?(\"[A-Za-z0-9\.@ ]+\")\s*(:?\/ ?(\*|\d+(:?px|%)))?)\s*((:?(:?(:?\d+(:?px|%))|\*)\s*)*)/gi,
 66             found,
 67             displayTypeFound,
 68             gridNotFound,
 69             completed;
 70         //all without displayType   (:?(\"[A-Za-z0-9\.@ ]+\")\s*(:?\/(\*|\d+(:?em|px|%)))?\s*)((:?(:?(:?\d+(:?em|px|%))|\*) *)*)
 71         // /\s*(inline|block|list-item|inline-block|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|none)?\s*(:?(\"[A-Za-z0-9\.@ ]+\")\s*(:?\/(\*|\d+(:?em|px|%)))?\s*)((:?(:?(:?\d+(:?em|px|%))|\*) *)*)/g
 72         if (displayValue !== undefined) {
 73             while ((found = allRefExp.exec(displayValue)) !== null) {
 74                 if (completed) {
 75                     log.error("Invalid template, invalid width definition");
 76                     throw new Error("Invalid template, width  definition");
 77                 }
 78                 if (found[1]) {
 79                     if (displayTypeFound) {
 80                         log.error("Invalid template, multiple display type");
 81                         throw new Error("Invalid template, multiple display type");
 82                     }
 83                     displayMetadata.displayType = found[1];
 84                     displayTypeFound = true;
 85                 }
 86                 if (found[3]) {
 87                     if (gridNotFound) {
 88                         log.error("Invalid template, invalid grid definition");
 89                         throw new Error("Invalid template, invalid grid definition");
 90                     }
 91                     displayMetadata.grid.push({rowText:found[3].replace(/"/g, "").replace(/\s*/, ""), height:undefined});
 92                 } else {
 93                     gridNotFound = true;
 94                 }
 95                 if (found[5]) {
 96                     if (!displayMetadata.grid[displayMetadata.grid.length - 1]) {
 97                         log.error("Invalid template, invalid height definition");
 98                         throw new Error("Invalid template, height definition");
 99                     }
100                     displayMetadata.grid[displayMetadata.grid.length - 1].height = found[5];
101                 }
102                 if (found[7]) {
103                     displayMetadata.widths = found[7].split(/\s+/);
104                     completed = true;
105                 }
106             }
107         }
108         log.info("display result: ", displayMetadata);
109         return displayMetadata;
110     }
111 
112     /**
113      * @namespace Top level template. Used as "fake page template"
114      */
115     rootTemplate = function () {
116 
117         var that =
118         /**
119          * @lends rootTemplate#
120          */
121         {
122             /**
123              * Inserts given template into TOM.
124              * If template "isRoot", inserts here, else looks inside TOM and
125              * inserts in place.
126              *
127              * @param {Template}aTemplate the template
128              */
129             insert:function (aTemplate) {
130                 log.info("add template ", aTemplate.selectorText);
131                 if (aTemplate.isRoot()) {
132                     log.debug("insert as root", aTemplate);
133                     that.rows.push(aTemplate);
134                     return true;
135                 } else {
136                     log.debug("search position :", aTemplate.position.position);
137                     return that.rows.some(function (element) {
138                         return element.insert(aTemplate);
139                     });
140                 }
141             },
142             /**
143              * Templates stored into the root template
144              * @type gridRow[]
145              */
146             rows:[]
147         };
148 
149         return that;
150     }();
151 
152     function parsePosition(positionValue) {
153         var positionMetadata, matched, positionRegExp;
154         /*
155          * Name:    position
156          * New value:    <letter> | same
157          * Percentages:    N/A
158          * Computed value:    ‘<letter>’ or ‘static’; see text
159          *
160          * <letter> must be a single letter or digit, with category Lu, Ll, Lt or Nd in Unicode [UNICODE]),
161          * or a “@” symbol
162          */
163         log.info("compiling position...");
164         log.debug("position source: ", positionValue);
165         /**
166          * @namespace Preprocessed template position info
167          * @name PositionMetadata
168          */
169         positionMetadata =
170         /**
171          * @lends PositionMetadata#
172          */
173         {
174             /**
175              * Position string
176              * @type string
177              */
178             position:null
179         };
180         positionRegExp = /^\s*(same|[a-zA-Z0-9])\s*$/i;
181         if (positionValue !== undefined) {
182             matched = positionValue.match(positionRegExp);
183             if (matched === null) {
184                 log.info("Unexpected value at ", positionValue);
185                 //throw new Error("Unexpected value at ", positionValue);
186                 return positionMetadata;
187             }
188             positionMetadata.position = matched[1];
189         }
190         log.info("position result: ", positionMetadata);
191         return positionMetadata;
192     }
193 
194     function parseProperties(rule) {
195         var preProcessTemplate = {};
196         log.info("compiling properties...");
197         log.debug("properties source: ", rule);
198         preProcessTemplate.selectorText = rule.selectorText;
199         preProcessTemplate.display = parseDisplay(rule.declaration[templateLayout.fn.constants.DISPLAY]);
200         preProcessTemplate.position = parsePosition(rule.declaration[templateLayout.fn.constants.POSITION]);
201         log.info("properties result: ", preProcessTemplate);
202         return preProcessTemplate;
203     }
204 
205     /**
206      * Creates a compiler
207      *
208      * @class CSS template compiler
209      */
210     compiler = function () {
211         return new compiler.prototype.init();
212     };
213 
214     /**
215      * Extension point.
216      * Elements added to compiler.fn extend compiler functionality
217      */
218     compiler.fn = compiler.prototype;
219 
220     compiler.prototype = {
221         constructor:compiler,
222         /**
223          * @ignore
224          */
225         init:function () {
226             return this;
227         },
228         /**
229          * Compiles given parser data</p>
230          *
231          * @param {ParserBufferEntry[]}buffer parser generated data
232          * @returns {rootTemplate} Template Object Model
233          */
234         compile:function (buffer) {
235             var selectorText, preProcessTemplate, inserted, template;
236             log.info("compile...");
237             log.debug("buffer: ", buffer);
238 
239             for (selectorText in buffer) {
240                 if (buffer.hasOwnProperty(selectorText)) {
241                     log.debug("next buffer element: ", selectorText);
242                     log.group();
243                     preProcessTemplate = parseProperties(buffer[selectorText]);
244                     if (this.isEmptyDisplay(preProcessTemplate.display) && this.isEmptyPosition(preProcessTemplate.position)) {
245                         log.groupEnd();
246                         log.info("preProcess: empty template", preProcessTemplate);
247                     } else {
248                         log.debug("preProcess:", preProcessTemplate);
249                         template = compiler.fn.templateBuilder(preProcessTemplate).createTemplate();
250                         inserted = rootTemplate.insert(template);
251                         log.groupEnd();
252                         log.info("element insertion...", inserted ? "[OK]" : "ERROR!");
253                     }
254                 }
255             }
256             log.debug("compile... OK");
257             return rootTemplate;
258         },
259         /**
260          * Checks if display is empty
261          * @param {DisplayMetadata}display compiled display
262          * @returns {boolean}true if display.grid.length === 0
263          */
264         isEmptyDisplay:function (display) {
265             return display.grid.length === 0;
266         },
267         /**
268          * Checks is position is empty
269          * @param {PositionMetadata}position compiled position
270          * @returns {boolean}true if position.position === null
271          */
272         isEmptyPosition:function (position) {
273             return position.position === null;
274         }
275     };
276 
277     compiler.prototype.init.prototype = compiler.prototype;
278 
279     templateLayout.fn.compiler = compiler;
280 
281     (function (global) {
282         var gridSlot;
283         log.info("load gridSlot module...");
284 
285         /**
286          * Creates a slot.
287          *
288          * @param {string}slotText slot identifier
289          * @param {integer}rowIndex row index
290          * @param {integer}colIndex column index
291          * @param {Object}[options] optional initialization
292          * @param {integer}options.rowSpan row span number
293          * @param {integer}options.colSpan column span number
294          * @param {boolean}options.allowDisconnected row span number
295          * @param {boolean}options.allowColSpan row span number
296          * @param {boolean}options.allowRowSpan row span number
297          *
298          * @class Template slot.
299          * Features:
300          * <ul>
301          *     <li>column and row span</li>
302          *     <li>disconnected regions</li>
303          * </ul>
304          */
305         gridSlot = function (slotText, rowIndex, colIndex, options) {
306             log.debug("slot", slotText + "...");
307             return new gridSlot.prototype.init(slotText, rowIndex, colIndex, options);
308         };
309 
310         gridSlot.prototype = {
311             constructor:gridSlot,
312             /**
313              * slot identifier
314              * @type string
315              */
316             slotText:undefined,
317             /**
318              * row index. If row spans, topmost row index
319              * @type integer
320              */
321             rowIndex:undefined,
322             /**
323              * column index. If column spans, leftmost column index
324              * @type integer
325              */
326             colIndex:undefined,
327             /**
328              * row span number
329              * @type integer
330              */
331             rowSpan:1,
332             /**
333              * column span number
334              * @type integer
335              */
336             colSpan:1,
337             /**
338              * Can exists more than one group of slots with the same identifier?
339              * @type boolean
340              */
341             allowDisconnected:false,
342             /**
343              * is column span allowed?
344              * @type boolean
345              */
346             allowColSpan:false,
347             /**
348              * is row span allowed?
349              * @type boolean
350              */
351             allowRowSpan:false,
352             /**
353              * HTML node that maps the slot
354              * @type HTMLElement
355              */
356             htmlNode:undefined,
357             /**
358              * Stores the sum of children heights
359              * @type integer
360              */
361             contentHeight:0,
362 
363             /**
364              * @ignore
365              * see gridSlot.constructor
366              */
367             init:function (slotText, rowIndex, colIndex, options) {
368                 this.slotText = slotText;
369                 this.rowIndex = rowIndex;
370                 this.colIndex = colIndex;
371                 this.contentHeight = 0;
372                 //options
373                 this.rowSpan = 1;
374                 this.colSpan = 1;
375                 this.height = "auto";
376 
377                 this.allowDisconnected = false;
378                 this.allowColSpan = true;
379                 this.allowRowSpan = true;
380                 this.htmlNode = undefined;
381                 wef.extend(this, options, ["rowSpan", "colSpan", "allowDisconnected", "allowColSpan", "allowRowSpan"]);
382             }
383         };
384 
385         /**
386          * Extension point
387          */
388         gridSlot.fn = gridSlot.prototype;
389         gridSlot.prototype.init.prototype = gridSlot.prototype;
390 
391         global.gridSlot = gridSlot;
392         log.info("load gridSlot module... [OK]");
393     })(compiler.fn);
394 
395 
396     (function (global) {
397         var gridRow;
398         log.info("load gridRow module...");
399         /**
400          * Creates a row
401          *
402          * @param {string}rowText row slots identifiers
403          * @param {integer}rowIndex row index
404          * @param {gridSlot[]}slots row gridSlot elements
405          * @param {Object}[options] optional initialization
406          * @param {string}options.height row height as string
407          *
408          * @class Template row. Store {@link gridSlot} elements
409          */
410         gridRow = function (rowText, rowIndex, slots, options) {
411             log.debug("row...");
412             return new gridRow.prototype.init(rowText, rowIndex, slots, options);
413         };
414         gridRow.prototype = {
415             constructor:gridRow,
416             /**
417              * Row slots identifiers
418              * @type string
419              */
420             rowText:undefined,
421             /**
422              * Row index
423              * @type integer
424              */
425             rowIndex:undefined,
426             /**
427              * Slots row gridSlot elements
428              * @type gridSlot[]
429              */
430             slots:[],
431             /**
432              * Number of slots in row
433              * @type integer
434              */
435             length:undefined,
436             /**
437              * Row height as string
438              * @type string
439              */
440             height:undefined,
441             /**
442              * @ignore
443              * see constructor
444              */
445             init:function (rowText, rowIndex, slots, options) {
446                 this.rowText = rowText;
447                 this.rowIndex = rowIndex;
448                 this.slots = slots;
449                 this.length = this.rowText.length;
450                 //options
451                 this.height = undefined;
452                 wef.extend(this, options, ["height"]);
453             }
454         };
455         /**
456          * Extension point
457          */
458         gridRow.fn = gridRow.prototype;
459         gridRow.prototype.init.prototype = gridRow.prototype;
460 
461         global.gridRow = gridRow;
462         log.info("load gridRow module... [OK]");
463     })(compiler.fn);
464 
465 
466     (function (global) {
467         var grid;
468         log.info("load grid module...");
469         /**
470          * Creates a template grid
471          *
472          * @param {gridRow[]}rows template rows
473          * @param [options] optional initialization
474          *
475          * @class Template grid, represented as a tabular structure
476          */
477         grid = function (rows, options) {
478             log.debug("grid...");
479             return new grid.prototype.init(rows, options);
480         };
481 
482         grid.prototype = {
483             constructor:grid,
484             /**
485              * Template rows
486              * @type gridRow[]
487              */
488             rows:undefined,
489             /**
490              * Hash table like structure that stores nested Template objects.
491              * <ul>
492              *     <li>key = template position</li>
493              *     <li>value = array of template objects</li>
494              * <ul>
495              *
496              * @type Object
497              */
498             filledSlots:undefined,
499             /**
500              * columns widths
501              * @type string[]
502              */
503             widths:[],
504             /**
505              * minimums columns widths
506              * @type string[]
507              */
508             minWidths:[],
509             /**
510              * preferred columns widths
511              * @type string[]
512              */
513             preferredWidths:[],
514             /**
515              * Number of rows
516              * @type integer
517              */
518             rowNumber:undefined,
519             /**
520              * Number of columns
521              * @type integer
522              */
523             colNumber:undefined,
524             /**
525              * @ignore
526              */
527             init:function (rows, options) {
528                 this.rows = rows;
529                 this.filledSlots = {};
530                 //options
531                 this.widths = [];
532                 this.minWidths = [];
533                 this.preferredWidths = [];
534                 wef.extend(this, options, ["widths", "minWidths", "preferredWidths"]);
535                 this.colNumber = this.widths.length;
536                 this.rowNumber = rows.length;
537             },
538             /**
539              * Checks if grid contains specified slot identifier
540              *
541              * @param {string}slotIdentifier slot identifier
542              * @returns {boolean} true if rows[i].rowText contains slotIdentifier,
543              * else if not
544              */
545             hasSlot:function hasSlot(slotIdentifier) {
546                 var result;
547                 result = this.rows.some(function (row) {
548                     var regExp = new RegExp(slotIdentifier);
549                     return regExp.exec(row.rowText);
550                 });
551                 log.debug("hasSlot " + slotIdentifier + "?", result ? "yes" : "no");
552                 return result;
553             },
554             /**
555              * Gets the "@" slot OR topmost left slot
556              */
557             getDefaultSlot:function () {
558                 var firstLetterSlot, definedDefaultSlot = false;
559                 this.rows.forEach(function (row) {
560                     if (definedDefaultSlot) {
561                         return; //skip row
562                     }
563                     Array.prototype.some.call(row.rowText, function (slotText, slotIndex) {
564                         if (slotText === "@") {
565                             definedDefaultSlot = row.slots[slotIndex];
566                             return true;
567                         }
568                         if (!firstLetterSlot && slotText !== ".") {
569                             firstLetterSlot = row.slots[slotIndex];
570                             return false; //continue searching @
571                         }
572                     });
573                 });
574                 return definedDefaultSlot || firstLetterSlot;
575             },
576             /**
577              * Traverses this grid and its children and insert the given
578              * template in place
579              * @param {template}aTemplate given template
580              * @returns {boolean} true if inserted, false if not
581              */
582             setTemplate:function (aTemplate) {
583                 var row, tmp, result;
584                 if (this.hasSlot(aTemplate.position.position)) {
585                     //push template
586                     tmp = this.filledSlots[aTemplate.position.position] || [];
587                     tmp.push(aTemplate);
588                     this.filledSlots[aTemplate.position.position] = tmp;
589                     log.debug("grid [" + aTemplate.position.position + "] =", aTemplate);
590                     return true;
591                 } else {
592                     result = this.rows.some(function (row) {
593                         var result;
594                         result = row.slots.some(function (slotId) {
595                             var result;
596                             result = this.filledSlots[slotId.slotText] && this.filledSlots[slotId.slotText].some(function (currentTemplate) {
597                                 return !currentTemplate.isLeaf() && currentTemplate.insert(aTemplate);
598                             }, this);
599                             if (!result) {
600                                 log.debug("not found, try another slot");
601                             }
602                             return result;
603                         }, this);
604                         if (!result) {
605                             log.debug("not found, try another row");
606                         }
607                         return result;
608                     }, this);
609                     if (!result) {
610                         log.debug("not found, try another branch");
611                     }
612                     return result;
613                 }
614             }
615         };
616         /**
617          * Extension point
618          */
619         grid.fn = grid.prototype;
620         grid.prototype.init.prototype = grid.prototype;
621 
622         global.grid = grid;
623         log.info("load grid module... [OK]");
624     })(compiler.fn);
625 
626     (function (global) {
627         var template;
628         log.info("load template module...");
629         /**
630          * Creates a template
631          *
632          * @param {string}selectorText CSS selector
633          * @param {PositionMetadata}position raw position information
634          * @param {DisplayMetadata}display raw display information
635          * @param {grid}grid its physical structure
636          *
637          * @class A Template has a grid, the raw information generated by
638          * the preprocessor and a link to the DOM reference node
639          */
640         template = function (selectorText, position, display, grid) {
641             log.debug("template...");
642             return new template.prototype.init(selectorText, position, display, grid);
643         };
644 
645         template.prototype = {
646             constructor:template,
647             /**
648              * Link to parent template. Unused
649              */
650             parentTemplate:undefined,
651             /**
652              * CSS selector
653              * @type string
654              */
655             selectorText:undefined,
656             /**
657              * Raw display information
658              * @type DisplayMetadata
659              */
660             display:undefined,
661             /**
662              * Raw position information
663              * @type PositionMetadata
664              */
665             position:undefined,
666             /**
667              * Its grid, the physical representation
668              * @type grid
669              */
670             grid:undefined,
671             /**
672              * A link to the DOM reference node
673              * @type HTMLElement
674              */
675             htmlNode:undefined,
676             /**
677              * @ignore
678              */
679             init:function (selectorText, position, display, grid) {
680                 this.selectorText = selectorText;
681                 this.display = display;
682                 this.position = position;
683                 this.grid = grid;
684                 this.htmlNode = undefined;
685             },
686             /**
687              * Checks if has parent
688              *
689              * @returns {boolean} true if doesn't have a position value,
690              * false if not
691              */
692             isRoot:function () {
693                 return this.position.position === null;
694             },
695             /**
696              * Checks if has children
697              *
698              * @returns {boolean} true if has a grid value, false if not
699              */
700             isLeaf:function () {
701                 return this.display.grid.length === 0;
702             },
703             /**
704              * Insert given template in this template or its children.
705              * Calls grid.setTemplate()
706              *
707              * @param aTemplate given template
708              * @returns {boolean} true if inserted, false if not
709              */
710             insert:function (aTemplate) {
711                 log.debug("trying to insert into ", this);
712                 return this.grid.setTemplate(aTemplate);
713             }
714         };
715         /**
716          * Extension point
717          */
718         template.fn = template.prototype;
719         template.prototype.init.prototype = template.prototype;
720 
721         global.template = template;
722         log.info("load template module... [OK]");
723     })(compiler.fn);
724 
725     (function (global) {
726         var templateBuilder;
727         log.info("load templateBuilder module...");
728 
729         /**
730          * Creates a gridBuffer
731          *
732          * @class {@link templateBuilder} temporal data structure.
733          * Only for internal use. Doesn´t check array index
734          * @name GridBuffer
735          */
736         function GridBuffer() {
737             this._rows = [];
738         }
739 
740         GridBuffer.prototype =
741         /**
742          * @lends GridBuffer#
743          */
744         {
745             constructor:GridBuffer,
746             /**
747              * Array of rows
748              * @type gridRow[]
749              * @public
750              */
751             _rows:[],
752             /**
753              * Add the slot to _rows
754              * @param {gridSlot}slot the slot
755              */
756             add:function (slot) {
757                 if (!Array.isArray(this._rows[slot.rowIndex])) {
758                     this._rows[slot.rowIndex] = [];
759                 }
760                 this._rows[slot.rowIndex][slot.colIndex] = slot;
761             },
762             /**
763              * Gets the slot with the specified slot Text
764              * @param {string}slotText slot text
765              * @returns {gridSlot} the specified slot
766              */
767             getSlot:function (slotText) {
768                 var result = [];
769                 this._rows.forEach(function (row) {
770                     row.forEach(function (slot) {
771                         if (slot.slotText === slotText) {
772                             result.push(slot);
773                         }
774                     }, this);
775                 }, this);
776                 return result;
777             },
778             /**
779              * Gets rows
780              * @returns {gridRow[]} _rows
781              */
782             getRows:function () {
783                 return this._rows;
784             },
785             /**
786              * Gets the specified row in _rows
787              * @param index row index
788              * @returns {gridRow} the specified row
789              */
790             getRow:function (index) {
791                 return this._rows[index];
792             }
793         };
794 
795         /**
796          * @class
797          */
798         templateBuilder = function (source) {
799             log.debug("templateBuilder...");
800             return new templateBuilder.prototype.init(source);
801         };
802 
803         templateBuilder.prototype = {
804             constructor:templateBuilder,
805             buffer:undefined,
806             source:undefined,
807             init:function (source) {
808                 this.source = source;
809                 this.buffer = new GridBuffer();
810             },
811             createTemplate:function () {
812                 var display, grid, gridRows, gridOptions = {}, heights;
813                 display = this.source.display;
814                 grid = null;
815                 if (display.grid.length > 0) {
816                     this._addGrid(this.source.display);
817 
818                     heights = this._normalizeHeights(this.source.display);
819                     gridRows = this.buffer.getRows().map(function (row, index) {
820                         return compiler.fn.gridRow(this.source.display.grid[index].rowText, index, row, {height:heights[index]});
821                     }, this);
822 
823                     gridOptions.widths = this._normalizeWidths(this.source.display.widths);
824                     gridOptions.minWidths = gridOptions.widths.map(function (col) {
825                         return templateBuilder.fn._getMinWidth(col);
826                     });
827                     gridOptions.preferredWidths = gridOptions.widths.map(function (col) {
828                         return templateBuilder.fn._getPreferredWidth(col);
829                     });
830                     grid = compiler.fn.grid(gridRows, gridOptions);
831                 } else {
832                     //TODO:fixme
833                 }
834 
835                 return compiler.fn.template(this.source.selectorText, this.source.position, display, grid);
836             },
837             _addGrid:function (display) {
838                 if (display.grid.length > 0) {
839                     display.grid.forEach(function (row, rowIndex) {
840                         this._addGridRow(row, rowIndex);
841                     }, this);
842                 }
843             },
844             checkRowSpan:function (slotText, rowIndex, colIndex) {
845                 var previousRow, oldRowSpan, candidateRowSpan, slotGroups;
846                 slotGroups = this.buffer.getSlot(slotText);
847                 if (!slotGroups[0].allowRowSpan) {
848                     log.info("Slot don't allow row span");
849                     return false;
850                 }
851 
852                 if (rowIndex > 0) {
853                     return slotGroups.some(function (slot) {
854                         previousRow = this.source.display.grid[rowIndex - 1];
855                         if (previousRow.rowText.charAt(colIndex) === slotText) {
856                             oldRowSpan = slot.rowSpan;
857                             candidateRowSpan = (rowIndex - slot.rowIndex) + 1;
858                             if (candidateRowSpan == oldRowSpan) {
859                                 //do nothing
860                                 log.debug("Slot row span... [OK]");
861                                 return true;
862                             }
863                             if (candidateRowSpan == oldRowSpan + 1) {
864                                 slot.rowSpan++;
865                                 this.buffer.add(slot);
866                                 log.debug("Slot row span... [OK]");
867                                 return true;
868                             } else {
869                                 return false;
870                             }
871                         } else {
872                             return false;
873                         }
874                     }, this);
875                 } else {
876                     return false;
877                 }
878             },
879             checkColSpan:function (slotText, row, colIndex) {
880                 var slotGroups, oldColSpan, candidateColSpan;
881                 slotGroups = this.buffer.getSlot(slotText);
882                 if (!slotGroups[0].allowColSpan) {
883                     log.info("Slot don't allow col span");
884                     return false;
885                 }
886                 if (colIndex > 0) {
887                     return slotGroups.some(function (slot) {
888                         if (row.rowText[colIndex - 1] === slotText) {
889                             oldColSpan = slot.colSpan;
890                             candidateColSpan = (colIndex - slot.colIndex) + 1;
891                             if (candidateColSpan == oldColSpan + 1) {
892                                 slot.colSpan++;
893                                 this.buffer.add(slot);
894                                 log.debug("Slot col span... [OK]");
895                                 return true;
896                             } else {
897                                 return false;
898                             }
899                         } else {
900                             return false;
901                         }
902                     }, this);
903                 } else {
904                     return false;
905                 }
906             },
907             _addGridRow:function (row, rowIndex) {
908                 var identifiers, colSpan, rowSpan, regions;
909                 identifiers = Array.prototype.map.call(row.rowText, function (substring) {
910                     return substring.charAt(0);
911                 });
912 
913                 identifiers.forEach(function (slotText, colIndex) {
914                     //slot doesn't exist
915                     if (this.buffer.getSlot(slotText).length === 0) {
916                         this._addGridSlot(slotText, rowIndex, colIndex, 1, 1);
917                         return; //no span or region violations
918                     }
919 
920                     if (this.buffer.getSlot(slotText)[0].allowColSpan) {
921                         colSpan = this.checkColSpan(slotText, row, colIndex);
922                     } else {
923                         colSpan = false;
924                     }
925 
926                     if (this.buffer.getSlot(slotText)[0].allowRowSpan) {
927                         rowSpan = this.checkRowSpan(slotText, rowIndex, colIndex);
928                     } else {
929                         rowSpan = false;
930                     }
931 
932                     if (this.buffer.getSlot(slotText)[0].allowDisconnected) {
933                         //TODO: region check
934                         log.info("Slot allow disconnected regions");
935                         this._addGridSlot(slotText, rowIndex, colIndex, 1, 1);
936                         regions = true;
937                     } else {
938                         regions = false;
939                     }
940 
941                     if (!colSpan && !rowSpan && !regions) {
942                         throw new Error("Invalid template definition at \"" + slotText + "\"");
943                     }
944 
945                 }, this);
946             },
947             _addGridSlot:function (slotText, rowIndex, colIndex, rowSpan, colSpan) {
948                 var options, allowDisconnected, allowColSpan, allowRowSpan;
949 
950                 if (slotText === ".") {
951                     allowDisconnected = true;
952                     allowColSpan = false;
953                     allowRowSpan = false;
954                 } else {
955                     allowDisconnected = false;
956                     allowColSpan = true;
957                     allowRowSpan = true;
958                 }
959                 options = {
960                     rowSpan:rowSpan,
961                     colSpan:colSpan,
962                     allowDisconnected:allowDisconnected,
963                     allowColSpan:allowColSpan,
964                     allowRowSpan:allowRowSpan
965                 };
966                 this.buffer.add(compiler.fn.gridSlot(slotText, rowIndex, colIndex, options));
967             },
968             _getMinWidth:function (width) {
969                 if (width === "*") {
970                     return "0px";
971                 } else {
972                     return width;
973                 }
974             },
975             _getPreferredWidth:function (width) {
976                 if (width === "*") {
977                     return "9999px";
978                 } else {
979                     return width;
980                 }
981             },
982             _normalizeWidths:function (widths) {
983                 var i, tmp = [], dotColumn;
984 
985                 function checkDotColumn(row) {
986                     return row.rowText.charAt(i) === ".";
987                 }
988 
989                 for (i = 0; i < this._getMaxColNumber(this.source.display.grid); i++) {
990                     if (widths[i] === undefined) {
991                         tmp[i] = "*";
992                     } else {
993                         if (/-/.test(widths[i])) {
994                             throw new Error("Invalid argument: negative width not allowed");
995                         }
996                     }
997                     if (this.source.display.grid.every(checkDotColumn)) {
998                         tmp[i] = "0px";
999                     }
1000                     if (widths[i] !== undefined) {
1001                         tmp[i] = widths[i];
1002                     }
1003                 }
1004                 return tmp;
1005             },
1006             _normalizeHeights:function (display) {
1007                 var dotLineRegExp = /^\.+$/, negativeHeight = /-/, i, tmp = [];
1008                 for (i = 0; i < display.grid.length; i++) {
1009                     if (display.grid[i].height === undefined) {
1010                         tmp[i] = "auto";
1011                     } else {
1012                         if (negativeHeight.test(display.grid[i].rowText)) {
1013                             throw new Error("Invalid argument: negative height not allowed");
1014                         }
1015                     }
1016                     if (dotLineRegExp.test(display.grid[i].rowText)) {
1017                         tmp[i] = "0px";
1018                     }
1019                     //can overwrite dotLine rule
1020                     if (display.grid[i].height !== undefined) {
1021                         tmp[i] = display.grid[i].height;
1022                     }
1023                 }
1024                 return tmp;
1025             },
1026             _getMaxColNumber:function (grid) {
1027                 if (grid.length === 0) {
1028                     return 0;
1029                 }
1030                 return grid.reduce(function (last, row) {
1031                     return last > row.rowText.length ? last : row.rowText.length;
1032                 }, 0);
1033             }
1034         };
1035 
1036         templateBuilder.fn = templateBuilder.prototype;
1037         templateBuilder.prototype.init.prototype = templateBuilder.prototype;
1038 
1039         global.templateBuilder = templateBuilder;
1040         log.info("load templateBuilder module... [OK]");
1041     })(compiler.fn);
1042 
1043     log.info("compiler module load... [OK]");
1044 
1045 })(window.templateLayout);