PageSourceSearch

https://erp.hikaruline.com/ecomonline/Design/assets/plugins/jExcel/js/jexcel.js?1

js hikaruline.com collected 2026-10-06 20:40:52 UTC 509,705 bytes, 13,182 lines download raw bytes

1/**
2 * jExcel v3.9.1
3 *
4 * Author: Paul Hodel <[email protected]>
5 * Website: https://bossanova.uk/jexcel/
6 * Description: Create amazing web based spreadsheets.
7 *
8 * This software is distribute under MIT License
9 */
10
11 if (! jSuites && typeof(require) === 'function') {
12    var jSuites = require('jsuites');
13    require('jsuites/dist/jsuites.css');
14}
15
16;(function (global, factory) {
17    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
18    typeof define === 'function' && define.amd ? define(factory) :
19    global.jexcel = factory();
20}(this, (function () {
21
22    'use strict';
23
24    // Jexcel core object
25
26    var jexcel = (function(el, options) {
27        // Create jexcel object
28        var obj = {};
29        obj.options = {};
30
31        if (! (el instanceof Element || el instanceof HTMLDocument)) {
32            console.error('JEXCEL: el is not a valid DOM element');
33            return false;
34        }
35
36        // Loading default configuration
37        var defaults = {
38            // External data
39            url:null,
40            // Data
41            data:null,
42            // Copy behavior
43            copyCompatibility:false,
44            // Rows and columns definitions
45            rows:[],
46            columns:[],
47            // Deprected legacy options
48            colHeaders:[],
49            colWidths:[],
50            colAlignments:[],
51            nestedHeaders:null,
52            // Column width that is used by default
53            defaultColWidth:50,
54            defaultColAlign:'center',
55            // Spare rows and columns
56            minSpareRows:0,
57            minSpareCols:0,
58            // Minimal table dimensions
59            minDimensions:[0,0],
60            // Allow Export
61            allowExport:true,
62            // @type {boolean} - Include the header titles on download
63            includeHeadersOnDownload:false,
64            // Allow column sorting
65            columnSorting:true,
66            // Allow column dragging
67            columnDrag:false,
68            // Allow column resizing
69            columnResize:true,
70            // Allow row resizing
71            rowResize:false,
72            // Allow row dragging
73            rowDrag:true,
74            // Allow table edition
75            editable:true,
76            // Allow new rows
77            allowInsertRow:true,
78            // Allow new rows
79            allowManualInsertRow:true,
80            // Allow new columns
81            allowInsertColumn:true,
82            // Allow new rows
83            allowManualInsertColumn:true,
84            // Allow row delete
85            allowDeleteRow:true,
86            // Allow deleting of all rows
87            allowDeletingAllRows:false,
88            // Allow column delete
89            allowDeleteColumn:true,
90            // Allow rename column
91            allowRenameColumn:true,
92            // Allow comments
93            allowComments:false,
94            // Global wrap
95            wordWrap:false,
96            // Image options
97            imageOptions: null,
98            // CSV source
99            csv:null,
100            // Filename
101            csvFileName:'jexcel',
102            // Consider first line as header
103            csvHeaders:true,
104            // Delimiters
105            csvDelimiter:',',
106            // Disable corner selection
107            selectionCopy:true,
108            // Merged cells
109            mergeCells:{},
110            // Create toolbar
111            toolbar:null,
112            // Allow search
113            search:false,
114            // Create pagination
115            pagination:false,
116            paginationOptions:null,
117            // Full screen
118            fullscreen:false,
119            // Lazy loading
120            lazyLoading:false,
121            loadingSpin:false,
122            // Table overflow
123            tableOverflow:false,
124            tableHeight:'300px',
125            tableWidth:null,
126            // Meta
127            meta: null,
128            // Style
129            style:null,
130            // Execute formulas
131            parseFormulas:true,
132            autoIncrement:true,
133            autoCasting:true,
134            // Event handles
135            onundo:null,
136            onredo:null,
137            onload:null,
138            onchange:null,
139            onbeforechange:null,
140            onafterchanges:null,
141            onbeforeinsertrow: null,
142            oninsertrow:null,
143            onbeforeinsertcolumn: null,
144            oninsertcolumn:null,
145            onbeforedeleterow:null,
146            ondeleterow:null,
vendor: 119,504 bytes, lines 147-3210
147            onbeforedeletecolumn:null,
148            ondeletecolumn:null,
149            onmoverow:null,
150            onmovecolumn:null,
151            onresizerow:null,
152            onresizecolumn:null,
153            onsort:null,
154            onselection:null,
155            onpaste:null,
156            onbeforepaste:null,
157            onmerge:null,
158            onfocus:null,
159            onblur:null,
160            onchangeheader:null,
161            oneditionstart:null,
162            oneditionend:null,
163            onchangestyle:null,
164            onchangemeta:null,
165            onchangepage:null,
166            // Customize any cell behavior
167            updateTable:null,
168            // Detach the HTML table when calling updateTable
169            detachForUpdates: false,
170            // Texts
171            text:{
172                noRecordsFound: 'No records found',
173                showingPage: 'Showing page {0} of {1} entries',
174                show: 'Show ',
175                search: 'Search',
176                entries: ' entries',
177                columnName: 'Column name',
178                insertANewColumnBefore: 'Insert a new column before',
179                insertANewColumnAfter: 'Insert a new column after',
180                deleteSelectedColumns: 'Delete selected columns',
181                renameThisColumn: 'Rename this column',
182                orderAscending: 'Order ascending',
183                orderDescending: 'Order descending',
184                insertANewRowBefore: 'Insert a new row before',
185                insertANewRowAfter: 'Insert a new row after',
186                deleteSelectedRows: 'Delete selected rows',
187                editComments: 'Edit comments',
188                addComments: 'Add comments',
189                comments: 'Comments',
190                clearComments: 'Clear comments',
191                copy: 'Copy...',
192                paste: 'Paste...',
193                saveAs: 'Save as...',
194                about: 'About',
195                areYouSureToDeleteTheSelectedRows: 'Are you sure to delete the selected rows?',
196                areYouSureToDeleteTheSelectedColumns: 'Are you sure to delete the selected columns?',
197                thisActionWillDestroyAnyExistingMergedCellsAreYouSure: 'This action will destroy any existing merged cells. Are you sure?',
198                thisActionWillClearYourSearchResultsAreYouSure: 'This action will clear your search results. Are you sure?',
199                thereIsAConflictWithAnotherMergedCell: 'There is a conflict with another merged cell',
200                invalidMergeProperties: 'Invalid merged properties',
201                cellAlreadyMerged: 'Cell already merged',
202                noCellsSelected: 'No cells selected',
203            },
204            // About message
205            about:"jExcel CE Spreadsheet\nVersion 3.9.1\nAuthor: Paul Hodel <[email protected]>\nWebsite: https://bossanova.uk/jexcel/v3",
206        };
207    
208        // Loading initial configuration from user
209        for (var property in defaults) {
210            if (options && options.hasOwnProperty(property)) {
211                if (property === 'text') {
212                    obj.options[property] = defaults[property];
213                    for (var textKey in options[property]) {
214                        if (options[property].hasOwnProperty(textKey)){
215                            obj.options[property][textKey] = options[property][textKey];
216                        }
217                    }
218                } else {
219                    obj.options[property] = options[property];
220                }
221            } else {
222                obj.options[property] = defaults[property];
223            }
224        }
225
226        // Global elements
227        obj.el = el;
228        obj.corner = null;
229        obj.contextMenu = null;
230        obj.textarea = null;
231        obj.ads = null;
232        obj.content = null;
233        obj.table = null;
234        obj.thead = null;
235        obj.tbody = null;
236        obj.rows = [];
237        obj.results = null;
238        obj.searchInput = null;
239        obj.toolbar = null;
240        obj.pagination = null;
241        obj.pageNumber = null;
242        obj.headerContainer = null;
243        obj.colgroupContainer = null;
244    
245        // Containers
246        obj.headers = [];
247        obj.records = [];
248        obj.history = [];
249        obj.formula = [];
250        obj.colgroup = [];
251        obj.selection = [];
252        obj.highlighted  = [];
253        obj.selectedCell = null;
254        obj.selectedContainer = null;
255        obj.style = [];
256        obj.data = null;
257    
258        // Internal controllers
259        obj.cursor = null;
260        obj.historyIndex = -1;
261        obj.ignoreEvents = false;
262        obj.ignoreHistory = false;
263        obj.edition = null;
264        obj.hashString = null;
265        obj.resizing = null;
266        obj.dragging = null;
267    
268        // Lazy loading
269        if (obj.options.lazyLoading == true && (obj.options.tableOverflow == false && obj.options.fullscreen == false)) {
270            console.error('JEXCEL: The lazyloading only works when tableOverflow = yes or fullscreen = yes');
271            obj.options.lazyLoading = false;
272        }
273        
274        /**
275         * Activate/Disable fullscreen 
276         * use programmatically : table.fullscreen(); or table.fullscreen(true); or table.fullscreen(false);
277         * @Param {boolean} activate
278         */
279        obj.fullscreen = function(activate) {
280            // If activate not defined, get reverse options.fullscreen
281            if (activate == null) {
282                activate = ! obj.options.fullscreen;
283            }
284    
285            // If change
286            if (obj.options.fullscreen != activate) {
287                obj.options.fullscreen = activate;
288    
289                // Test LazyLoading conflict
290                if (activate == true) {
291                    el.classList.add('fullscreen');
292                } else {
293                    el.classList.remove('fullscreen');
294                }
295            } 
296        }
297    
298        /**
299         * Prepare the jexcel table
300         * 
301         * @Param config
302         */
303        obj.prepareTable = function() {
304            // Loading initial data from remote sources
305            var results = [];
306    
307            // Number of columns
308            var size = obj.options.columns.length;
309    
310            if (obj.options.data && typeof(obj.options.data[0]) !== 'undefined') {
311                // Data keys
312                var keys = Object.keys(obj.options.data[0]);
313    
314                if (keys.length > size) {
315                    size = keys.length;
316                }
317            }
318    
319            // Minimal dimensions
320            if (obj.options.minDimensions[0] > size) {
321                size = obj.options.minDimensions[0];
322            }
323    
324            // Requests
325            var multiple = [];
326    
327            // Preparations
328            for (var i = 0; i < size; i++) {
329                // Deprected options. You should use only columns
330                if (! obj.options.colHeaders[i]) {
331                    obj.options.colHeaders[i] = '';
332                }
333                if (! obj.options.colWidths[i]) {
334                    obj.options.colWidths[i] = obj.options.defaultColWidth;
335                }
336                if (! obj.options.colAlignments[i]) {
337                    obj.options.colAlignments[i] = obj.options.defaultColAlign;
338                }
339    
340                // Default column description
341                if (! obj.options.columns[i]) {
342                    obj.options.columns[i] = { type:'text' };
343                } else if (! obj.options.columns[i].type) {
344                    obj.options.columns[i].type = 'text';
345                }
346                if (! obj.options.columns[i].name) {
347                    obj.options.columns[i].name = keys && keys[i] ? keys[i] : i;
348                }
349                if (! obj.options.columns[i].source) {
350                    obj.options.columns[i].source = [];
351                }
352                if (! obj.options.columns[i].options) {
353                    obj.options.columns[i].options = [];
354                }
355                if (! obj.options.columns[i].editor) {
356                    obj.options.columns[i].editor = null;
357                }
358                if (! obj.options.columns[i].allowEmpty) {
359                    obj.options.columns[i].allowEmpty = false;
360                }
361                if (! obj.options.columns[i].title) {
362                    obj.options.columns[i].title = obj.options.colHeaders[i] ? obj.options.colHeaders[i] : '';
363                }
364                if (! obj.options.columns[i].width) {
365                    obj.options.columns[i].width = obj.options.colWidths[i] ? obj.options.colWidths[i] : obj.options.defaultColWidth;
366                }
367                if (! obj.options.columns[i].align) {
368                    obj.options.columns[i].align = obj.options.colAlignments[i] ? obj.options.colAlignments[i] : 'center';
369                }
370    
371                // Pre-load initial source for json autocomplete
372                if (obj.options.columns[i].type == 'autocomplete' || obj.options.columns[i].type == 'dropdown') {
373                    // if remote content
374                    if (obj.options.columns[i].url) {
375                        multiple.push(jSuites.ajax({
376                            url: obj.options.columns[i].url,
377                            index: i,
378                            method: 'GET',
379                            dataType: 'json',
380                            multiple: multiple,
381                            success: function(data) {
382                                var source = [];
383                                for (var i = 0; i < data.length; i++) {
384                                    obj.options.columns[this.index].source.push(data[i]);
385                                }
386                            },
387                            complete: function() {
388                                obj.createTable();
389                            }
390                        }));
391                    }
392                } else if (obj.options.columns[i].type == 'calendar') {
393                    // Default format for date columns
394                    if (! obj.options.columns[i].options.format) {
395                        obj.options.columns[i].options.format = 'DD/MM/YYYY';
396                    }
397                }
398            }
399    
400            // On complete
401            if (! multiple.length) {
402                obj.createTable();
403            }
404        }
405    
406        obj.createTable = function() {
407            // Elements
408            obj.table = document.createElement('table');
409            obj.thead = document.createElement('thead');
410            obj.tbody = document.createElement('tbody');
411    
412            // Create headers controllers
413            obj.headers = [];
414            obj.colgroup = [];
415    
416            // Create table container
417            obj.content = document.createElement('div');
418            obj.content.classList.add('jexcel_content');
419    
420            // Create toolbar object
421            obj.toolbar = document.createElement('div');
422            obj.toolbar.classList.add('jexcel_toolbar');
423    
424            // Search
425            var searchContainer = document.createElement('div');
426            var searchText = document.createTextNode((obj.options.text.search) + ': ');
427            obj.searchInput = document.createElement('input');
428            obj.searchInput.classList.add('jexcel_search');
429            searchContainer.appendChild(searchText);
430            searchContainer.appendChild(obj.searchInput);
431            obj.searchInput.onfocus = function() {
432                obj.resetSelection();
433            }
434    
435            // Pagination select option
436            var paginationUpdateContainer = document.createElement('div');
437    
438            if (obj.options.pagination > 0 && obj.options.paginationOptions && obj.options.paginationOptions.length > 0) {
439                obj.paginationDropdown = document.createElement('select');
440                obj.paginationDropdown.classList.add('jexcel_pagination_dropdown');
441                obj.paginationDropdown.onchange = function() {
442                    obj.options.pagination = parseInt(this.value);
443                    obj.page(0);
444                }
445    
446                for (var i = 0; i < obj.options.paginationOptions.length; i++) {
447                    var temp = document.createElement('option');
448                    temp.value = obj.options.paginationOptions[i];
449                    temp.innerHTML = obj.options.paginationOptions[i];
450                    obj.paginationDropdown.appendChild(temp);
451                }
452    
453                paginationUpdateContainer.appendChild(document.createTextNode(obj.options.text.show));
454                paginationUpdateContainer.appendChild(obj.paginationDropdown);
455                paginationUpdateContainer.appendChild(document.createTextNode(obj.options.text.entries));
456            }
457    
458            // Filter and pagination container
459            obj.filter = document.createElement('div');
460            obj.filter.classList.add('jexcel_filter');
461            obj.filter.appendChild(paginationUpdateContainer);
462            obj.filter.appendChild(searchContainer);
463    
464            // Colsgroup
465            obj.colgroupContainer = document.createElement('colgroup');
466            var tempCol = document.createElement('col');
467            tempCol.setAttribute('width', '50');
468            obj.colgroupContainer.appendChild(tempCol);
469    
470            // Nested
471            if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
472                // Flexible way to handle nestedheaders
473                if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) {
474                    for (var j = 0; j < obj.options.nestedHeaders.length; j++) {
475                        obj.thead.appendChild(obj.createNestedHeader(obj.options.nestedHeaders[j]));
476                    }
477                } else {
478                    obj.thead.appendChild(obj.createNestedHeader(obj.options.nestedHeaders));
479                }
480            }
481    
482            // Row
483            obj.headerContainer = document.createElement('tr');
484            var tempCol = document.createElement('td');
485            tempCol.classList.add('jexcel_selectall');
486            obj.headerContainer.appendChild(tempCol);
487    
488            for (var i = 0; i < obj.options.columns.length; i++) {
489                // Create header
490                obj.createCellHeader(i);
491                // Append cell to the container
492                obj.headerContainer.appendChild(obj.headers[i]);
493                obj.colgroupContainer.appendChild(obj.colgroup[i]);
494            }
495    
496            obj.thead.appendChild(obj.headerContainer);
497    
498            // Content table
499            obj.table = document.createElement('table');
500            obj.table.classList.add('jexcel');
501            obj.table.setAttribute('cellpadding', '0');
502            obj.table.setAttribute('cellspacing', '0');
503            obj.table.setAttribute('unselectable', 'yes');
504            obj.table.setAttribute('onselectstart', 'return false');
505            obj.table.appendChild(obj.colgroupContainer);
506            obj.table.appendChild(obj.thead);
507            obj.table.appendChild(obj.tbody);
508    
509            // Spreadsheet corner
510            obj.corner = document.createElement('div');
511            obj.corner.className = 'jexcel_corner';
512            obj.corner.setAttribute('unselectable', 'on');
513            obj.corner.setAttribute('onselectstart', 'return false');
514    
515            if (obj.options.selectionCopy == false) {
516                obj.corner.style.display = 'none';
517            }
518    
519            // Textarea helper
520            obj.textarea = document.createElement('textarea');
521            obj.textarea.className = 'jexcel_textarea';
522            obj.textarea.id = 'jexcel_textarea';
523            obj.textarea.tabIndex = '-1';
524
525            // Contextmenu container
526            obj.contextMenu = document.createElement('div');
527            obj.contextMenu.className = 'jexcel_contextmenu';
528    
529            // Create element
530            jSuites.contextmenu(obj.contextMenu, {
531                onclick:function() {
532                    obj.contextMenu.contextmenu.close(false);
533                }
534            });
535    
536            // Powered by jExcel
537            var ads = document.createElement('a');
538            ads.setAttribute('href', 'https://bossanova.uk/jexcel/');
539            obj.ads = document.createElement('div');
540            obj.ads.className = 'jexcel_about';
541            if (typeof(sessionStorage) !== "undefined" && ! sessionStorage.getItem('jexcel')) {
542                sessionStorage.setItem('jexcel', true);
543                var img = document.createElement('img');
544                img.src = '//bossanova.uk/jexcel/logo.png';
545                ads.appendChild(img);
546            }
547            var span = document.createElement('span');
548            span.innerHTML = 'Jexcel spreadsheet';
549            ads.appendChild(span);
550            obj.ads.appendChild(ads);
551
552            // Create table container TODO: frozen columns
553            var container = document.createElement('div');
554            container.classList.add('jexcel_table');
555    
556            // Pagination
557            obj.pagination = document.createElement('div');
558            obj.pagination.classList.add('jexcel_pagination');
559            var paginationInfo = document.createElement('div');
560            var paginationPages = document.createElement('div');
561            obj.pagination.appendChild(paginationInfo);
562            obj.pagination.appendChild(paginationPages);
563
564            // Hide pagination if not in use
565            if (! obj.options.pagination) {
566                obj.pagination.style.display = 'none';
567            }
568
569            // Append containers to the table
570            if (obj.options.search == true) {
571                el.appendChild(obj.filter);
572            }
573    
574            // Elements
575            obj.content.appendChild(obj.table);
576            obj.content.appendChild(obj.corner);
577            obj.content.appendChild(obj.textarea);
578    
579            el.appendChild(obj.toolbar);
580            el.appendChild(obj.content);
581            el.appendChild(obj.pagination);
582            el.appendChild(obj.contextMenu);
583            el.appendChild(obj.ads);
584            el.classList.add('jexcel_container');
585    
586            // Create toolbar
587            if (obj.options.toolbar && obj.options.toolbar.length) {
588                obj.createToolbar();
589            }
590    
591            // Fullscreen
592            if (obj.options.fullscreen == true) {
593                el.classList.add('fullscreen');
594            } else {
595                // Overflow
596                if (obj.options.tableOverflow == true) {
597                    if (obj.options.tableHeight) {
598                        obj.content.style['overflow-y'] = 'auto';
599                        obj.content.style.maxHeight = obj.options.tableHeight;
600                    }
601                    if (obj.options.tableWidth) {
602                        obj.content.style['overflow-x'] = 'auto';
603                        obj.content.style.width = obj.options.tableWidth;
604                    }
605                }
606            }
607    
608            // With toolbars
609            if (obj.options.tableOverflow != true && obj.options.toolbar) {
610                el.classList.add('with-toolbar');
611            }
612    
613            // Actions
614            if (obj.options.columnDrag == true) {
615                obj.thead.classList.add('draggable');
616            }
617            if (obj.options.columnResize == true) {
618                obj.thead.classList.add('resizable');
619            }
620            if (obj.options.rowDrag == true) {
621                obj.tbody.classList.add('draggable');
622            }
623            if (obj.options.rowResize == true) {
624                obj.tbody.classList.add('resizable');
625            }
626    
627            // Load data
628            obj.setData();
629    
630            // Style
631            if (obj.options.style) {
632                obj.setStyle(obj.options.style, null, null, 1, 1);
633            }
634        }
635
636        /**
637         * Refresh the data
638         * 
639         * @return void
640         */
641        obj.refresh = function() {
642            if (obj.options.url) {
643                // Loading
644                if (obj.options.loadingSpin == true) {
645                    jSuites.loading.show();
646                }
647    
648                jSuites.ajax({
649                    url: obj.options.url,
650                    method: 'GET',
651                    dataType: 'json',
652                    success: function(result) {
653                        // Data
654                        obj.options.data = (result.data) ? result.data : result;
655                        // Prepare table
656                        obj.setData();
657                        // Hide spin
658                        if (obj.options.loadingSpin == true) {
659                            jSuites.loading.hide();
660                        }
661                    }
662                });
663            } else {
664                obj.setData();
665            }
666        }
667
668        /**
669         * Set data
670         * 
671         * @param array data In case no data is sent, default is reloaded
672         * @return void
673         */
674        obj.setData = function(data) {
675            // Update data
676            if (data) {
677                if (typeof(data) == 'string') {
678                    data = JSON.parse(data);
679                }
680    
681                obj.options.data = data;
682            }
683    
684            // Data
685            if (! obj.options.data) {
686                obj.options.data = [];
687            }
688    
689            // Prepare data
690            if (obj.options.data) {
691                var data = [];
692                for (var j = 0; j < obj.options.data.length; j++) {
693                    var row = [];
694                    for (var i = 0; i < obj.options.columns.length; i++) {
695                        row[i] = obj.options.data[j][obj.options.columns[i].name];
696                    }
697                    data.push(row);
698                }
699    
700                obj.options.data = data;
701            }
702    
703            // Adjust minimal dimensions
704            var j = 0;
705            var i = 0;
706            var size_i = obj.options.columns.length;
707            var size_j = obj.options.data.length;
708            var min_i = obj.options.minDimensions[0];
709            var min_j = obj.options.minDimensions[1];
710            var max_i = min_i > size_i ? min_i : size_i;
711            var max_j = min_j > size_j ? min_j : size_j;
712    
713            for (j = 0; j < max_j; j++) {
714                for (i = 0; i < max_i; i++) {
715                    if (obj.options.data[j] == undefined) {
716                        obj.options.data[j] = [];
717                    }
718    
719                    if (obj.options.data[j][i] == undefined) {
720                        obj.options.data[j][i] = '';
721                    }
722                }
723            }
724    
725            // Reset containers
726            obj.rows = [];
727            obj.results = null;
728            obj.records = [];
729            obj.history = [];
730    
731            // Reset internal controllers
732            obj.historyIndex = -1;
733    
734            // Reset data
735            obj.tbody.innerHTML = '';
736    
737            // Lazy loading
738            if (obj.options.lazyLoading == true) {
739                // Load only 100 records
740                var startNumber = 0
741                var finalNumber = obj.options.data.length < 100 ? obj.options.data.length : 100;
742    
743                if (obj.options.pagination) {
744                    obj.options.pagination = false;
745                    console.error('JEXCEL: Pagination will be disable due the lazyLoading');
746                }
747            } else if (obj.options.pagination) {
748                // Pagination
749                if (! obj.pageNumber) {
750                    obj.pageNumber = 0;
751                }
752                var quantityPerPage = obj.options.pagination;
753                startNumber = (obj.options.pagination * obj.pageNumber);
754                finalNumber = (obj.options.pagination * obj.pageNumber) + obj.options.pagination;
755    
756                if (obj.options.data.length < finalNumber) {
757                    finalNumber = obj.options.data.length;
758                }
759            } else {
760                var startNumber = 0;
761                var finalNumber = obj.options.data.length;
762            }
763    
764            // Append nodes to the HTML
765            for (j = 0; j < obj.options.data.length; j++) {
766                // Create row
767                var tr = obj.createRow(j, obj.options.data[j]);
768                // Append line to the table
769                if (j >= startNumber && j < finalNumber) {
770                    obj.tbody.appendChild(tr);
771                }
772            }
773    
774            if (obj.options.lazyLoading == true) {
775                // Do not create pagination with lazyloading activated
776            } else if (obj.options.pagination) {
777                obj.updatePagination();
778            }
779    
780            // Merge cells
781            if (obj.options.mergeCells) {
782                var keys = Object.keys(obj.options.mergeCells);
783                for (var i = 0; i < keys.length; i++) {
784                    var num = obj.options.mergeCells[keys[i]];
785                    obj.setMerge(keys[i], num[0], num[1], 1);
786                }
787            }
788    
789            // Updata table with custom configurations if applicable
790            obj.updateTable();
791    
792           // Onload
793            if (! obj.ignoreEvents) {
794                if (typeof(obj.options.onload) == 'function') {
795                    obj.options.onload(el, obj);
796                }
797            }
798        }
799    
800        /**
801         * Get the whole table data
802         * 
803         * @param bool get highlighted cells only
804         * @return array data
805         */
806        obj.getData = function(highlighted, dataOnly) {
807            // Control vars
808            var dataset = [];
809            var px = 0;
810            var py = 0;
811
812            // Data type
813            var dataType = dataOnly == true || obj.options.copyCompatibility == false ? true : false;
814
815            // Column and row length
816            var x = obj.options.columns.length
817            var y = obj.options.data.length
818    
819            // Go through the columns to get the data
820            for (var j = 0; j < y; j++) {
821                px = 0;
822                for (var i = 0; i < x; i++) {
823                    // Cell selected or fullset
824                    if (! highlighted || obj.records[j][i].classList.contains('highlight')) {
825                        // Get value
826                        if (! dataset[py]) {
827                            dataset[py] = [];
828                        }
829                        if (! dataType) {
830                            dataset[py][px] = obj.records[j][i].innerHTML;
831                        } else {
832                            dataset[py][px] = obj.options.data[j][i];
833                        }
834                        px++;
835                    }
836                }
837                if (px > 0) {
838                    py++;
839                }
840           }
841    
842           return dataset;
843        }
844    
845        /**
846         * Get the whole table data
847         * 
848         * @param integer row number
849         * @return string value
850         */
851        obj.getJson = function(highlighted) {
852            // Control vars
853            var data = [];
854    
855            // Column and row length
856            var x = obj.options.columns.length
857            var y = obj.options.data.length
858    
859            // Go through the columns to get the data
860            for (var j = 0; j < y; j++) {
861                var row = null;
862                for (var i = 0; i < x; i++) {
863                    if (! highlighted || obj.records[j][i].classList.contains('highlight')) {
864                        if (row == null) {
865                            row = {};
866                        }
867                        row[obj.options.columns[i].name] = obj.options.data[j][i];
868                    }
869                }
870    
871                if (row != null) {
872                    data.push(row);
873                }
874           }
875    
876           return data;
877        }
878    
879        /**
880         * Get a row data by rowNumber
881         */
882        obj.getRowData = function(rowNumber) {
883            return obj.options.data[rowNumber];
884        }
885    
886        /**
887         * Set a row data by rowNumber
888         */
889        obj.setRowData = function(rowNumber, data) {
890            for (var i = 0; i < obj.headers.length; i++) {
891                // Update cell
892                var columnName = jexcel.getColumnNameFromId([ i, rowNumber ]);
893                // Set value
894                if (data[i] != null) {
895                    obj.setValue(columnName, data[i]);
896                }
897            }
898        }
899    
900        /**
901         * Get a column data by columnNumber
902         */
903        obj.getColumnData = function(columnNumber) {
904            var dataset = [];
905            // Go through the rows to get the data
906            for (var j = 0; j < obj.options.data.length; j++) {
907                dataset.push(obj.options.data[j][columnNumber]);
908            }
909            return dataset;
910        }
911    
912        /**
913         * Set a column data by colNumber
914         */
915        obj.setColumnData = function(colNumber, data) {
916            for (var j = 0; j < obj.rows.length; j++) {
917                // Update cell
918                var columnName = jexcel.getColumnNameFromId([ colNumber, j ]);
919                // Set value
920                if (data[j] != null) {
921                    obj.setValue(columnName, data[j]);
922                }
923            }
924        }
925    
926        /**
927         * Create row
928         */
929        obj.createRow = function(j, data) {
930            // Create container
931            if (! obj.records[j]) {
932                obj.records[j] = [];
933            }
934            // Default data
935            if (! data) {
936                var data = obj.options.data[j];
937            }
938            // New line of data to be append in the table
939            obj.rows[j] = document.createElement('tr');
940            obj.rows[j].setAttribute('data-y', j);
941            // Definitions
942            if (obj.options.rows[j]) {
943                if (obj.options.rows[j].height) {
944                    obj.rows[j].style.height = obj.options.rows[j].height;
945                }
946            }
947            // Row number label
948            var td = document.createElement('td');
949            td.innerHTML = parseInt(j + 1);
950            td.setAttribute('data-y', j);
951            td.className = 'jexcel_row';
952            obj.rows[j].appendChild(td);
953    
954            // Data columns
955            for (i = 0; i < obj.options.columns.length; i++) {
956                // New column of data to be append in the line
957                obj.records[j][i] = obj.createCell(i, j, data[i]);
958                // Add column to the row
959                obj.rows[j].appendChild(obj.records[j][i]);
960            }
961    
962            // Add row to the table body
963            return obj.rows[j];
964        }
965    
966        /**
967         * Create cell
968         */
969        obj.createCell = function(i, j, value) {
970            // Create cell and properties
971            var td = document.createElement('td');
972            td.setAttribute('data-x', i);
973            td.setAttribute('data-y', j);
974            // Hidden column
975            if (obj.options.columns[i].type == 'hidden') {
976                td.style.display = 'none';
977                td.innerHTML = value;
978            } else if (obj.options.columns[i].type == 'checkbox' || obj.options.columns[i].type == 'radio') {
979                // Create input
980                var element = document.createElement('input');
981                element.type = obj.options.columns[i].type;
982                element.name = 'c' + i;
983                element.checked = (value == 1 || value == true || value == 'true') ? true : false;
984                element.onclick = function() {
985                    obj.setValue(td, this.checked);
986                }
987    
988                if (obj.options.columns[i].readOnly == true || obj.options.editable == false) {
989                    element.setAttribute('disabled', 'disabled');
990                }
991    
992                // Append to the table
993                td.appendChild(element);
994                // Make sure the values are correct
995                obj.options.data[j][i] = element.checked;
996            } else if (obj.options.columns[i].type == 'calendar') {
997                // Try formatted date
998                var formatted = jSuites.calendar.extractDateFromString(value, obj.options.columns[i].options.format);
999                // Create calendar cell
1000                td.innerHTML = jSuites.calendar.getDateString(formatted ? formatted : value, obj.options.columns[i].options.format);
1001            } else if (obj.options.columns[i].type == 'dropdown' || obj.options.columns[i].type == 'autocomplete') {
1002                // Create dropdown cell
1003                td.classList.add('jexcel_dropdown');
1004                td.innerHTML = obj.getDropDownValue(i, value);
1005            } else if (obj.options.columns[i].type == 'color') {
1006                if (obj.options.columns[i].render == 'square') {
1007                    var color = document.createElement('div');
1008                    color.className = 'color';
1009                    color.style.backgroundColor = value;
1010                    td.appendChild(color);
1011                } else {
1012                    td.style.color = value;
1013                    td.innerHTML = value;
1014                }
1015            } else if (obj.options.columns[i].type == 'image') {
1016                if (value && value.substr(0, 10) == 'data:image') {
1017                    var img = document.createElement('img');
1018                    img.src = value;
1019                    td.appendChild(img);
1020                }
1021            } else {
1022                if ((''+value).substr(0,1) == '=' && obj.options.parseFormulas == true) {
1023                    value = obj.executeFormula(value, i, j)
1024                }
1025                if (obj.options.columns[i].mask) {
1026                    var decimal = obj.options.columns[i].decimal || '.';
1027                    value = '' + jSuites.mask.run(value, obj.options.columns[i].mask, decimal);
1028                }
1029    
1030                td.innerHTML = value;
1031            }
1032    
1033            // Readonly
1034            if (obj.options.columns[i].readOnly == true) {
1035                td.className = 'readonly';
1036            }
1037    
1038            // Text align
1039            var colAlign = obj.options.columns[i].align ? obj.options.columns[i].align : 'center';
1040            td.style.textAlign = colAlign;
1041    
1042            // Wrap option
1043            if (obj.options.columns[i].wordWrap != false && (obj.options.wordWrap == true || obj.options.columns[i].wordWrap == true || td.innerHTML.length > 200)) {
1044                td.style.whiteSpace = 'pre-wrap';
1045            }
1046    
1047            // Overflow
1048            if (i > 0) {
1049                if (value || td.innerHTML) {
1050                    obj.records[j][i-1].style.overflow = 'hidden';
1051                } else {
1052                    if (i == obj.options.columns.length - 1) {
1053                        td.style.overflow = 'hidden';
1054                    }
1055                }
1056            }
1057    
1058            return td;
1059        }
1060    
1061        obj.createCellHeader = function(colNumber) {
1062            // Create col global control
1063            var colWidth = obj.options.columns[colNumber].width ? obj.options.columns[colNumber].width : obj.options.defaultColWidth;
1064            var colAlign = obj.options.columns[colNumber].align ? obj.options.columns[colNumber].align : obj.options.defaultColAlign;
1065    
1066            // Create header cell
1067            obj.headers[colNumber] = document.createElement('td');
1068            obj.headers[colNumber].innerHTML = obj.options.columns[colNumber].title ? obj.options.columns[colNumber].title : jexcel.getColumnName(colNumber);
1069            obj.headers[colNumber].setAttribute('data-x', colNumber);
1070            obj.headers[colNumber].style.textAlign = colAlign;
1071            if (obj.options.columns[colNumber].title) {
1072                obj.headers[colNumber].setAttribute('title', obj.options.columns[colNumber].title);
1073            }
1074    
1075            // Width control
1076            obj.colgroup[colNumber] = document.createElement('col');
1077            obj.colgroup[colNumber].setAttribute('width', colWidth);
1078    
1079            // Hidden column
1080            if (obj.options.columns[colNumber].type == 'hidden') {
1081                obj.headers[colNumber].style.display = 'none';
1082                obj.colgroup[colNumber].style.display = 'none';
1083            }
1084        }
1085    
1086        obj.createNestedHeader = function(nestedInformation) {
1087            var tr = document.createElement('tr');
1088            tr.classList.add('jexcel_nested');
1089            var td = document.createElement('td');
1090            tr.appendChild(td);
1091    
1092            var headerIndex = 0;
1093            for (var i = 0; i < nestedInformation.length; i++) {
1094                // Default values
1095                if (! nestedInformation[i].colspan) {
1096                    nestedInformation[i].colspan = 1;
1097                }
1098                if (! nestedInformation[i].align) {
1099                    nestedInformation[i].align = 'center';
1100                }
1101                if (! nestedInformation[i].title) {
1102                    nestedInformation[i].title = '';
1103                }
1104    
1105                // Number of columns
1106                var numberOfColumns = nestedInformation[i].colspan;
1107    
1108                // Classes container
1109                var column = [];
1110                // Header classes for this cell
1111                for (var x = 0; x < numberOfColumns; x++) {
1112                    if (obj.options.columns[headerIndex] && obj.options.columns[headerIndex].type == 'hidden') {
1113                        numberOfColumns++;
1114                    }
1115                    column.push(headerIndex);
1116                    headerIndex++;
1117                }
1118    
1119                // Created the nested cell
1120                var td = document.createElement('td');
1121                td.setAttribute('data-column', column.join(','));
1122                td.setAttribute('colspan', nestedInformation[i].colspan);
1123                td.setAttribute('align', nestedInformation[i].align);
1124                td.innerHTML = nestedInformation[i].title;
1125                tr.appendChild(td);
1126            }
1127    
1128            return tr;
1129        }
1130    
1131        /**
1132         * Create toolbar
1133         */
1134        obj.createToolbar = function(toolbar) {
1135            if (toolbar) {
1136                obj.options.toolbar = toolbar;
1137            } else {
1138                var toolbar = obj.options.toolbar;
1139            }
1140    
1141            for (var i = 0; i < toolbar.length; i++) {
1142                if (toolbar[i].type == 'i') {
1143                    var toolbarItem = document.createElement('i');
1144                    toolbarItem.classList.add('jexcel_toolbar_item');
1145                    toolbarItem.classList.add('material-icons');
1146                    toolbarItem.setAttribute('data-k', toolbar[i].k);
1147                    toolbarItem.setAttribute('data-v', toolbar[i].v);
1148                    // Tooltip
1149                    if (toolbar[i].tooltip) {
1150                        toolbarItem.setAttribute('title', toolbar[i].tooltip);
1151                    }
1152                    // Handle click
1153                    if (toolbar[i].onclick && typeof(toolbar[i].onclick)) {
1154                        toolbarItem.onclick = toolbar[i].onclick;
1155                    } else {
1156                        toolbarItem.onclick = function() {
1157                            var k = this.getAttribute('data-k');
1158                            var v = this.getAttribute('data-v');
1159                            obj.setStyle(obj.highlighted, k, v);
1160                        }
1161                    }
1162                    // Append element
1163                    toolbarItem.innerHTML = toolbar[i].content;
1164                    obj.toolbar.appendChild(toolbarItem);
1165                } else if (toolbar[i].type == 'select') {
1166                   var toolbarItem = document.createElement('select');
1167                   toolbarItem.classList.add('jexcel_toolbar_item');
1168                   toolbarItem.setAttribute('data-k', toolbar[i].k);
1169                   // Tooltip
1170                   if (toolbar[i].tooltip) {
1171                       toolbarItem.setAttribute('title', toolbar[i].tooltip);
1172                   }
1173                   // Handle onchange
1174                   if (toolbar[i].onchange && typeof(toolbar[i].onchange)) {
1175                       toolbarItem.onchange = toolbar[i].onchange;
1176                   } else {
1177                       toolbarItem.onchange = function() {
1178                           var k = this.getAttribute('data-k');
1179                           obj.setStyle(obj.highlighted, k, this.value);
1180                       }
1181                   }
1182                   // Add options to the dropdown
1183                   for(var j = 0; j < toolbar[i].v.length; j++) {
1184                        var toolbarDropdownOption = document.createElement('option');
1185                        toolbarDropdownOption.value = toolbar[i].v[j];
1186                        toolbarDropdownOption.innerHTML = toolbar[i].v[j];
1187                        toolbarItem.appendChild(toolbarDropdownOption);
1188                   }
1189                   obj.toolbar.appendChild(toolbarItem);
1190                } else if (toolbar[i].type == 'color') {
1191                     var toolbarItem = document.createElement('i');
1192                     toolbarItem.classList.add('jexcel_toolbar_item');
1193                     toolbarItem.classList.add('material-icons');
1194                     toolbarItem.setAttribute('data-k', toolbar[i].k);
1195                     toolbarItem.setAttribute('data-v', '');
1196                     // Tooltip
1197                     if (toolbar[i].tooltip) {
1198                         toolbarItem.setAttribute('title', toolbar[i].tooltip);
1199                     }
1200                     obj.toolbar.appendChild(toolbarItem);
1201                     toolbarItem.onclick = function() {
1202                         this.color.open();
1203                     }
1204                     toolbarItem.innerHTML = toolbar[i].content;
1205                     jSuites.color(toolbarItem, {
1206                         onchange:function(o, v) {
1207                             var k = o.getAttribute('data-k');
1208                             obj.setStyle(obj.highlighted, k, v);
1209                         }
1210                     });
1211                }
1212            }
1213        }
1214    
1215        /**
1216         * Merge cells
1217         * @param cellName
1218         * @param colspan
1219         * @param rowspan
1220         * @param ignoreHistoryAndEvents
1221         */
1222        obj.setMerge = function(cellName, colspan, rowspan, ignoreHistoryAndEvents) {
1223            var test = false;
1224    
1225            if (! cellName) {
1226                if (! obj.highlighted.length) {
1227                    alert(obj.options.text.noCellsSelected);
1228                    return null;
1229                } else {
1230                    var x1 = parseInt(obj.highlighted[0].getAttribute('data-x'));
1231                    var y1 = parseInt(obj.highlighted[0].getAttribute('data-y'));
1232                    var x2 = parseInt(obj.highlighted[obj.highlighted.length-1].getAttribute('data-x'));
1233                    var y2 = parseInt(obj.highlighted[obj.highlighted.length-1].getAttribute('data-y'));
1234                    var cellName = jexcel.getColumnNameFromId([ x1, y1 ]);
1235                    var colspan = (x2 - x1) + 1;
1236                    var rowspan = (y2 - y1) + 1;
1237                }
1238            }
1239    
1240            var cell = jexcel.getIdFromColumnName(cellName, true);
1241    
1242            if (obj.options.mergeCells[cellName]) {
1243                if (obj.records[cell[1]][cell[0]].getAttribute('data-merged')) {
1244                    test = obj.options.text.cellAlreadyMerged;
1245                }
1246            } else if ((! colspan || colspan < 2) && (! rowspan || rowspan < 2)) {
1247                test = obj.options.text.invalidMergeProperties;
1248            } else {
1249                var cells = [];
1250                for (var j = cell[1]; j < cell[1] + rowspan; j++) {
1251                    for (var i = cell[0]; i < cell[0] + colspan; i++) {
1252                        var columnName = jexcel.getColumnNameFromId([i, j]);
1253                        if (obj.records[j][i].getAttribute('data-merged')) {
1254                            test = obj.options.text.thereIsAConflictWithAnotherMergedCell;
1255                        }
1256                    }
1257                }
1258            }
1259    
1260            if (test) {
1261                alert(test);
1262            } else {
1263                // Add property
1264                if (colspan > 1) {
1265                    obj.records[cell[1]][cell[0]].setAttribute('colspan', colspan);
1266                } else {
1267                    colspan = 1;
1268                }
1269                if (rowspan > 1) {
1270                    obj.records[cell[1]][cell[0]].setAttribute('rowspan', rowspan);
1271                } else {
1272                    rowspan = 1;
1273                }
1274                // Keep links to the existing nodes
1275                obj.options.mergeCells[cellName] = [ colspan, rowspan, [] ];
1276                // Mark cell as merged
1277                obj.records[cell[1]][cell[0]].setAttribute('data-merged', 'true');
1278                // Overflow
1279                obj.records[cell[1]][cell[0]].style.overflow = 'hidden';
1280                // History data
1281                var data = [];
1282                // Adjust the nodes
1283                for (var y = cell[1]; y < cell[1] + rowspan; y++) {
1284                    for (var x = cell[0]; x < cell[0] + colspan; x++) {
1285                        if (! (cell[0] == x && cell[1] == y)) {
1286                            data.push(obj.options.data[y][x]);
1287                            obj.updateCell(x, y, '', true);
1288                            obj.options.mergeCells[cellName][2].push(obj.records[y][x]);
1289                            obj.records[y][x].style.display = 'none';
1290                            obj.records[y][x] = obj.records[cell[1]][cell[0]];
1291                        }
1292                    }
1293                }
1294                // In the initialization is not necessary keep the history
1295                obj.updateSelection(obj.records[cell[1]][cell[0]]);
1296    
1297                if (! ignoreHistoryAndEvents) {
1298                    obj.setHistory({
1299                        action:'setMerge',
1300                        column:cellName,
1301                        colspan:colspan,
1302                        rowspan:rowspan,
1303                        data:data,
1304                    });
1305    
1306                    if (typeof(obj.options.onmerge) == 'function') {
1307                        obj.options.onmerge(el, cellName, colspan, rowspan);
1308                    }
1309                }
1310            }
1311        }
1312    
1313        /**
1314         * Merge cells
1315         * @param cellName
1316         * @param colspan
1317         * @param rowspan
1318         * @param ignoreHistoryAndEvents
1319         */
1320        obj.getMerge = function(cellName) {
1321            var data = {};
1322            if (cellName) {
1323                if (obj.options.mergeCells[cellName]) {
1324                    data = [ obj.options.mergeCells[cellName][0], obj.options.mergeCells[cellName][1] ];
1325                } else {
1326                    data = null;
1327                }
1328            } else {
1329                if (obj.options.mergeCells) {
1330                    var mergedCells = obj.options.mergeCells;
1331                    var keys = Object.keys(obj.options.mergeCells);
1332                    for (var i = 0; i < keys.length; i++) {
1333                        data[keys[i]] = [ obj.options.mergeCells[keys[i]][0], obj.options.mergeCells[keys[i]][1] ];
1334                    }
1335                }
1336            }
1337    
1338            return data;
1339        }
1340    
1341        /**
1342         * Remove merge by cellname
1343         * @param cellName
1344         */
1345        obj.removeMerge = function(cellName, data, keepOptions) {
1346            if (obj.options.mergeCells[cellName]) {
1347                var cell = jexcel.getIdFromColumnName(cellName, true);
1348                obj.records[cell[1]][cell[0]].removeAttribute('colspan');
1349                obj.records[cell[1]][cell[0]].removeAttribute('rowspan');
1350                obj.records[cell[1]][cell[0]].removeAttribute('data-merged');
1351                var info = obj.options.mergeCells[cellName];
1352    
1353                var index = 0;
1354                for (var j = 0; j < info[1]; j++) {
1355                    for (var i = 0; i < info[0]; i++) {
1356                        if (j > 0 || i > 0) {
1357                            obj.records[cell[1]+j][cell[0]+i] = info[2][index];
1358                            obj.records[cell[1]+j][cell[0]+i].style.display = '';
1359                            // Recover data
1360                            if (data && data[index]) {
1361                                obj.updateCell(cell[0]+i, cell[1]+j, data[index]);
1362                            }
1363                            index++;
1364                        }
1365                    }
1366                }
1367    
1368                // Update selection
1369                obj.updateSelection(obj.records[cell[1]][cell[0]], obj.records[cell[1]+j-1][cell[0]+i-1]);
1370    
1371                if (! keepOptions) {
1372                    delete(obj.options.mergeCells[cellName]);
1373                }
1374            }
1375        }
1376    
1377        /**
1378         * Remove all merged cells
1379         */
1380        obj.destroyMerged = function(keepOptions) {
1381            // Remove any merged cells
1382            if (obj.options.mergeCells) {
1383                var mergedCells = obj.options.mergeCells;
1384                var keys = Object.keys(obj.options.mergeCells);
1385                for (var i = 0; i < keys.length; i++) {
1386                    obj.removeMerge(keys[i], null, keepOptions);
1387                }
1388            }
1389        }
1390    
1391        /**
1392         * Is column merged
1393         */
1394        obj.isColMerged = function(x, insertBefore) {
1395            var cols = [];
1396            // Remove any merged cells
1397            if (obj.options.mergeCells) {
1398                var keys = Object.keys(obj.options.mergeCells);
1399                for (var i = 0; i < keys.length; i++) {
1400                    var info = jexcel.getIdFromColumnName(keys[i], true);
1401                    var colspan = obj.options.mergeCells[keys[i]][0];
1402                    var x1 = info[0];
1403                    var x2 = info[0] + (colspan > 1 ? colspan - 1 : 0);
1404    
1405                    if (insertBefore == null) {
1406                        if ((x1 <= x && x2 >= x)) {
1407                            cols.push(keys[i]);
1408                        }
1409                    } else {
1410                        if (insertBefore) {
1411                            if ((x1 < x && x2 >= x)) {
1412                                cols.push(keys[i]);
1413                            }
1414                        } else {
1415                            if ((x1 <= x && x2 > x)) {
1416                                cols.push(keys[i]);
1417                            }
1418                        }
1419                    }
1420                }
1421            }
1422    
1423            return cols;
1424        }
1425    
1426        /**
1427         * Is rows merged
1428         */
1429        obj.isRowMerged = function(y, insertBefore) {
1430            var rows = [];
1431            // Remove any merged cells
1432            if (obj.options.mergeCells) {
1433                var keys = Object.keys(obj.options.mergeCells);
1434                for (var i = 0; i < keys.length; i++) {
1435                    var info = jexcel.getIdFromColumnName(keys[i], true);
1436                    var rowspan = obj.options.mergeCells[keys[i]][1];
1437                    var y1 = info[1];
1438                    var y2 = info[1] + (rowspan > 1 ? rowspan - 1 : 0);
1439    
1440                    if (insertBefore == null) {
1441                        if ((y1 <= y && y2 >= y)) {
1442                            rows.push(keys[i]);
1443                        }
1444                    } else {
1445                        if (insertBefore) {
1446                            if ((y1 < y && y2 >= y)) {
1447                                rows.push(keys[i]);
1448                            }
1449                        } else {
1450                            if ((y1 <= y && y2 > y)) {
1451                                rows.push(keys[i]);
1452                            }
1453                        }
1454                    }
1455                }
1456            }
1457    
1458            return rows;
1459        }
1460    
1461        /**
1462         * Open the editor
1463         * 
1464         * @param object cell
1465         * @return void
1466         */
1467        obj.openEditor = function(cell, empty, e) {
1468            // Get cell position
1469            var y = cell.getAttribute('data-y');
1470            var x = cell.getAttribute('data-x');
1471    
1472            // On edition start
1473            if (! obj.ignoreEvents) {
1474                if (typeof(obj.options.oneditionstart) == 'function') {
1475                    obj.options.oneditionstart(el, cell, x, y);
1476                }
1477            }
1478    
1479            // Overflow
1480            if (x > 0) {
1481                obj.records[y][x-1].style.overflow = 'hidden';
1482            }
1483    
1484            // Create editor
1485            var createEditor = function(type) {
1486                // Cell information
1487                var info = cell.getBoundingClientRect();
1488    
1489                // Create dropdown
1490                var editor = document.createElement(type);
1491                editor.style.width = (info.width) + 'px';
1492                editor.style.height = (info.height - 2) + 'px';
1493                editor.style.minHeight = (info.height - 2) + 'px';
1494    
1495                // Edit cell
1496                cell.classList.add('editor');
1497                cell.innerHTML = '';
1498                cell.appendChild(editor);
1499    
1500                return editor;
1501            }
1502    
1503            // Readonly
1504            if (cell.classList.contains('readonly') == true) {
1505                // Do nothing
1506            } else {
1507                // Holder
1508                obj.edition = [ obj.records[y][x], obj.records[y][x].innerHTML, x, y ];
1509    
1510                // If there is a custom editor for it
1511                if (obj.options.columns[x].editor) {
1512                    // Custom editors
1513                    obj.options.columns[x].editor.openEditor(cell, el, empty, e);
1514                } else {
1515                    // Native functions
1516                    if (obj.options.columns[x].type == 'hidden') {
1517                        // Do nothing
1518                    } else if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio') {
1519                        // Get value
1520                        var value = cell.children[0].checked ? false : true;
1521                        // Toogle value
1522                        obj.setValue(cell, value);
1523                        // Do not keep edition open
1524                        obj.edition = null;
1525                    } else if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') {
1526                        // Get current value
1527                        var value = obj.options.data[y][x];
1528    
1529                        // Create dropdown
1530                        if (typeof(obj.options.columns[x].filter) == 'function') {
1531                            var source = obj.options.columns[x].filter(el, cell, x, y, obj.options.columns[x].source);
1532                        } else {
1533                            var source = obj.options.columns[x].source;
1534                        }
1535    
1536                        // Create editor
1537                        var editor = createEditor('div');
1538                        var options = {
1539                            data: source,
1540                            multiple: obj.options.columns[x].multiple ? true : false,
1541                            autocomplete: obj.options.columns[x].autocomplete || obj.options.columns[x].type == 'autocomplete' ? true : false,
1542                            opened:true,
1543                            value: obj.options.columns[x].multiple ? value.split(';') : value,
1544                            width:'100%',
1545                            height:editor.style.minHeight,
1546                            position: (obj.options.tableOverflow == true || obj.options.fullscreen == true) ? true : false,
1547                            onclose:function() {
1548                                obj.closeEditor(cell, true);
1549                            }
1550                        };
1551                        if (obj.options.columns[x].options && obj.options.columns[x].options.type) {
1552                            options.type = obj.options.columns[x].options.type;
1553                        }
1554                        jSuites.dropdown(editor, options);
1555                    } else if (obj.options.columns[x].type == 'calendar' || obj.options.columns[x].type == 'color') {
1556                        // Value
1557                        var value = obj.options.data[y][x];
1558                        // Create editor
1559                        var editor = createEditor('input');
1560                        editor.value = value;
1561    
1562                        if (obj.options.tableOverflow == true || obj.options.fullscreen == true) {
1563                            obj.options.columns[x].options.position = true;
1564                        }
1565                        obj.options.columns[x].options.value = obj.options.data[y][x];
1566                        obj.options.columns[x].options.opened = true;
1567                        obj.options.columns[x].options.onclose = function(el, value) {
1568                            obj.closeEditor(cell, true);
1569                        }
1570                        // Current value
1571                        if (obj.options.columns[x].type == 'color') {
1572                            jSuites.color(editor, obj.options.columns[x].options);
1573                        } else {
1574                            var calendar = jSuites.calendar(editor, obj.options.columns[x].options);
1575                            calendar.setValue(value);
1576                        }
1577                        // Focus on editor
1578                        editor.focus();
1579                    } else if (obj.options.columns[x].type == 'image') {
1580                        // Value
1581                        var img = cell.children[0];
1582                        // Create editor
1583                        var editor = createEditor('div');
1584                        editor.style.position = 'relative';
1585                        var div = document.createElement('div');
1586                        div.classList.add('jclose');
1587                        if (img && img.src) {
1588                            div.appendChild(img);
1589                        }
1590                        editor.appendChild(div);
1591                        jSuites.image(div, obj.options.imageOptions);
1592                        const rect = cell.getBoundingClientRect();
1593                        const rectContent = div.getBoundingClientRect();
1594                        if (window.innerHeight < rect.bottom + rectContent.height) {
1595                            div.style.top = (rect.top - (rectContent.height + 2)) + 'px';
1596                        } else {
1597                            div.style.top = (rect.top) + 'px';
1598                        }
1599                    } else {
1600                        // Value
1601                        var value = empty == true ? '' : obj.options.data[y][x];
1602    
1603                        // Basic editor
1604                        if (obj.options.columns[x].wordWrap != false && (obj.options.wordWrap == true || obj.options.columns[x].wordWrap == true)) {
1605                            var editor = createEditor('textarea');
1606                        } else {
1607                            var editor = createEditor('input');
1608                            // Mask
1609                            if (obj.options.columns[x].mask) {
1610                                editor.setAttribute('data-mask', obj.options.columns[x].mask);
1611                            }
1612                        }
1613    
1614                        editor.value = value;
1615                        editor.onblur = function() {
1616                            obj.closeEditor(cell, true);
1617                        };
1618                        editor.focus();
1619                    }
1620                }
1621            }
1622        }
1623    
1624        /**
1625         * Close the editor and save the information
1626         * 
1627         * @param object cell
1628         * @param boolean save
1629         * @return void
1630         */
1631        obj.closeEditor = function(cell, save) {
1632            var x = parseInt(cell.getAttribute('data-x'));
1633            var y = parseInt(cell.getAttribute('data-y'));
1634    
1635            // Get cell properties
1636            if (save == true) {
1637                // If custom editor
1638                if (obj.options.columns[x].editor) {
1639                    // Custom editor
1640                    var value = obj.options.columns[x].editor.closeEditor(cell, save);
1641                } else {
1642                    // Native functions
1643                    if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio' || obj.options.columns[x].type == 'hidden') {
1644                        // Do nothing
1645                    } else if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') {
1646                        var value = cell.children[0].dropdown.close(true);
1647                    } else if (obj.options.columns[x].type == 'calendar') {
1648                        var value = cell.children[0].calendar.close(true);
1649                    } else if (obj.options.columns[x].type == 'color') {
1650                        var value = cell.children[0].color.close(true);
1651                    } else if (obj.options.columns[x].type == 'image') {
1652                        var img = cell.children[0].children[0].children[0];
1653                        var value = img && img.tagName == 'IMG' ? img.src : '';
1654                    } else if (obj.options.columns[x].type == 'numeric') {
1655                        var value = cell.children[0].value;
1656                        if (value.substr(0,1) != '=') {
1657                            if (value == '') {
1658                                value = obj.options.columns[x].allowEmpty ? '' : 0;
1659                            }
1660                        }
1661                        cell.children[0].onblur = null;
1662                    } else {
1663                        var value = cell.children[0].value;
1664                        cell.children[0].onblur = null;
1665                    }
1666                }
1667    
1668                // Ignore changes if the value is the same
1669                if (obj.options.data[y][x] == value) {
1670                    cell.innerHTML = obj.edition[1];
1671                } else {
1672                    obj.setValue(cell, value);
1673                }
1674            } else {
1675                if (obj.options.columns[x].editor) {
1676                    // Custom editor
1677                    obj.options.columns[x].editor.closeEditor(cell, save);
1678                } else {
1679                    if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') {
1680                        cell.children[0].dropdown.close(true);
1681                    } else if (obj.options.columns[x].type == 'calendar') {
1682                        cell.children[0].calendar.close(true);
1683                    } else if (obj.options.columns[x].type == 'color') {
1684                        cell.children[0].color.close(true);
1685                    } else {
1686                        cell.children[0].onblur = null;
1687                    }
1688                }
1689    
1690                // Restore value
1691                cell.innerHTML = obj.edition && obj.edition[1] ? obj.edition[1] : '';
1692            }
1693    
1694            // On edition end
1695            if (! obj.ignoreEvents) {
1696                if (typeof(obj.options.oneditionend) == 'function') {
1697                    obj.options.oneditionend(el, cell, x, y, value, save);
1698                }
1699            }
1700    
1701            // Remove editor class
1702            cell.classList.remove('editor');
1703    
1704            // Finish edition
1705            obj.edition = null;
1706        }
1707    
1708        /**
1709         * Get the cell object
1710         * 
1711         * @param object cell
1712         * @return string value
1713         */
1714        obj.getCell = function(cell) {
1715            // Convert in case name is excel liked ex. A10, BB92
1716            cell = jexcel.getIdFromColumnName(cell, true);
1717            var x = cell[0];
1718            var y = cell[1];
1719    
1720            return obj.records[y][x];
1721        }
1722    
1723        /**
1724         * Get the cell object from coords
1725         * 
1726         * @param object cell
1727         * @return string value
1728         */
1729        obj.getCellFromCoords = function(x, y) {
1730            return obj.records[y][x];
1731        }
1732    
1733        /**
1734         * Get label
1735         * 
1736         * @param object cell
1737         * @return string value
1738         */
1739        obj.getLabel = function(cell) {
1740            // Convert in case name is excel liked ex. A10, BB92
1741            cell = jexcel.getIdFromColumnName(cell, true);
1742            var x = cell[0];
1743            var y = cell[1];
1744    
1745            return obj.records[y][x].innerHTML;
1746        }
1747    
1748        /**
1749         * Get labelfrom coords
1750         * 
1751         * @param object cell
1752         * @return string value
1753         */
1754        obj.getLabelFromCoords = function(x, y) {
1755            return obj.records[y][x].innerHTML;
1756        }
1757    
1758        /**
1759         * Get the value from a cell
1760         * 
1761         * @param object cell
1762         * @return string value
1763         */
1764        obj.getValue = function(cell, processedValue) {
1765            if (typeof(cell) == 'object') {
1766                var x = cell.getAttribute('data-x');
1767                var y = cell.getAttribute('data-y');
1768            } else {
1769                cell = jexcel.getIdFromColumnName(cell, true);
1770                var x = cell[0];
1771                var y = cell[1];
1772            }
1773    
1774            var value = null;
1775    
1776            if (x != null && y != null) {
1777                if (obj.records[y] && obj.records[y][x] && (processedValue || obj.options.copyCompatibility == true)) {
1778                    value = obj.records[y][x].innerHTML;
1779                } else {
1780                    if (obj.options.data[y] && obj.options.data[y][x] != 'undefined') {
1781                        value = obj.options.data[y][x];
1782                    }
1783                }
1784            }
1785    
1786            return value;
1787        }
1788    
1789        /**
1790         * Get the value from a coords
1791         * 
1792         * @param int x
1793         * @param int y
1794         * @return string value
1795         */
1796        obj.getValueFromCoords = function(x, y, processedValue) {
1797            var value = null;
1798    
1799            if (x != null && y != null) {
1800                if ((obj.records[y] && obj.records[y][x]) && processedValue || obj.options.copyCompatibility == true) {
1801                    value = obj.records[y][x].innerHTML;
1802                } else {
1803                    if (obj.options.data[y] && obj.options.data[y][x] != 'undefined') {
1804                        value = obj.options.data[y][x];
1805                    }
1806                }
1807            }
1808    
1809            return value;
1810        }
1811    
1812        /**
1813         * Set a cell value
1814         * 
1815         * @param mixed cell destination cell
1816         * @param string value value
1817         * @return void
1818         */
1819        obj.setValue = function(cell, value, force) {
1820            var records = [];
1821    
1822            if (typeof(cell) == 'string') {
1823                var columnId = jexcel.getIdFromColumnName(cell, true);
1824                var x = columnId[0];
1825                var y = columnId[1];
1826    
1827                // Update cell
1828                records.push(obj.updateCell(x, y, value, force));
1829    
1830                // Update all formulas in the chain
1831                obj.updateFormulaChain(x, y, records);
1832            } else {
1833                var x = null;
1834                var y = null;
1835                if (cell && cell.getAttribute) {
1836                    var x = cell.getAttribute('data-x');
1837                    var y = cell.getAttribute('data-y');
1838                }
1839    
1840                // Update cell
1841                if (x != null && y != null) {
1842                    records.push(obj.updateCell(x, y, value, force));
1843    
1844                    // Update all formulas in the chain
1845                    obj.updateFormulaChain(x, y, records);
1846                } else {
1847                    var keys = Object.keys(cell);
1848                    if (keys.length > 0) {
1849                        for (var i = 0; i < keys.length; i++) {
1850                            if (typeof(cell[i]) == 'string') {
1851                                var columnId = jexcel.getIdFromColumnName(cell[i], true);
1852                                var x = columnId[0];
1853                                var y = columnId[1];
1854                            } else {
1855                                var x = cell[i].getAttribute('data-x');
1856                                var y = cell[i].getAttribute('data-y');
1857                            }
1858    
1859                             // Update cell
1860                            if (x != null && y != null) {
1861                                records.push(obj.updateCell(x, y, value, force));
1862    
1863                                // Update all formulas in the chain
1864                                obj.updateFormulaChain(x, y, records);
1865                            }
1866                        }
1867                    }
1868                }
1869            }
1870    
1871            // Update history
1872            obj.setHistory({
1873                action:'setValue',
1874                records:records,
1875                selection:obj.selectedCell,
1876            });
1877    
1878            // Update table with custom configurations if applicable
1879            obj.updateTable();
1880    
1881            // On after changes
1882            obj.onafterchanges(el, records);
1883        }
1884    
1885        /**
1886         * Set a cell value based on coordinates
1887         * 
1888         * @param int x destination cell
1889         * @param int y destination cell
1890         * @param string value
1891         * @return void
1892         */
1893        obj.setValueFromCoords = function(x, y, value, force) {
1894            var records = [];
1895            records.push(obj.updateCell(x, y, value, force));
1896    
1897            // Update all formulas in the chain
1898            obj.updateFormulaChain(x, y, records);
1899    
1900            // Update history
1901            obj.setHistory({
1902                action:'setValue',
1903                records:records,
1904                selection:obj.selectedCell,
1905            });
1906    
1907            // Update table with custom configurations if applicable
1908            obj.updateTable();
1909    
1910            // On after changes
1911            obj.onafterchanges(el, records);
1912        }
1913    
1914        /**
1915         * Toogle
1916         */
1917        obj.setCheckRadioValue = function() {
1918            var records = [];
1919            var keys = Object.keys(obj.highlighted);
1920            for (var i = 0; i < keys.length; i++) {
1921                var x = obj.highlighted[i].getAttribute('data-x');
1922                var y = obj.highlighted[i].getAttribute('data-y');
1923    
1924                if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio') {
1925                    // Update cell
1926                    records.push(obj.updateCell(x, y, ! obj.options.data[y][x]));
1927                }
1928            }
1929    
1930            if (records.length) {
1931                // Update history
1932                obj.setHistory({
1933                    action:'setValue',
1934                    records:records,
1935                    selection:obj.selectedCell,
1936                });
1937    
1938                // On after changes
1939                obj.onafterchanges(el, records);
1940            }
1941        }
1942    
1943        /**
1944         * Update cell content
1945         * 
1946         * @param object cell
1947         * @return void
1948         */
1949        obj.updateCell = function(x, y, value, force) {
1950            // Changing value depending on the column type
1951            if (obj.records[y][x].classList.contains('readonly') == true && ! force) {
1952                // Do nothing
1953                var record = {
1954                    x: x,
1955                    y: y,
1956                    col: x,
1957                    row: y
1958                }
1959            } else {
1960                // On change
1961                if (! obj.ignoreEvents) {
1962                    if (typeof(obj.options.onbeforechange) == 'function') {
1963                        // Overwrite a value
1964                        var val = obj.options.onbeforechange(el, obj.records[y][x], x, y, value);
1965
1966                        // If you return something this will overwrite the value
1967                        if (val != undefined) {
1968                            value = val;
1969                        }
1970                    }
1971                }
1972
1973                // History format
1974                var record = {
1975                    x: x,
1976                    y: y,
1977                    col: x,
1978                    row: y,
1979                    newValue: value,
1980                    oldValue: obj.options.data[y][x],
1981                }
1982
1983                if (obj.options.columns[x].editor) {
1984                    // Update data and cell
1985                    obj.options.data[y][x] = value;
1986                    obj.options.columns[x].editor.setValue(obj.records[y][x], value, force);
1987                } else {
1988                    // Native functions
1989                    if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio') {
1990                        // Unchecked all options
1991                        if (obj.options.columns[x].type == 'radio') {
1992                            for (var j = 0; j < obj.options.data.length; j++) {
1993                                obj.options.data[j][x] = false;
1994                            }
1995                        }
1996
1997                        // Update data and cell
1998                        obj.records[y][x].children[0].checked = (value == 1 || value == true || value == 'true' || value == 'TRUE') ? true : false;
1999                        obj.options.data[y][x] = obj.records[y][x].children[0].checked;
2000                    } else if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') {
2001                        // Update data and cell
2002                        obj.options.data[y][x] = value;
2003                        obj.records[y][x].innerHTML = obj.getDropDownValue(x, value);
2004                    } else if (obj.options.columns[x].type == 'calendar') {
2005                        // Update calendar
2006                        var formatted = jSuites.calendar.extractDateFromString(value, obj.options.columns[x].options.format);
2007                        // Update data and cell
2008                        obj.options.data[y][x] = value;
2009                        obj.records[y][x].innerHTML = jSuites.calendar.getDateString(formatted ? formatted : value, obj.options.columns[x].options.format);
2010                    } else if (obj.options.columns[x].type == 'color') {
2011                        // Update color
2012                        obj.options.data[y][x] = value;
2013                        // Render
2014                        if (obj.options.columns[x].render == 'square') {
2015                            var color = document.createElement('div');
2016                            color.className = 'color';
2017                            color.style.backgroundColor = value;
2018                            obj.records[y][x].innerHTML = '';
2019                            obj.records[y][x].appendChild(color);
2020                        } else {
2021                        obj.records[y][x].style.color = value;
2022                            obj.records[y][x].innerHTML = value;
2023                        }
2024                    } else if (obj.options.columns[x].type == 'image') {
2025                        value = ''+value;
2026                        obj.options.data[y][x] = value;
2027                        obj.records[y][x].innerHTML = '';
2028                        if (value && value.substr(0, 10) == 'data:image') {
2029                            var img = document.createElement('img');
2030                            img.src = value;
2031                            obj.records[y][x].appendChild(img);
2032                        }
2033                    } else {
2034                        // Update data and cell
2035                        obj.options.data[y][x] = value;
2036                        // Label
2037                        if (('' + value).substr(0,1) == '='  && obj.options.parseFormulas == true) {
2038                            value = obj.executeFormula(value, x, y);
2039                        }
2040                        if (obj.options.columns[x].mask) {
2041                            var decimal = obj.options.columns[x].decimal || '.';
2042                            value = '' + jSuites.mask.run(value, obj.options.columns[x].mask, decimal);
2043                        }
2044                        obj.records[y][x].innerHTML = value;
2045
2046                        // Handle big text inside a cell
2047                        if (obj.options.columns[x].wordWrap != false && (obj.options.wordWrap == true || obj.options.columns[x].wordWrap == true || obj.records[y][x].innerHTML.length > 200)) {
2048                            obj.records[y][x].style.whiteSpace = 'pre-wrap';
2049                        } else {
2050                            obj.records[y][x].style.whiteSpace = '';
2051                        }
2052                    }
2053                }
2054
2055                // Overflow
2056                if (x > 0) {
2057                    if (value) {
2058                        obj.records[y][x-1].style.overflow = 'hidden';
2059                    } else {
2060                        obj.records[y][x-1].style.overflow = '';
2061                    }
2062                }
2063
2064                // On change
2065                if (! obj.ignoreEvents) {
2066                    if (typeof(obj.options.onchange) == 'function') {
2067                        obj.options.onchange(el, (obj.records[y] && obj.records[y][x] ? obj.records[y][x] : null), x, y, value, record.oldValue);
2068                    }
2069                }
2070            }
2071
2072            return record;
2073        }
2074
2075        /**
2076         * Helper function to copy data using the corner icon
2077         */
2078        obj.copyData = function(o, d) {
2079            // Get data from all selected cells
2080            var data = obj.getData(true, true);
2081
2082            // Selected cells
2083            var h = obj.selectedContainer;
2084
2085            // Cells
2086            var x1 = parseInt(o.getAttribute('data-x'));
2087            var y1 = parseInt(o.getAttribute('data-y'));
2088            var x2 = parseInt(d.getAttribute('data-x'));
2089            var y2 = parseInt(d.getAttribute('data-y'));
2090
2091            // Records
2092            var records = [];
2093            var breakControl = false;
2094
2095            if (h[0] == x1) {
2096                // Vertical copy
2097                if (y1 < h[1]) {
2098                    var rowNumber = y1 - h[1];
2099                } else {
2100                    var rowNumber = 1;
2101                }
2102                var colNumber = 0;
2103            } else {
2104                if (x1 < h[0]) {
2105                    var colNumber = x1 - h[0];
2106                } else {
2107                    var colNumber = 1;
2108                }
2109                var rowNumber = 0;
2110            }
2111
2112            // Copy data procedure
2113            var posx = 0;
2114            var posy = 0;
2115
2116            for (var j = y1; j <= y2; j++) {
2117                // Skip hidden rows
2118                if (obj.rows[j] && obj.rows[j].style.display == 'none') {
2119                    continue;
2120                }
2121
2122                // Controls
2123                if (data[posy] == undefined) {
2124                    posy = 0;
2125                }
2126                posx = 0;
2127
2128                // Data columns
2129                if (h[0] != x1) {
2130                    if (x1 < h[0]) {
2131                        var colNumber = x1 - h[0];
2132                    } else {
2133                        var colNumber = 1;
2134                    }
2135                }
2136                // Data columns
2137                for (var i = x1; i <= x2; i++) {
2138                    // Update non-readonly
2139                    if (obj.records[j][i] && ! obj.records[j][i].classList.contains('readonly') && obj.records[j][i].style.display != 'none' && breakControl == false) {
2140                        // Stop if contains value
2141                        if (! obj.selection.length) {
2142                            if (obj.options.data[j][i] != '') {
2143                                breakControl = true;
2144                                continue;
2145                            }
2146                        }
2147    
2148                        // Column
2149                        if (data[posy] == undefined) {
2150                            posx = 0;
2151                        } else if (data[posy][posx] == undefined) {
2152                            posx = 0;
2153                        }
2154
2155                        // Value
2156                        var value = data[posy][posx];
2157    
2158                        if (value && ! data[1] && obj.options.autoIncrement == true) {
2159                            if (obj.options.columns[i].type == 'text' || obj.options.columns[i].type == 'number') {
2160                                if ((''+value).substr(0,1) == '=') {
2161                                    var tokens = value.match(/([A-Z]+[0-9]+)/g);
2162    
2163                                    if (tokens) {
2164                                        var affectedTokens = [];
2165                                        for (var index = 0; index < tokens.length; index++) {
2166                                            var position = jexcel.getIdFromColumnName(tokens[index], 1);
2167                                            position[0] += colNumber;
2168                                            position[1] += rowNumber;
2169                                            if (position[1] < 0) {
2170                                                position[1] = 0;
2171                                            }
2172                                            var token = jexcel.getColumnNameFromId([position[0], position[1]]);
2173    
2174                                            if (token != tokens[index]) {
2175                                                affectedTokens[tokens[index]] = token;
2176                                            }
2177                                        }
2178                                        // Update formula
2179                                        if (affectedTokens) {
2180                                            value = obj.updateFormula(value, affectedTokens)
2181                                        }
2182                                    }
2183                                } else {
2184                                    if (value == Number(value)) {
2185                                        value = Number(value) + rowNumber;
2186                                    }
2187                                }
2188                            } else if (obj.options.columns[i].type == 'calendar') {
2189                                var date = new Date(value);
2190                                date.setDate(date.getDate() + rowNumber);
2191                                value = date.getFullYear() + '-' + jexcel.doubleDigitFormat(parseInt(date.getMonth() + 1)) + '-' + jexcel.doubleDigitFormat(date.getDate()) + ' ' + '00:00:00';
2192                            }
2193                        }
2194    
2195                        records.push(obj.updateCell(i, j, value));
2196    
2197                        // Update all formulas in the chain
2198                        obj.updateFormulaChain(i, j, records);
2199                    }
2200                    posx++;
2201                    if (h[0] != x1) {
2202                        colNumber++;
2203                    }
2204                }
2205                posy++;
2206                rowNumber++;
2207            }
2208    
2209            // Update history
2210            obj.setHistory({
2211                action:'setValue',
2212                records:records,
2213                selection:obj.selectedCell,
2214            });
2215    
2216            // Update table with custom configuration if applicable
2217            obj.updateTable();
2218    
2219            // On after changes
2220            obj.onafterchanges(el, records);
2221        }
2222    
2223        /**
2224         * Refresh current selection
2225         */
2226        obj.refreshSelection = function() {
2227            if (obj.selectedCell) {
2228                obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
2229            }
2230        }
2231    
2232        /**
2233         * Move coords to A1 in case ovelaps with an excluded cell
2234         */
2235        obj.conditionalSelectionUpdate = function(type, o, d) {
2236            if (type == 1) {
2237                if (obj.selectedCell && ((o >= obj.selectedCell[1] && o <= obj.selectedCell[3]) || (d >= obj.selectedCell[1] && d <= obj.selectedCell[3]))) {
2238                    obj.resetSelection();
2239                    return;
2240                }
2241            } else {
2242                if (obj.selectedCell && ((o >= obj.selectedCell[0] && o <= obj.selectedCell[2]) || (d >= obj.selectedCell[0] && d <= obj.selectedCell[2]))) {
2243                    obj.resetSelection();
2244                    return;
2245                }
2246            }
2247        }
2248    
2249        /**
2250         * Clear table selection
2251         */
2252        obj.resetSelection = function(blur) {
2253            // Remove style
2254            if (! obj.highlighted.length) {
2255                var previousStatus = 0;
2256            } else {
2257                var previousStatus = 1;
2258    
2259                for (var i = 0; i < obj.highlighted.length; i++) {
2260                    obj.highlighted[i].classList.remove('highlight');
2261                    obj.highlighted[i].classList.remove('highlight-left');
2262                    obj.highlighted[i].classList.remove('highlight-right');
2263                    obj.highlighted[i].classList.remove('highlight-top');
2264                    obj.highlighted[i].classList.remove('highlight-bottom');
2265                    obj.highlighted[i].classList.remove('highlight-selected');
2266    
2267                    var px = parseInt(obj.highlighted[i].getAttribute('data-x'));
2268                    var py = parseInt(obj.highlighted[i].getAttribute('data-y'));
2269    
2270                    // Check for merged cells
2271                    if (obj.highlighted[i].getAttribute('data-merged')) {
2272                        var colspan = parseInt(obj.highlighted[i].getAttribute('colspan'));
2273                        var rowspan = parseInt(obj.highlighted[i].getAttribute('rowspan'));
2274                        var ux = colspan > 0 ? px + (colspan - 1) : px;
2275                        var uy = rowspan > 0 ? py + (rowspan - 1): py;
2276                    } else {
2277                        var ux = px;
2278                        var uy = py;
2279                    }
2280    
2281                    // Remove selected from headers
2282                    for (var j = px; j <= ux; j++) {
2283                        if (obj.headers[j]) {
2284                            obj.headers[j].classList.remove('selected');
2285                        }
2286                    }
2287    
2288                    // Remove selected from rows
2289                    for (var j = py; j <= uy; j++) {
2290                        if (obj.rows[j]) {
2291                            obj.rows[j].classList.remove('selected');
2292                        }
2293                    }
2294                }
2295            }
2296    
2297            // Reset highlighed cells
2298            obj.highlighted = [];
2299    
2300            // Reset
2301            obj.selectedCell = null;
2302    
2303            // Hide corner
2304            obj.corner.style.top = '-2000px';
2305            obj.corner.style.left = '-2000px';
2306    
2307            if (obj.ignoreEvents != true && blur == true) {
2308                if (obj.options.onblur) {
2309                    if (typeof(obj.options.onblur) == 'function') {
2310                        if (previousStatus == 1) {
2311                            obj.options.onblur(el);
2312                        }
2313                    }
2314                }
2315            }
2316    
2317            return previousStatus;
2318        }
2319    
2320        /**
2321         * Update selection based on two cells
2322         */
2323        obj.updateSelection = function(el1, el2, origin) {
2324            var x1 = el1.getAttribute('data-x');
2325            var y1 = el1.getAttribute('data-y');
2326            if (el2) {
2327                var x2 = el2.getAttribute('data-x');
2328                var y2 = el2.getAttribute('data-y');
2329            } else {
2330                var x2 = x1;
2331                var y2 = y1;
2332            }
2333    
2334            obj.updateSelectionFromCoords(x1, y1, x2, y2, origin);
2335        }
2336    
2337        /**
2338         * Update selection from coords
2339         */
2340        obj.updateSelectionFromCoords = function(x1, y1, x2, y2, origin) {
2341            // Reset Selection
2342            var updated = null;
2343            var previousState = obj.resetSelection();
2344    
2345            // Same element
2346            if (x2 == null) {
2347                x2 = x1;
2348            }
2349            if (y2 == null) {
2350                y2 = y1;
2351            }
2352    
2353            // Selection must be within the existing data
2354            if (x1 >= obj.headers.length) {
2355                x1 = obj.headers.length - 1;
2356            }
2357            if (y1 >= obj.rows.length) {
2358                y1 = obj.rows.length - 1;
2359            }
2360            if (x2 >= obj.headers.length) {
2361                x2 = obj.headers.length - 1;
2362            }
2363            if (y2 >= obj.rows.length) {
2364                y2 = obj.rows.length - 1;
2365            }
2366    
2367            // Keep selected cell
2368            obj.selectedCell = [x1, y1, x2, y2];
2369    
2370            // Select cells
2371            if (x1 != null) {
2372                // Add selected cell
2373                if (obj.records[y1][x1]) {
2374                    obj.records[y1][x1].classList.add('highlight-selected');
2375                }
2376    
2377                // Origin & Destination
2378                if (parseInt(x1) < parseInt(x2)) {
2379                    var px = parseInt(x1);
2380                    var ux = parseInt(x2);
2381                } else {
2382                    var px = parseInt(x2);
2383                    var ux = parseInt(x1);
2384                }
2385    
2386                if (parseInt(y1) < parseInt(y2)) {
2387                    var py = parseInt(y1);
2388                    var uy = parseInt(y2);
2389                } else {
2390                    var py = parseInt(y2);
2391                    var uy = parseInt(y1);
2392                }
2393    
2394                // Verify merged columns
2395                for (var i = px; i <= ux; i++) {
2396                    for (var j = py; j <= uy; j++) {
2397                        if (obj.records[j][i] && obj.records[j][i].getAttribute('data-merged')) {
2398                            var x = parseInt(obj.records[j][i].getAttribute('data-x'));
2399                            var y = parseInt(obj.records[j][i].getAttribute('data-y'));
2400                            var colspan = parseInt(obj.records[j][i].getAttribute('colspan'));
2401                            var rowspan = parseInt(obj.records[j][i].getAttribute('rowspan'));
2402    
2403                            if (colspan > 1) {
2404                                if (x < px) {
2405                                    px = x;
2406                                }
2407                                if (x + colspan > ux) {
2408                                    ux = x + colspan - 1;
2409                                }
2410                            }
2411    
2412                            if (rowspan) {
2413                                if (y < py) {
2414                                    py = y;
2415    
2416                                }
2417                                if (y + rowspan > uy) {
2418                                    uy = y + rowspan - 1;
2419                                }
2420                            }
2421                        }
2422                    }
2423                }
2424    
2425                // Limits
2426                var borderLeft = null;
2427                var borderRight = null;
2428                var borderTop = null;
2429                var borderBottom = null;
2430    
2431                // Vertical limits
2432                for (var j = py; j <= uy; j++) {
2433                    if (obj.rows[j].style.display != 'none') {
2434                        if (borderTop == null) {
2435                            borderTop = j;
2436                        }
2437                        borderBottom = j;
2438                    }
2439                }
2440    
2441                // Redefining styles
2442                for (var i = px; i <= ux; i++) {
2443                    for (var j = py; j <= uy; j++) {
2444                        if (obj.rows[j].style.display != 'none' && obj.records[j][i].style.display != 'none') {
2445                            obj.records[j][i].classList.add('highlight');
2446                            obj.highlighted.push(obj.records[j][i]);
2447                        }
2448                    }
2449    
2450                    // Horizontal limits
2451                    if (obj.options.columns[i].type != 'hidden') {
2452                        if (borderLeft == null) {
2453                            borderLeft = i;
2454                        }
2455                        borderRight = i;
2456                    }
2457                }
2458    
2459                // Create borders
2460                if (! borderLeft) {
2461                    borderLeft = 0;
2462                }
2463                if (! borderRight) {
2464                    borderRight = 0;
2465                }
2466                for (var i = borderLeft; i <= borderRight; i++) {
2467                    if (obj.options.columns[i].type != 'hidden') {
2468                        // Top border
2469                        if (obj.records[borderTop][i]) {
2470                            obj.records[borderTop][i].classList.add('highlight-top');
2471                        }
2472                        // Bottom border
2473                        if (obj.records[borderBottom][i]) {
2474                            obj.records[borderBottom][i].classList.add('highlight-bottom');
2475                        }
2476                        // Add selected from headers
2477                        obj.headers[i].classList.add('selected');
2478                    }
2479                }
2480    
2481                for (var j = borderTop; j <= borderBottom; j++) {
2482                    if (obj.rows[j].style.display != 'none') {
2483                        // Left border
2484                        obj.records[j][borderLeft].classList.add('highlight-left');
2485                        // Right border
2486                        obj.records[j][borderRight].classList.add('highlight-right');
2487                        // Add selected from rows
2488                        obj.rows[j].classList.add('selected');
2489                    }
2490                }
2491    
2492                obj.selectedContainer = [ borderLeft, borderTop, borderRight, borderBottom ];
2493            }
2494    
2495            // Handle events
2496            if (obj.ignoreEvents != true) {
2497                if (obj.options.onfocus) {
2498                    if (typeof(obj.options.onfocus) == 'function') {
2499                        if (previousState == 0) {
2500                            obj.options.onfocus(el);
2501                        }
2502                    }
2503                }
2504    
2505                if (typeof(obj.options.onselection) == 'function') {
2506                    obj.options.onselection(el, borderLeft, borderTop, borderRight, borderBottom, origin);
2507                }
2508            }
2509
2510            // Find corner cell
2511            obj.updateCornerPosition();
2512        }
2513    
2514        /**
2515         * Remove copy selection
2516         * 
2517         * @return void
2518         */
2519        obj.removeCopySelection = function() {
2520            // Remove current selection
2521            for (var i = 0; i < obj.selection.length; i++) {
2522                obj.selection[i].classList.remove('selection');
2523                obj.selection[i].classList.remove('selection-left');
2524                obj.selection[i].classList.remove('selection-right');
2525                obj.selection[i].classList.remove('selection-top');
2526                obj.selection[i].classList.remove('selection-bottom');
2527            }
2528    
2529            obj.selection = [];
2530        }
2531    
2532        /**
2533         * Update copy selection
2534         * 
2535         * @param int x, y
2536         * @return void
2537         */
2538        obj.updateCopySelection = function(x3, y3) {
2539            // Remove selection
2540            obj.removeCopySelection();
2541    
2542            // Get elements first and last
2543            var x1 = obj.selectedContainer[0];
2544            var y1 = obj.selectedContainer[1];
2545            var x2 = obj.selectedContainer[2];
2546            var y2 = obj.selectedContainer[3];
2547    
2548            if (x3 != null && y3 != null) {
2549                if (x3 - x2 > 0) {
2550                    var px = parseInt(x2) + 1;
2551                    var ux = parseInt(x3);
2552                } else {
2553                    var px = parseInt(x3);
2554                    var ux = parseInt(x1) - 1;
2555                }
2556    
2557                if (y3 - y2 > 0) {
2558                    var py = parseInt(y2) + 1;
2559                    var uy = parseInt(y3);
2560                } else {
2561                    var py = parseInt(y3);
2562                    var uy = parseInt(y1) - 1;
2563                }
2564    
2565                if (ux - px < uy - py) {
2566                    var px = parseInt(x1);
2567                    var ux = parseInt(x2);
2568                } else {
2569                    var py = parseInt(y1);
2570                    var uy = parseInt(y2);
2571                }
2572    
2573                for (var j = py; j <= uy; j++) {
2574                    for (var i = px; i <= ux; i++) {
2575                        if (obj.records[j][i] && obj.rows[j].style.display != 'none' && obj.records[j][i].style.display != 'none') {
2576                            obj.records[j][i].classList.add('selection');
2577                            obj.records[py][i].classList.add('selection-top');
2578                            obj.records[uy][i].classList.add('selection-bottom');
2579                            obj.records[j][px].classList.add('selection-left');
2580                            obj.records[j][ux].classList.add('selection-right');
2581    
2582                            // Persist selected elements
2583                            obj.selection.push(obj.records[j][i]);
2584                        }
2585                    }
2586                }
2587            }
2588        }
2589    
2590        /**
2591         * Update corner position
2592         * 
2593         * @return void
2594         */
2595        obj.updateCornerPosition = function() {
2596            // If any selected cells
2597            if (! obj.highlighted.length) {
2598                obj.corner.style.top = '-2000px';
2599                obj.corner.style.left = '-2000px';
2600            } else {
2601                // Get last cell
2602                var last = obj.highlighted[obj.highlighted.length-1];
2603
2604                const contentRect = obj.content.getBoundingClientRect();
2605                var x1 = contentRect.left;
2606                var y1 = contentRect.top;
2607
2608                const lastRect = last.getBoundingClientRect();
2609                var x2 = lastRect.left;
2610                var y2 = lastRect.top;
2611                var w2 = lastRect.width;
2612                var h2 = lastRect.height;
2613
2614                var x = (x2 - x1) + obj.content.scrollLeft + w2 - 4;
2615                var y = (y2 - y1) + obj.content.scrollTop + h2 - 4;
2616
2617                // Place the corner in the correct place
2618                obj.corner.style.top = y + 'px';
2619                obj.corner.style.left = x + 'px';
2620            }
2621        }
2622    
2623        /**
2624         * Update scroll position based on the selection
2625         */
2626        obj.updateScroll = function(direction) {
2627            // jExcel Container information
2628            const contentRect = obj.content.getBoundingClientRect();
2629            var x1 = contentRect.left;
2630            var y1 = contentRect.top;
2631            var w1 = contentRect.width;
2632            var h1 = contentRect.height;
2633
2634            // Direction Left or Up
2635            var reference = obj.records[obj.selectedCell[3]][obj.selectedCell[2]];
2636    
2637            // Reference
2638            const referenceRect = reference.getBoundingClientRect();
2639            var x2 = referenceRect.left;
2640            var y2 = referenceRect.top;
2641            var w2 = referenceRect.width;
2642            var h2 = referenceRect.height;
2643    
2644            // Direction
2645            if (direction == 0 || direction == 1) {
2646                var x = (x2 - x1) + obj.content.scrollLeft;
2647                var y = (y2 - y1) + obj.content.scrollTop - 2;
2648            } else {
2649                var x = (x2 - x1) + obj.content.scrollLeft + w2;
2650                var y = (y2 - y1) + obj.content.scrollTop + h2;
2651            }
2652    
2653            // Top position check
2654            if (y > (obj.content.scrollTop + 30) && y < (obj.content.scrollTop + h1)) {
2655                // In the viewport
2656            } else {
2657                // Out of viewport
2658                if (y < obj.content.scrollTop + 30) {
2659                    obj.content.scrollTop = y - h2;
2660                } else {
2661                    obj.content.scrollTop = y - (h1 - 2);
2662                }
2663            }
2664    
2665            // Left position check - TODO: change that to the bottom border of the element
2666            if (x > (obj.content.scrollLeft) && x < (obj.content.scrollLeft + w1)) {
2667                // In the viewport
2668            } else {
2669                // Out of viewport
2670                if (x < obj.content.scrollLeft + 30) {
2671                    obj.content.scrollLeft = x;
2672                    if (obj.content.scrollLeft < 50) {
2673                        obj.content.scrollLeft = 0;
2674                    }
2675                } else {
2676                    obj.content.scrollLeft = x - (w1 - 20);
2677                }
2678            }
2679        }
2680    
2681        /**
2682         * Get the column width
2683         * 
2684         * @param int column column number (first column is: 0)
2685         * @return int current width
2686         */
2687        obj.getWidth = function(column) {
2688            if (! column) {
2689                // Get all headers
2690                var data = [];
2691                for (var i = 0; i < obj.headers.length; i++) {
2692                    data.push(obj.options.columns[i].width);
2693                }
2694            } else {
2695                // In case the column is an object
2696                if (typeof(column) == 'object') {
2697                    column = $(column).getAttribute('data-x');
2698                }
2699    
2700                data = obj.colgroup[column].getAttribute('width')
2701            }
2702    
2703            return data;
2704        }
2705    
2706        /**
2707         * Set the column width
2708         * 
2709         * @param int column number (first column is: 0)
2710         * @param int new column width
2711         * @param int old column width
2712         */
2713        obj.setWidth = function (column, width, oldWidth) {
2714            if (width > 0) {
2715                // In case the column is an object
2716                if (typeof(column) == 'object') {
2717                    column = column.getAttribute('data-x');
2718                }
2719    
2720                // Oldwidth
2721                if (! oldWidth) {
2722                    oldWidth = obj.colgroup[column].getAttribute('width');
2723                }
2724    
2725                // Set width
2726                obj.colgroup[column].setAttribute('width', width);
2727                obj.options.columns[column].width = width;
2728    
2729                // Keeping history of changes
2730                obj.setHistory({
2731                    action:'setWidth',
2732                    column:column,
2733                    oldValue:oldWidth,
2734                    newValue:width,
2735                });
2736    
2737                // On resize column
2738                if (obj.ignoreEvents != true) {
2739                    if (typeof(obj.options.onresizecolumn) == 'function') {
2740                        obj.options.onresizecolumn(el, column, width, oldWidth);
2741                    }
2742                }
2743    
2744                // Update corner position
2745                obj.updateCornerPosition();
2746            }
2747        }
2748    
2749        /**
2750         * Set the row height
2751         * 
2752         * @param row - row number (first row is: 0)
2753         * @param height - new row height
2754         * @param oldHeight - old row height
2755         */
2756        obj.setHeight = function (row, height, oldHeight) {
2757            if (height > 0) {
2758                // In case the column is an object
2759                if (typeof(row) == 'object') {
2760                    row = row.getAttribute('data-y');
2761                }
2762    
2763                // Oldwidth
2764                if (! oldHeight) {
2765                    oldHeight = obj.rows[row].getAttribute('height');
2766
2767                    if (! oldHeight) {
2768                        var rect = obj.rows[row].getBoundingClientRect();
2769                        oldHeight = rect.height;
2770                    }
2771                }
2772
2773                // Integer
2774                height = parseInt(height);
2775
2776                // Set width
2777                obj.rows[row].style.height = height + 'px';
2778    
2779                // Keep options updated
2780                if (! obj.options.rows[row]) {
2781                    obj.options.rows[row] = {};
2782                }
2783                obj.options.rows[row].height = height;
2784    
2785                // Keeping history of changes
2786                obj.setHistory({
2787                    action:'setHeight',
2788                    row:row,
2789                    oldValue:oldHeight,
2790                    newValue:height,
2791                });
2792    
2793                // On resize column
2794                if (obj.ignoreEvents != true) {
2795                    if (typeof(obj.options.onresizerow) == 'function') {
2796                        obj.options.onresizerow(el, row, height, oldHeight);
2797                    }
2798                }
2799    
2800                // Update corner position
2801                obj.updateCornerPosition();
2802            }
2803        }
2804    
2805        /**
2806         * Get the row height
2807         * 
2808         * @param row - row number (first row is: 0)
2809         * @return height - current row height
2810         */
2811        obj.getHeight = function(row) {
2812            if (! row) {
2813                // Get height of all rows
2814                var data = [];
2815                for (var j = 0; j < obj.rows.length; j++) {
2816                    var h = obj.rows[j].style.height;
2817                    if (h) {
2818                        data[j] = h;
2819                    }
2820                }
2821            } else {
2822                // In case the row is an object
2823                if (typeof(row) == 'object') {
2824                    row = $(row).getAttribute('data-y');
2825                }
2826    
2827                var data = obj.rows[row].style.height;
2828            }
2829    
2830            return data;
2831        }
2832    
2833        /**
2834         * Get the column title
2835         * 
2836         * @param column - column number (first column is: 0)
2837         * @param title - new column title
2838         */
2839        obj.getHeader = function(column) {
2840            return obj.headers[column].innerText;
2841        }
2842    
2843        /**
2844         * Set the column title
2845         * 
2846         * @param column - column number (first column is: 0)
2847         * @param title - new column title
2848         */
2849        obj.setHeader = function(column, newValue) {
2850            if (obj.headers[column]) {
2851                var oldValue = obj.headers[column].innerText;
2852    
2853                if (! newValue) {
2854                    newValue = prompt(obj.options.text.columnName, oldValue)
2855                }
2856    
2857                if (newValue) {
2858                    obj.headers[column].innerHTML = newValue;
2859                    // Keep the title property
2860                    obj.headers[column].setAttribute('title', newValue);
2861                    // Update title
2862                    obj.options.columns[column].title = newValue;
2863                }
2864    
2865                obj.setHistory({
2866                    action: 'setHeader',
2867                    column: column,
2868                    oldValue: oldValue,
2869                    newValue: newValue
2870                });
2871    
2872                // On change
2873                if (! obj.ignoreEvents) {
2874                    if (typeof(obj.options.onchangeheader) == 'function') {
2875                        obj.options.onchangeheader(el, column, oldValue, newValue);
2876                    }
2877                }
2878            }
2879        }
2880    
2881        /**
2882         * Get the headers
2883         * 
2884         * @param asArray
2885         * @return mixed
2886         */
2887        obj.getHeaders = function (asArray) {
2888            var title = [];
2889    
2890            for (var i = 0; i < obj.headers.length; i++) {
2891                title.push(obj.getHeader(i));
2892            }
2893    
2894            return asArray ? title : title.join(obj.options.csvDelimiter);
2895        }
2896    
2897        /**
2898         * Get meta information from cell(s)
2899         * 
2900         * @return integer
2901         */
2902        obj.getMeta = function(cell, key) {
2903            if (! cell) {
2904                return obj.options.meta;
2905            } else {
2906                if (key) {
2907                    return obj.options.meta[cell] && obj.options.meta[cell][key] ? obj.options.meta[cell][key] : null;
2908                } else {
2909                    return obj.options.meta[cell] ? obj.options.meta[cell] : null;
2910                }
2911            }
2912        }
2913    
2914        /**
2915         * Set meta information to cell(s)
2916         * 
2917         * @return integer
2918         */
2919        obj.setMeta = function(o, k, v) {
2920            if (! obj.options.meta) {
2921                obj.options.meta = {}
2922            }
2923    
2924            if (k && v) {
2925                // Set data value
2926                if (! obj.options.meta[o]) {
2927                    obj.options.meta[o] = {};
2928                }
2929                obj.options.meta[o][k] = v;
2930            } else {
2931                // Apply that for all cells
2932                var keys = Object.keys(o);
2933                for (var i = 0; i < keys.length; i++) {
2934                    if (! obj.options.meta[keys[i]]) {
2935                        obj.options.meta[keys[i]] = {};
2936                    }
2937    
2938                    var prop = Object.keys(o[keys[i]]);
2939                    for (var j = 0; j < prop.length; j++) {
2940                        obj.options.meta[keys[i]][prop[j]] = o[keys[i]][prop[j]];
2941                    }
2942                }
2943            }
2944    
2945            if (obj.ignoreEvents != true) {
2946                if (typeof(obj.options.onchangemeta) == 'function') {
2947                    obj.options.onchangemeta(el, o, k, v);
2948                }
2949            }
2950        }
2951    
2952        /**
2953         * Update meta information
2954         * 
2955         * @return integer
2956         */
2957        obj.updateMeta = function(affectedCells) {
2958            if (obj.options.meta) {
2959                var newMeta = {};
2960                var keys = Object.keys(obj.options.meta);
2961                for (var i = 0; i < keys.length; i++) {
2962                    if (affectedCells[keys[i]]) {
2963                        newMeta[affectedCells[keys[i]]] = obj.options.meta[keys[i]];
2964                    } else {
2965                        newMeta[keys[i]] = obj.options.meta[keys[i]];
2966                    }
2967                }
2968                // Update meta information
2969                obj.options.meta = newMeta;
2970            }
2971        }
2972    
2973        /**
2974         * Get style information from cell(s)
2975         * 
2976         * @return integer
2977         */
2978        obj.getStyle = function(cell, key) {
2979            // Cell
2980            if (! cell) {
2981                // Control vars
2982                var data = {};
2983    
2984                // Column and row length
2985                var x = obj.options.data[0].length;
2986                var y = obj.options.data.length;
2987    
2988                // Go through the columns to get the data
2989                for (var j = 0; j < y; j++) {
2990                    for (var i = 0; i < x; i++) {
2991                        // Value
2992                        var v = key ? obj.records[j][i].style[key] : obj.records[j][i].getAttribute('style');
2993    
2994                        // Any meta data for this column?
2995                        if (v) {
2996                            // Column name
2997                            var k = jexcel.getColumnNameFromId([i, j]);
2998                            // Value
2999                            data[k] = v;
3000                        }
3001                    }
3002                }
3003    
3004               return data;
3005            } else {
3006                cell = jexcel.getIdFromColumnName(cell, true);
3007    
3008                return key ? obj.records[cell[1]][cell[0]].style[key] : obj.records[cell[1]][cell[0]].getAttribute('style');
3009            }
3010        },
3011    
3012        obj.resetStyle = function(o, ignoreHistoryAndEvents) {
3013            var keys = Object.keys(o);
3014            for (var i = 0; i < keys.length; i++) {
3015                // Position
3016                var cell = jexcel.getIdFromColumnName(keys[i], true);
3017                if (obj.records[cell[1]] && obj.records[cell[1]][cell[0]]) {
3018                    obj.records[cell[1]][cell[0]].setAttribute('style', '');
3019                }
3020            }
3021            obj.setStyle(o, null, null, null, ignoreHistoryAndEvents);
3022        }
3023    
3024        /**
3025         * Set meta information to cell(s)
3026         * 
3027         * @return integer
3028         */
3029        obj.setStyle = function(o, k, v, force, ignoreHistoryAndEvents) {
3030            var newValue = {};
3031            var oldValue = {};
3032    
3033            // Apply style
3034            var applyStyle = function(cellId, key, value) {
3035                // Position
3036                var cell = jexcel.getIdFromColumnName(cellId, true);
3037    
3038                if (obj.records[cell[1]] && obj.records[cell[1]][cell[0]]) {
3039                    // Current value
3040                    var currentValue = obj.records[cell[1]][cell[0]].style[key];
3041    
3042                    // Change layout
3043                    if (currentValue == value && ! force) {
3044                        value = '';
3045                        obj.records[cell[1]][cell[0]].style[key] = '';
3046                    } else {
3047                        obj.records[cell[1]][cell[0]].style[key] = value;
3048                    }
3049    
3050                    // History
3051                    if (! oldValue[cellId]) {
3052                        oldValue[cellId] = [];
3053                    }
3054                    if (! newValue[cellId]) {
3055                        newValue[cellId] = [];
3056                    }
3057    
3058                    oldValue[cellId].push([key + ':' + currentValue]);
3059                    newValue[cellId].push([key + ':' + value]);
3060                }
3061            }
3062    
3063            if (k && v) {
3064                // Get object from string
3065                if (typeof(o) == 'string') {
3066                    applyStyle(o, k, v);
3067                } else {
3068                    // Avoid duplications
3069                    var oneApplication = [];
3070                    // Apply that for all cells
3071                    for (var i = 0; i < o.length; i++) {
3072                        var x = o[i].getAttribute('data-x');
3073                        var y = o[i].getAttribute('data-y');
3074                        var cellName = jexcel.getColumnNameFromId([x, y]);
3075                        // This happens when is a merged cell
3076                        if (! oneApplication[cellName]) {
3077                            applyStyle(cellName, k, v);
3078                            oneApplication[cellName] = true;
3079                        }
3080                    }
3081                }
3082            } else {
3083                var keys = Object.keys(o);
3084                for (var i = 0; i < keys.length; i++) {
3085                    var style = o[keys[i]];
3086                    if (typeof(style) == 'string') {
3087                        style = style.split(';');
3088                    }
3089                    for (var j = 0; j < style.length; j++) {
3090                        if (typeof(style[j]) == 'string') {
3091                            style[j] = style[j].split(':');
3092                        }
3093                        // Apply value
3094                        if (style[j][0].trim()) {
3095                            applyStyle(keys[i], style[j][0].trim(), style[j][1]);
3096                        }
3097                    }
3098                }
3099            }
3100    
3101            var keys = Object.keys(oldValue);
3102            for (var i = 0; i < keys.length; i++) {
3103                oldValue[keys[i]] = oldValue[keys[i]].join(';');
3104            }
3105            var keys = Object.keys(newValue);
3106            for (var i = 0; i < keys.length; i++) {
3107                newValue[keys[i]] = newValue[keys[i]].join(';');
3108            }
3109    
3110            if (! ignoreHistoryAndEvents) {
3111                // Keeping history of changes
3112                obj.setHistory({
3113                    action: 'setStyle',
3114                    oldValue: oldValue,
3115                    newValue: newValue,
3116                });
3117            }
3118    
3119            if (obj.ignoreEvents != true) {
3120                if (typeof(obj.options.onchangestyle) == 'function') {
3121                    obj.options.onchangestyle(el, o, k, v);
3122                }
3123            }
3124        }
3125    
3126        /**
3127         * Get cell comments, null cell for all
3128         */
3129        obj.getComments = function(cell, withAuthor) {
3130            if (cell) {
3131                if (typeof(cell) == 'string') {
3132                    var cell = jexcel.getIdFromColumnName(cell, true);
3133                }
3134
3135                if (withAuthor) {
3136                    return [obj.records[cell[1]][cell[0]].getAttribute('title'), obj.records[cell[1]][cell[0]].getAttribute('author')];
3137                } else {
3138                    return obj.records[cell[1]][cell[0]].getAttribute('title') || '';
3139                }
3140            } else {
3141                var data = {};
3142                for (var j = 0; j < obj.options.data.length; j++) {
3143                    for (var i = 0; i < obj.options.columns.length; i++) {
3144                        var comments = obj.records[j][i].getAttribute('title');
3145                        if (comments) {
3146                            var cell = jexcel.getColumnNameFromId([i, j]);
3147                            data[cell] = comments;
3148                        }
3149                    }
3150                }
3151                return data;
3152            }
3153        }
3154
3155        /**
3156         * Set cell comments
3157         */
3158        obj.setComments = function(cellId, comments, author) {
3159            if (typeof(cellId) == 'string') {
3160                var cell = jexcel.getIdFromColumnName(cellId, true);
3161            } else {
3162                var cell = cellId;
3163            }
3164    
3165            // Keep old value
3166            var title = obj.records[cell[1]][cell[0]].getAttribute('title');
3167            var author = obj.records[cell[1]][cell[0]].getAttribute('data-author');
3168            var oldValue = [ title, author ];
3169    
3170            // Set new values
3171            obj.records[cell[1]][cell[0]].setAttribute('title', comments ? comments : '');
3172            obj.records[cell[1]][cell[0]].setAttribute('data-author', author ? author : '');
3173    
3174            // Remove class if there is no comment
3175            if (comments) {
3176                obj.records[cell[1]][cell[0]].classList.add('jexcel_comments');
3177            } else {
3178                obj.records[cell[1]][cell[0]].classList.remove('jexcel_comments');
3179            }
3180    
3181            // Save history
3182            obj.setHistory({
3183                action:'setComments',
3184                column: cellId,
3185                newValue: [ comments, author ],
3186                oldValue: oldValue,
3187            });
3188        }
3189    
3190        /**
3191         * Get table config information
3192         */
3193        obj.getConfig = function() {
3194            var options = obj.options;
3195            options.style = obj.getStyle();
3196            options.mergeCells = obj.getMerge();
3197            options.comments = obj.getComments();
3198    
3199            return options;
3200        }
3201    
3202        /**
3203         * Sort data and reload table
3204         */
3205        obj.orderBy = function(column, order) {
3206            if (column >= 0) {
3207                // Merged cells
3208                if (Object.keys(obj.options.mergeCells).length > 0) {
3209                    if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
3210                        return false;
3211                    } else {
3212                        // Remove merged cells
3213                        obj.destroyMerged();
3214                    }
3215                }
3216    
3217                // Direction
3218                if (order == null) {
3219                    order = obj.headers[column].classList.contains('arrow-down') ? 1 : 0;
3220                } else {
3221                    order = order ? 1 : 0;
3222                }
3223    
3224                // Filter
3225                Array.prototype.orderBy = function(p, o) {
3226                    return this.slice(0).sort(function(a, b) {
3227                        var valueA = a[p] == '' ? '' : Number(a[p]) == a[p] ? Number(a[p]) : a[p].toLowerCase();
3228                        var valueB = b[p] == '' ? '' : Number(b[p]) == b[p] ? Number(b[p]) : b[p].toLowerCase();
3229    
3230                        if (! o) {
3231                            return (valueA == '' && valueB != '') ? 1 : (valueA != '' && valueB == '') ? -1 : (valueA > valueB) ? 1 : (valueA < valueB) ? -1 :  0;
3232                        } else {
3233                            return (valueA == '' && valueB != '') ? 1 : (valueA != '' && valueB == '') ? -1 : (valueA > valueB) ? -1 : (valueA < valueB) ? 1 :  0;
3234                        }
3235                    });
3236                }
3237    
3238                // Test order
3239                var temp = [];
3240                if (obj.options.columns[column].type == 'calendar' ||
3241                    obj.options.columns[column].type == 'checkbox' ||
3242                    obj.options.columns[column].type == 'radio') {
3243                    for (var j = 0; j < obj.options.data.length; j++) {
3244                        temp[j] = [ j, obj.options.data[j][column] ];
3245                    }
3246                } else {
3247                    for (var j = 0; j < obj.options.data.length; j++) {
3248                        temp[j] = [ j, obj.records[j][column].innerHTML ];
3249                    }
3250                }
3251                temp = temp.orderBy(1, order);
3252    
3253                // Save history
3254                var newValue = [];
3255                for (var j = 0; j < temp.length; j++) {
3256                    newValue[j] = temp[j][0];
3257                }
3258    
3259                // Save history
3260                obj.setHistory({
3261                    action: 'orderBy',
3262                    rows: newValue,
3263                    column: column,
3264                    order: order,
3265                });
3266    
3267                // Update order
3268                obj.updateOrderArrow(column, order);
3269                obj.updateOrder(newValue);
3270    
3271                // On sort event
3272                if (obj.ignoreEvents != true) {
3273                    if (typeof(obj.options.onsort) == 'function') {
3274                        obj.options.onsort(el, column, order);
3275                    }
3276                }
3277    
3278                return true;
3279            }
3280        }
3281    
3282        /**
3283         * Update order arrow
3284         */
3285        obj.updateOrderArrow = function(column, order) {
3286            // Remove order
3287            for (var i = 0; i < obj.headers.length; i++) {
3288                obj.headers[i].classList.remove('arrow-up');
3289                obj.headers[i].classList.remove('arrow-down');
3290            }
3291    
3292            // No order specified then toggle order
3293            if (order) {
3294                obj.headers[column].classList.add('arrow-up');
3295            } else {
3296                obj.headers[column].classList.add('arrow-down');
3297            }
3298        }
3299    
3300        /**
3301         * Update rows position
3302         */
3303        obj.updateOrder = function(rows) {
3304            // History
3305            var data = []
3306            for (var j = 0; j < rows.length; j++) {
3307                data[j] = obj.options.data[rows[j]];
3308            }
3309            obj.options.data = data;
3310    
3311            var data = []
3312            for (var j = 0; j < rows.length; j++) {
3313                data[j] = obj.records[rows[j]];
3314            }
3315            obj.records = data;
3316    
3317            var data = []
3318            for (var j = 0; j < rows.length; j++) {
3319                data[j] = obj.rows[rows[j]];
3320            }
3321            obj.rows = data;
3322    
3323            // Update references
3324            obj.updateTableReferences();
3325    
3326            // Redo search
3327            if (obj.searchInput.value) {
3328                obj.search(obj.searchInput.value);
3329            } else {
3330                // Create page
3331                obj.results = null;
3332                obj.pageNumber = 0;
3333    
3334                if (obj.options.pagination > 0) {
3335                    obj.page(0);
3336                } else if (obj.options.lazyLoading == true) {
3337                    obj.loadPage(0);
3338                } else {
3339                    for (var j = 0; j < obj.rows.length; j++) {
3340                        obj.tbody.appendChild(obj.rows[j]);
3341                    }
3342                }
3343            }
3344        }
3345    
3346        /**
3347         * Move row
3348         * 
3349         * @return void
3350         */
3351        obj.moveRow = function(o, d, ignoreDom) {
3352            if (Object.keys(obj.options.mergeCells).length > 0) {
3353                if (o > d) {
3354                    var insertBefore = 1;
3355                } else {
3356                    var insertBefore = 0;
3357                }
3358
3359                if (obj.isRowMerged(o).length || obj.isRowMerged(d, insertBefore).length) {
3360                    if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
3361                        return false;
vendor: 97,692 bytes, lines 3362-5823
3362                    } else {
3363                        obj.destroyMerged();
3364                    }
3365                }
3366            }
3367    
3368            if (obj.options.search == true) {
3369                if (obj.results && obj.results.length != obj.rows.length) {
3370                    if (confirm(obj.options.text.thisActionWillClearYourSearchResultsAreYouSure)) {
3371                        obj.resetSearch();
3372                    } else {
3373                        return false;
3374                    }
3375                }
3376    
3377                obj.results = null;
3378            }
3379    
3380            if (! ignoreDom) {
3381                if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[d]) >= 0) {
3382                    if (o > d) {
3383                        obj.tbody.insertBefore(obj.rows[o], obj.rows[d]);
3384                    } else {
3385                        obj.tbody.insertBefore(obj.rows[o], obj.rows[d].nextSibling);
3386                    }
3387                } else {
3388                    obj.tbody.removeChild(obj.rows[o]);
3389                }
3390            }
3391    
3392            // Place references in the correct position
3393            obj.rows.splice(d, 0, obj.rows.splice(o, 1)[0]);
3394            obj.records.splice(d, 0, obj.records.splice(o, 1)[0]);
3395            obj.options.data.splice(d, 0, obj.options.data.splice(o, 1)[0]);
3396    
3397            // Respect pagination
3398            if (obj.options.pagination > 0 && obj.tbody.children.length != obj.options.pagination) {
3399                obj.page(obj.pageNumber);
3400            }
3401    
3402            // Keeping history of changes
3403            obj.setHistory({
3404                action:'moveRow',
3405                oldValue: o,
3406                newValue: d,
3407            });
3408    
3409            // Update table references
3410            obj.updateTableReferences();
3411    
3412            // Events
3413            if (obj.ignoreEvents != true) {
3414                if (typeof(obj.options.onmoverow) == 'function') {
3415                    obj.options.onmoverow(el, o, d);
3416                }
3417            }
3418        }
3419    
3420        /**
3421         * Insert a new row
3422         * 
3423         * @param mixed - number of blank lines to be insert or a single array with the data of the new row
3424         * @param rowNumber
3425         * @param insertBefore
3426         * @return void
3427         */
3428        obj.insertRow = function(mixed, rowNumber, insertBefore) {
3429            // Configuration
3430            if (obj.options.allowInsertRow == true) {
3431                // Records
3432                var records = [];
3433    
3434                // Data to be insert
3435                var data = [];
3436    
3437                // The insert could be lead by number of rows or the array of data
3438                if (mixed > 0) {
3439                    var numOfRows = mixed;
3440                } else {
3441                    var numOfRows = 1;
3442    
3443                    if (mixed) {
3444                        data = mixed;
3445                    }
3446                }
3447    
3448                // Direction
3449                var insertBefore = insertBefore ? true : false;
3450    
3451                // Current column number
3452                var lastRow = obj.options.data.length - 1;
3453    
3454                if (rowNumber == undefined || rowNumber >= parseInt(lastRow) || rowNumber < 0) {
3455                    rowNumber = lastRow;
3456                }
3457    
3458                // Onbeforeinsertrow
3459                if (typeof(obj.options.onbeforeinsertrow) == 'function') {
3460                    if (! obj.options.onbeforeinsertrow(el, rowNumber, numOfRows, insertBefore)) {
3461                        console.log('onbeforeinsertrow returned false');
3462    
3463                        return false;
3464                    }
3465                }
3466    
3467                // Merged cells
3468                if (Object.keys(obj.options.mergeCells).length > 0) {
3469                    if (obj.isRowMerged(rowNumber, insertBefore).length) {
3470                        if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
3471                            return false;
3472                        } else {
3473                            obj.destroyMerged();
3474                        }
3475                    }
3476                }
3477    
3478                // Clear any search
3479                if (obj.options.search == true) {
3480                    if (obj.results && obj.results.length != obj.rows.length) {
3481                        if (confirm(obj.options.text.thisActionWillClearYourSearchResultsAreYouSure)) {
3482                            obj.resetSearch();
3483                        } else {
3484                            return false;
3485                        }
3486                    }
3487    
3488                    obj.results = null;
3489                }
3490    
3491                // Insertbefore
3492                var rowIndex = (! insertBefore) ? rowNumber + 1 : rowNumber;
3493
3494                // Keep the current data
3495                var currentRecords = obj.records.splice(rowIndex);
3496                var currentData = obj.options.data.splice(rowIndex);
3497                var currentRows = obj.rows.splice(rowIndex);
3498    
3499                // Adding lines
3500                var rowRecords = [];
3501                var rowData = [];
3502                var rowNode = [];
3503
3504                for (var row = rowIndex; row < (numOfRows + rowIndex); row++) {
3505                    // Push data to the data container
3506                    obj.options.data[row] = [];
3507                    for (var col = 0; col < obj.options.columns.length; col++) {
3508                        obj.options.data[row][col]  = data[col] ? data[col] : '';
3509                    }
3510                    // Create row
3511                    var tr = obj.createRow(row, obj.options.data[row]);
3512                    // Append node
3513                    if (currentRows[0]) {
3514                        if (Array.prototype.indexOf.call(obj.tbody.children, currentRows[0]) >= 0) {
3515                            obj.tbody.insertBefore(tr, currentRows[0]);
3516                        }
3517                    } else {
3518                        if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[rowNumber]) >= 0) {
3519                            obj.tbody.appendChild(tr);
3520                        }
3521                    }
3522                    // Record History
3523                    rowRecords.push(obj.records[row]);
3524                    rowData.push(obj.options.data[row]);
3525                    rowNode.push(tr);
3526                }
3527    
3528                // Copy the data back to the main data
3529                Array.prototype.push.apply(obj.records, currentRecords);
3530                Array.prototype.push.apply(obj.options.data, currentData);
3531                Array.prototype.push.apply(obj.rows, currentRows);
3532    
3533                // Respect pagination
3534                if (obj.options.pagination > 0) {
3535                    obj.page(obj.pageNumber);
3536                }
3537    
3538                // Keep history
3539                obj.setHistory({
3540                    action: 'insertRow',
3541                    rowNumber: rowNumber,
3542                    numOfRows: numOfRows,
3543                    insertBefore: insertBefore,
3544                    rowRecords: rowRecords,
3545                    rowData: rowData,
3546                    rowNode: rowNode,
3547                });
3548    
3549                // Remove table references
3550                obj.updateTableReferences();
3551    
3552                // Events
3553                if (obj.ignoreEvents != true) {
3554                    if (typeof(obj.options.oninsertrow) == 'function') {
3555                        obj.options.oninsertrow(el, rowNumber, numOfRows, rowRecords, insertBefore);
3556                    }
3557                }
3558            }
3559        }
3560    
3561        /**
3562         * Delete a row by number
3563         * 
3564         * @param integer rowNumber - row number to be excluded
3565         * @param integer numOfRows - number of lines
3566         * @return void
3567         */
3568        obj.deleteRow = function(rowNumber, numOfRows) {
3569            // Global Configuration
3570            if (obj.options.allowDeleteRow == true) {
3571                if (obj.options.allowDeletingAllRows == true || obj.options.data.length > 1) {
3572                    // Delete row definitions
3573                    if (rowNumber == undefined) {
3574                        var number = obj.getSelectedRows();
3575    
3576                        if (! number[0]) {
3577                            rowNumber = obj.options.data.length - 1;
3578                            numOfRows = 1;
3579                        } else {
3580                            rowNumber = parseInt(number[0].getAttribute('data-y'));
3581                            numOfRows = number.length;
3582                        }
3583                    }
3584    
3585                    // Last column
3586                    var lastRow = obj.options.data.length - 1;
3587    
3588                    if (rowNumber == undefined || rowNumber > lastRow || rowNumber < 0) {
3589                        rowNumber = lastRow;
3590                    }
3591    
3592                    if (! numOfRows) {
3593                        numOfRows = 1;
3594                    }
3595    
3596                    // Do not delete more than the number of recoreds
3597                    if (rowNumber + numOfRows >= obj.options.data.length) {
3598                        numOfRows = obj.options.data.length - rowNumber;
3599                    }
3600    
3601                    // Onbeforedeleterow
3602                    if (typeof(obj.options.onbeforedeleterow) == 'function') {
3603                        if (! obj.options.onbeforedeleterow(el, rowNumber, numOfRows)) {
3604                            console.log('onbeforedeleterow returned false');
3605                            return false;
3606                        }
3607                    }
3608    
3609                    if (parseInt(rowNumber) > -1) {
3610                        // Merged cells
3611                        var mergeExists = false;
3612                        if (Object.keys(obj.options.mergeCells).length > 0) {
3613                            for (var row = rowNumber; row < rowNumber + numOfRows; row++) {
3614                                if (obj.isRowMerged(row, false).length) {
3615                                    mergeExists = true;
3616                                }
3617                            }
3618                        }
3619                        if (mergeExists) {
3620                            if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
3621                                return false;
3622                            } else {
3623                                obj.destroyMerged();
3624                            }
3625                        }
3626    
3627                        // Clear any search
3628                        if (obj.options.search == true) {
3629                            if (obj.results && obj.results.length != obj.rows.length) {
3630                                if (confirm(obj.options.text.thisActionWillClearYourSearchResultsAreYouSure)) {
3631                                    obj.resetSearch();
3632                                } else {
3633                                    return false;
3634                                }
3635                            }
3636    
3637                            obj.results = null;
3638                        }
3639    
3640                        // Remove node
3641                        for (var row = rowNumber; row < rowNumber + numOfRows; row++) {
3642                            if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[row]) >= 0) {
3643                                obj.rows[row].className = '';
3644                                obj.rows[row].parentNode.removeChild(obj.rows[row]);
3645                            }
3646                        }
3647    
3648                        // Remove data
3649                        var rowRecords = obj.records.splice(rowNumber, numOfRows);
3650                        var rowData = obj.options.data.splice(rowNumber, numOfRows);
3651                        var rowNode = obj.rows.splice(rowNumber, numOfRows);
3652    
3653                        // Respect pagination
3654                        if (obj.options.pagination > 0 && obj.tbody.children.length != obj.options.pagination) {
3655                            obj.page(obj.pageNumber);
3656                        }
3657    
3658                        // Remove selection
3659                        obj.conditionalSelectionUpdate(1, rowNumber, (rowNumber + numOfRows) - 1);
3660    
3661                        // Keep history
3662                        obj.setHistory({
3663                            action: 'deleteRow',
3664                            rowNumber: rowNumber,
3665                            numOfRows: numOfRows,
3666                            insertBefore: 1,
3667                            rowRecords: rowRecords,
3668                            rowData: rowData,
3669                            rowNode: rowNode
3670                        });
3671    
3672                        // Remove table references
3673                        obj.updateTableReferences();
3674    
3675                        // Events
3676                        if (obj.ignoreEvents != true) {
3677                            if (typeof(obj.options.ondeleterow) == 'function') {
3678                                obj.options.ondeleterow(el, rowNumber, numOfRows, rowRecords);
3679                            }
3680                        }
3681                    }
3682                } else {
3683                    console.error('JEXCEL. It is not possible to delete the last row');
3684                }
3685            }
3686        }
3687    
3688    
3689        /**
3690         * Move column
3691         * 
3692         * @return void
3693         */
3694        obj.moveColumn = function(o, d) {
3695            if (Object.keys(obj.options.mergeCells).length > 0) {
3696                if (o > d) {
3697                    var insertBefore = 1;
3698                } else {
3699                    var insertBefore = 0;
3700                }
3701
3702                if (obj.isColMerged(o).length || obj.isColMerged(d, insertBefore).length) {
3703                    if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
3704                        return false;
3705                    } else {
3706                        obj.destroyMerged();
3707                    }
3708                }
3709            }
3710    
3711            var o = parseInt(o);
3712            var d = parseInt(d);
3713    
3714            if (o > d) {
3715                obj.headerContainer.insertBefore(obj.headers[o], obj.headers[d]);
3716                obj.colgroupContainer.insertBefore(obj.colgroup[o], obj.colgroup[d]);
3717    
3718                for (var j = 0; j < obj.rows.length; j++) {
3719                    obj.rows[j].insertBefore(obj.records[j][o], obj.records[j][d]);
3720                }
3721            } else {
3722                obj.headerContainer.insertBefore(obj.headers[o], obj.headers[d].nextSibling);
3723                obj.colgroupContainer.insertBefore(obj.colgroup[o], obj.colgroup[d].nextSibling);
3724    
3725                for (var j = 0; j < obj.rows.length; j++) {
3726                    obj.rows[j].insertBefore(obj.records[j][o], obj.records[j][d].nextSibling);
3727                }
3728            }
3729    
3730            obj.options.columns.splice(d, 0, obj.options.columns.splice(o, 1)[0]);
3731            obj.headers.splice(d, 0, obj.headers.splice(o, 1)[0]);
3732            obj.colgroup.splice(d, 0, obj.colgroup.splice(o, 1)[0]);
3733    
3734            for (var j = 0; j < obj.rows.length; j++) {
3735                obj.options.data[j].splice(d, 0, obj.options.data[j].splice(o, 1)[0]);
3736                obj.records[j].splice(d, 0, obj.records[j].splice(o, 1)[0]);
3737            }
3738    
3739            // Keeping history of changes
3740            obj.setHistory({
3741                action:'moveColumn',
3742                oldValue: o,
3743                newValue: d,
3744            });
3745    
3746            // Update table references
3747            obj.updateTableReferences();
3748    
3749            // Events
3750            if (obj.ignoreEvents != true) {
3751                if (typeof(obj.options.onmovecolumn) == 'function') {
3752                    obj.options.onmovecolumn(el, o, d);
3753                }
3754            }
3755        }
3756    
3757    
3758        /**
3759         * Insert a new column
3760         * 
3761         * @param mixed - num of columns to be added or data to be added in one single column
3762         * @param int columnNumber - number of columns to be created
3763         * @param bool insertBefore
3764         * @param object properties - column properties
3765         * @return void
3766         */
3767        obj.insertColumn = function(mixed, columnNumber, insertBefore, properties) {
3768            // Configuration
3769            if (obj.options.allowInsertColumn == true) {
3770                // Records
3771                var records = [];
3772    
3773                // Data to be insert
3774                var data = [];
3775    
3776                // The insert could be lead by number of rows or the array of data
3777                if (mixed > 0) {
3778                    var numOfColumns = mixed;
3779                } else {
3780                    var numOfColumns = 1;
3781    
3782                    if (mixed) {
3783                        data = mixed;
3784                    }
3785                }
3786    
3787                // Direction
3788                var insertBefore = insertBefore ? true : false;
3789    
3790                // Current column number
3791                var lastColumn = obj.options.columns.length - 1;
3792    
3793                // Confirm position
3794                if (columnNumber == undefined || columnNumber >= parseInt(lastColumn) || columnNumber < 0) {
3795                    columnNumber = lastColumn;
3796                }
3797    
3798                // Onbeforeinsertcolumn
3799                if (typeof(obj.options.onbeforeinsertcolumn) == 'function') {
3800                    if (! obj.options.onbeforeinsertcolumn(el, columnNumber, numOfColumns, insertBefore)) {
3801                        console.log('onbeforeinsertcolumn returned false');
3802    
3803                        return false;
3804                    }
3805                }
3806    
3807                // Merged cells
3808                if (Object.keys(obj.options.mergeCells).length > 0) {
3809                    if (obj.isColMerged(columnNumber, insertBefore).length) {
3810                        if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
3811                            return false;
3812                        } else {
3813                            obj.destroyMerged();
3814                        }
3815                    }
3816                }
3817    
3818                // Create default properties
3819                if (! properties) {
3820                    properties = [];
3821                }
3822    
3823                for (var i = 0; i < numOfColumns; i++) {
3824                    if (! properties[i]) {
3825                        properties[i] = { type:'text', source:[], options:[], width:obj.options.defaultColWidth, align:obj.options.defaultColAlign };
3826                    }
3827                }
3828    
3829                // Insert before
3830                var columnIndex = (! insertBefore) ? columnNumber + 1 : columnNumber;
3831                obj.options.columns = jexcel.injectArray(obj.options.columns, columnIndex, properties);
3832    
3833                // Open space in the containers
3834                var currentHeaders = obj.headers.splice(columnIndex);
3835                var currentColgroup = obj.colgroup.splice(columnIndex);
3836    
3837                // History
3838                var historyHeaders = [];
3839                var historyColgroup = [];
3840                var historyRecords = [];
3841                var historyData = [];
3842    
3843                // Add new headers
3844                for (var col = columnIndex; col < (numOfColumns + columnIndex); col++) {
3845                    obj.createCellHeader(col);
3846                    obj.headerContainer.insertBefore(obj.headers[col], obj.headerContainer.children[col+1]);
3847                    obj.colgroupContainer.insertBefore(obj.colgroup[col], obj.colgroupContainer.children[col+1]);
3848    
3849                    historyHeaders.push(obj.headers[col]);
3850                    historyColgroup.push(obj.colgroup[col]);
3851                }
3852    
3853                // Adding visual columns
3854                for (var row = 0; row < obj.options.data.length; row++) {
3855                    // Keep the current data
3856                    var currentData = obj.options.data[row].splice(columnIndex);
3857                    var currentRecord = obj.records[row].splice(columnIndex);
3858    
3859                    // History
3860                    historyData[row] = [];
3861                    historyRecords[row] = [];
3862    
3863                    for (var col = columnIndex; col < (numOfColumns + columnIndex); col++) {
3864                        // New value
3865                        var value = data[row] ? data[row] : '';
3866                        obj.options.data[row][col] = value;
3867                        // New cell
3868                        var td = obj.createCell(col, row, obj.options.data[row][col]);
3869                        obj.records[row][col] = td;
3870                        // Add cell to the row
3871                        if (obj.rows[row]) {
3872                            obj.rows[row].insertBefore(td, obj.rows[row].children[col+1]);
3873                        }
3874    
3875                        // Record History
3876                        historyData[row].push(value);
3877                        historyRecords[row].push(td);
3878                    }
3879    
3880                    // Copy the data back to the main data
3881                    Array.prototype.push.apply(obj.options.data[row], currentData);
3882                    Array.prototype.push.apply(obj.records[row], currentRecord);
3883                }
3884    
3885                Array.prototype.push.apply(obj.headers, currentHeaders);
3886                Array.prototype.push.apply(obj.colgroup, currentColgroup);
3887    
3888                // Adjust nested headers
3889                if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
3890                    // Flexible way to handle nestedheaders
3891                    if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) {
3892                        for (var j = 0; j < obj.options.nestedHeaders.length; j++) {
3893                            var colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan) + numOfColumns;
3894                            obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan = colspan;
3895                            obj.thead.children[j].children[obj.thead.children[j].children.length-1].setAttribute('colspan', colspan);
3896                        }
3897                    } else {
3898                        var colspan = parseInt(obj.options.nestedHeaders[0].colspan) + numOfColumns;
3899                        obj.options.nestedHeaders[0].colspan = colspan;
3900                        obj.thead.children[0].children[obj.thead.children[0].children.length-1].setAttribute('colspan', colspan);
3901                    }
3902                }
3903    
3904                // Keep history
3905                obj.setHistory({
3906                    action: 'insertColumn',
3907                    columnNumber:columnNumber,
3908                    numOfColumns:numOfColumns,
3909                    insertBefore:insertBefore,
3910                    columns:properties,
3911                    headers:historyHeaders,
3912                    colgroup:historyColgroup,
3913                    records:historyRecords,
3914                    data:historyData,
3915                });
3916    
3917                // Remove table references
3918                obj.updateTableReferences();
3919    
3920                // Events
3921                if (obj.ignoreEvents != true) {
3922                    if (typeof(obj.options.oninsertcolumn) == 'function') {
3923                        obj.options.oninsertcolumn(el, columnNumber, numOfColumns, historyRecords, insertBefore);
3924                    }
3925                }
3926            }
3927        }
3928    
3929        /**
3930         * Delete a column by number
3931         * 
3932         * @param integer columnNumber - reference column to be excluded
3933         * @param integer numOfColumns - number of columns to be excluded from the reference column
3934         * @return void
3935         */
3936        obj.deleteColumn = function(columnNumber, numOfColumns) {
3937            // Global Configuration
3938            if (obj.options.allowDeleteColumn == true) {
3939                if (obj.headers.length > 1) {
3940                    // Delete column definitions
3941                    if (columnNumber == undefined) {
3942                        var number = obj.getSelectedColumns(true);
3943    
3944                        if (! number.length) {
3945                            // Remove last column
3946                            columnNumber = obj.headers.length - 1;
3947                            numOfColumns = 1;
3948                        } else {
3949                            // Remove selected
3950                            columnNumber = parseInt(number[0]);
3951                            numOfColumns = parseInt(number.length);
3952                        }
3953                    }
3954    
3955                    // Lasat column
3956                    var lastColumn = obj.options.data[0].length - 1;
3957    
3958                    if (columnNumber == undefined || columnNumber > lastColumn || columnNumber < 0) {
3959                        columnNumber = lastColumn;
3960                    }
3961    
3962                    // Minimum of columns to be delete is 1
3963                    if (! numOfColumns) {
3964                        numOfColumns = 1;
3965                    }
3966    
3967    
3968    
3969                    // Can't delete more than the limit of the table
3970                    if (numOfColumns > obj.options.data[0].length - columnNumber) {
3971                        numOfColumns = obj.options.data[0].length - columnNumber;
3972                    }
3973    
3974                    // onbeforedeletecolumn
3975                    if (typeof(obj.options.onbeforedeletecolumn) == 'function') {
3976                       if (! obj.options.onbeforedeletecolumn(el, columnNumber, numOfColumns)) {
3977                          console.log('onbeforedeletecolumn returned false');
3978                          return false;
3979                       }
3980                    }
3981    
3982                    // Can't remove the last column
3983                    if (parseInt(columnNumber) > -1) {
3984                        // Merged cells
3985                        var mergeExists = false;
3986                        if (Object.keys(obj.options.mergeCells).length > 0) {
3987                            for (var col = columnNumber; col < columnNumber + numOfColumns; col++) {
3988                                if (obj.isColMerged(col, false).length) {
3989                                    mergeExists = true;
3990                                }
3991                            }
3992                        }
3993                        if (mergeExists) {
3994                            if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
3995                                return false;
3996                            } else {
3997                                obj.destroyMerged();
3998                            }
3999                        }
4000    
4001                        // Delete the column properties
4002                        var columns = obj.options.columns.splice(columnNumber, numOfColumns);
4003    
4004                        for (var col = columnNumber; col < columnNumber + numOfColumns; col++) {
4005                            obj.colgroup[col].className = '';
4006                            obj.headers[col].className = '';
4007                            obj.colgroup[col].parentNode.removeChild(obj.colgroup[col]);
4008                            obj.headers[col].parentNode.removeChild(obj.headers[col]);
4009                        }
4010    
4011                        var historyHeaders = obj.headers.splice(columnNumber, numOfColumns);
4012                        var historyColgroup = obj.colgroup.splice(columnNumber, numOfColumns);
4013                        var historyRecords = [];
4014                        var historyData = [];
4015    
4016                        for (var row = 0; row < obj.options.data.length; row++) {
4017                            for (var col = columnNumber; col < columnNumber + numOfColumns; col++) {
4018                                obj.records[row][col].className = '';
4019                                obj.records[row][col].parentNode.removeChild(obj.records[row][col]);
4020                            }
4021                        }
4022    
4023                        // Delete headers
4024                        for (var row = 0; row < obj.options.data.length; row++) {
4025                            // History
4026                            historyData[row] = obj.options.data[row].splice(columnNumber, numOfColumns);
4027                            historyRecords[row] = obj.records[row].splice(columnNumber, numOfColumns);
4028                        }
4029    
4030                        // Remove selection
4031                        obj.conditionalSelectionUpdate(0, columnNumber, (columnNumber + numOfColumns) - 1);
4032    
4033                        // Adjust nested headers
4034                        if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
4035                            // Flexible way to handle nestedheaders
4036                            if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) {
4037                                for (var j = 0; j < obj.options.nestedHeaders.length; j++) {
4038                                    var colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan) - numOfColumns;
4039                                    obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan = colspan;
4040                                    obj.thead.children[j].children[obj.thead.children[j].children.length-1].setAttribute('colspan', colspan);
4041                                }
4042                            } else {
4043                                var colspan = parseInt(obj.options.nestedHeaders[0].colspan) - numOfColumns;
4044                                obj.options.nestedHeaders[0].colspan = colspan;
4045                                obj.thead.children[0].children[obj.thead.children[0].children.length-1].setAttribute('colspan', colspan);
4046                            }
4047                        }
4048    
4049                        // Keeping history of changes
4050                        obj.setHistory({
4051                            action:'deleteColumn',
4052                            columnNumber:columnNumber,
4053                            numOfColumns:numOfColumns,
4054                            insertBefore: 1,
4055                            columns:columns,
4056                            headers:historyHeaders,
4057                            colgroup:historyColgroup,
4058                            records:historyRecords,
4059                            data:historyData,
4060                        });
4061    
4062                        // Update table references
4063                        obj.updateTableReferences();
4064    
4065                        // Delete
4066                        if (obj.ignoreEvents != true) {
4067                            if (typeof(obj.options.ondeletecolumn) == 'function') {
4068                                obj.options.ondeletecolumn(el, columnNumber, numOfColumns, historyRecords);
4069                            }
4070                        }
4071                    }
4072                } else {
4073                    console.error('JEXCEL. It is not possible to delete the last column');
4074                }
4075            }
4076        }
4077    
4078        /**
4079         * Get seleted rows numbers
4080         * 
4081         * @return array
4082         */
4083        obj.getSelectedRows = function(asIds) {
4084            var rows = [];
4085            // Get all selected rows
4086            for (var j = 0; j < obj.rows.length; j++) {
4087                if (obj.rows[j].classList.contains('selected')) {
4088                    if (asIds) {
4089                        rows.push(j);
4090                    } else {
4091                        rows.push(obj.rows[j]);
4092                    }
4093                }
4094            }
4095    
4096            return rows;
4097        },
4098    
4099        /**
4100         * Get seleted column numbers
4101         * 
4102         * @return array
4103         */
4104        obj.getSelectedColumns = function() {
4105            var cols = [];
4106            // Get all selected cols
4107            for (var i = 0; i < obj.headers.length; i++) {
4108                if (obj.headers[i].classList.contains('selected')) {
4109                    cols.push(i);
4110                }
4111            }
4112    
4113            return cols;
4114        }
4115    
4116        /**
4117         * Get highlighted
4118         * 
4119         * @return array
4120         */
4121        obj.getHighlighted = function() {
4122            return obj.highlighted;
4123        }
4124    
4125        /**
4126         * Update cell references
4127         * 
4128         * @return void
4129         */
4130        obj.updateTableReferences = function() {
4131            // Update headers
4132            for (var i = 0; i < obj.headers.length; i++) {
4133                var x = obj.headers[i].getAttribute('data-x');
4134    
4135                if (x != i) {
4136                    // Update coords
4137                    obj.headers[i].setAttribute('data-x', i);
4138                    // Title
4139                    if (! obj.headers[i].getAttribute('title')) {
4140                        obj.headers[i].innerHTML = jexcel.getColumnName(i);
4141                    }
4142                }
4143            }
4144    
4145            // Update all rows
4146            for (var j = 0; j < obj.rows.length; j++) {
4147                if (obj.rows[j]) {
4148                    var y = obj.rows[j].getAttribute('data-y');
4149    
4150                    if (y != j) {
4151                        // Update coords
4152                        obj.rows[j].setAttribute('data-y', j);
4153                        obj.rows[j].children[0].setAttribute('data-y', j);
4154                        // Row number
4155                        obj.rows[j].children[0].innerHTML = j + 1;
4156                    }
4157                }
4158            }
4159    
4160            // Regular cells affected by this change
4161            var affectedTokens = [];
4162            var mergeCellUpdates = [];
4163    
4164            // Update cell
4165            var updatePosition = function(x,y,i,j) {
4166                if (x != i) {
4167                    obj.records[j][i].setAttribute('data-x', i);
4168                }
4169                if (y != j) {
4170                    obj.records[j][i].setAttribute('data-y', j);
4171                }
4172    
4173                // Other updates
4174                if (x != i || y != j) {
4175                    var columnIdFrom = jexcel.getColumnNameFromId([x, y]);
4176                    var columnIdTo = jexcel.getColumnNameFromId([i, j]);
4177                    affectedTokens[columnIdFrom] = columnIdTo;
4178                }
4179            }
4180    
4181            for (var j = 0; j < obj.records.length; j++) {
4182                for (var i = 0; i < obj.records[0].length; i++) {
4183                    if (obj.records[j][i]) {
4184                        // Current values
4185                        var x = obj.records[j][i].getAttribute('data-x');
4186                        var y = obj.records[j][i].getAttribute('data-y');
4187    
4188                        // Update column
4189                        if (obj.records[j][i].getAttribute('data-merged')) {
4190                            var columnIdFrom = jexcel.getColumnNameFromId([x, y]);
4191                            var columnIdTo = jexcel.getColumnNameFromId([i, j]);
4192                            if (mergeCellUpdates[columnIdFrom] == null) {
4193                                if (columnIdFrom == columnIdTo) {
4194                                    mergeCellUpdates[columnIdFrom] = false;
4195                                } else {
4196                                    var totalX = parseInt(i - x);
4197                                    var totalY = parseInt(j - y);
4198                                    mergeCellUpdates[columnIdFrom] = [ columnIdTo, totalX, totalY ];
4199                                }
4200                            }
4201                        } else {
4202                            updatePosition(x,y,i,j);
4203                        }
4204                    }
4205                }
4206            }
4207    
4208            // Update merged if applicable
4209            var keys = Object.keys(mergeCellUpdates);
4210            if (keys.length) {
4211                for (var i = 0; i < keys.length; i++) {
4212                    if (mergeCellUpdates[keys[i]]) {
4213                        var info = jexcel.getIdFromColumnName(keys[i], true)
4214                        var x = info[0];
4215                        var y = info[1];
4216                        updatePosition(x,y,x + mergeCellUpdates[keys[i]][1],y + mergeCellUpdates[keys[i]][2]);
4217    
4218                        var columnIdFrom = keys[i];
4219                        var columnIdTo = mergeCellUpdates[keys[i]][0];
4220                        for (var j = 0; j < obj.options.mergeCells[columnIdFrom][2].length; j++) {
4221                            var x = parseInt(obj.options.mergeCells[columnIdFrom][2][j].getAttribute('data-x'));
4222                            var y = parseInt(obj.options.mergeCells[columnIdFrom][2][j].getAttribute('data-y'));
4223                            obj.options.mergeCells[columnIdFrom][2][j].setAttribute('data-x', x + mergeCellUpdates[keys[i]][1]);
4224                            obj.options.mergeCells[columnIdFrom][2][j].setAttribute('data-y', y + mergeCellUpdates[keys[i]][2]);
4225                        }
4226    
4227                        obj.options.mergeCells[columnIdTo] = obj.options.mergeCells[columnIdFrom];
4228                        delete(obj.options.mergeCells[columnIdFrom]);
4229                    }
4230                }
4231            }
4232    
4233            // Update formulas
4234            obj.updateFormulas(affectedTokens);
4235    
4236            // Update meta data
4237            obj.updateMeta(affectedTokens);
4238    
4239            // Refresh selection
4240            obj.refreshSelection();
4241    
4242            // Update table with custom configuration if applicable
4243            obj.updateTable();
4244        }
4245    
4246        /**
4247         * Custom settings for the cells
4248         */
4249        obj.updateTable = function() {
4250            // Check for spare
4251            if (obj.options.minSpareRows > 0) {
4252                var numBlankRows = 0;
4253                for (var j = obj.rows.length - 1; j >= 0; j--) {
4254                    var test = false;
4255                    for (var i = 0; i < obj.headers.length; i++) {
4256                        if (obj.options.data[j][i]) {
4257                            test = true;
4258                        }
4259                    }
4260                    if (test) {
4261                        break;
4262                    } else {
4263                        numBlankRows++;
4264                    }
4265                }
4266    
4267                if (obj.options.minSpareRows - numBlankRows > 0) {
4268                    obj.insertRow(obj.options.minSpareRows - numBlankRows)
4269                }
4270            }
4271    
4272            if (obj.options.minSpareCols > 0) {
4273                var numBlankCols = 0;
4274                for (var i = obj.headers.length - 1; i >= 0 ; i--) {
4275                    var test = false;
4276                    for (var j = 0; j < obj.rows.length; j++) {
4277                        if (obj.options.data[j][i]) {
4278                            test = true;
4279                        }
4280                    }
4281                    if (test) {
4282                        break;
4283                    } else {
4284                        numBlankCols++;
4285                    }
4286                }
4287    
4288                if (obj.options.minSpareCols - numBlankCols > 0) {
4289                    obj.insertColumn(obj.options.minSpareCols - numBlankCols)
4290                }
4291            }
4292    
4293            // Customizations by the developer
4294            if (typeof(obj.options.updateTable) == 'function') {
4295                if (obj.options.detachForUpdates) {
4296                    el.removeChild(obj.content);
4297                }
4298
4299                for (var j = 0; j < obj.rows.length; j++) {
4300                    for (var i = 0; i < obj.headers.length; i++) {
4301                        obj.options.updateTable(el, obj.records[j][i], i, j, obj.options.data[j][i], obj.records[j][i].innerText, jexcel.getColumnNameFromId([i, j]));
4302                    }
4303                }
4304
4305                if (obj.options.detachForUpdates) {
4306                    el.insertBefore(obj.content, obj.pagination);
4307                }
4308            }
4309    
4310            // Update corner position
4311            setTimeout(function() {
4312                obj.updateCornerPosition();
4313            },0);
4314        }
4315
4316        /**
4317         * Show row
4318         */
4319        obj.showRow = function(rowNumber) {
4320            obj.rows[rowNumber].style.display = '';
4321        }
4322
4323        /**
4324         * Hide row
4325         */
4326        obj.hideRow = function(rowNumber) {
4327            obj.rows[rowNumber].style.display = 'none';
4328        }
4329
4330        /**
4331         * Show column
4332         */
4333        obj.showColumn = function(colNumber) {
4334            obj.headers[colNumber].style.display = '';
4335            obj.colgroup[colNumber].style.display = '';
4336            for (var j = 0; j < obj.options.data.length; j++) {
4337                obj.records[j][colNumber].style.display = '';
4338            }
4339        }
4340
4341        /**
4342         * Hide column
4343         */
4344        obj.hideColumn = function(colNumber) {
4345            obj.headers[colNumber].style.display = 'none';
4346            obj.colgroup[colNumber].style.display = 'none';
4347            for (var j = 0; j < obj.options.data.length; j++) {
4348                obj.records[j][colNumber].style.display = 'none';
4349            }
4350        }
4351
4352        /**
4353         * Show index column
4354         */
4355        obj.showIndex = function() {
4356            obj.table.classList.remove('jexcel_hidden_index');
4357        }
4358    
4359        /**
4360         * Hide index column
4361         */
4362        obj.hideIndex = function() {
4363            obj.table.classList.add('jexcel_hidden_index');
4364        }
4365    
4366        /**
4367         * Update all related cells in the chain
4368         */
4369        var chainLoopProtection = [];
4370    
4371        obj.updateFormulaChain = function(x, y, records) {
4372            var cellId = jexcel.getColumnNameFromId([x, y]);
4373            if (obj.formula[cellId] && obj.formula[cellId].length > 0) {
4374                if (chainLoopProtection[cellId]) {
4375                    obj.records[y][x].innerHTML = '#ERROR';
4376                    obj.formula[cellId] = '';
4377                } else {
4378                    // Protection
4379                    chainLoopProtection[cellId] = true;
4380    
4381                    for (var i = 0; i < obj.formula[cellId].length; i++) {
4382                        var cell = jexcel.getIdFromColumnName(obj.formula[cellId][i], true);
4383                        // Update cell
4384                        var value = ''+obj.options.data[cell[1]][cell[0]];
4385                        if (value.substr(0,1) == '=') {
4386                            records.push(obj.updateCell(cell[0], cell[1], value, true));
4387                        } else {
4388                            // No longer a formula, remove from the chain
4389                            Object.keys(obj.formula)[i] = null;
4390                        }
4391                        obj.updateFormulaChain(cell[0], cell[1], records);
4392                    }
4393                }
4394            }
4395    
4396            chainLoopProtection = [];
4397        }
4398    
4399        /**
4400         * Update formulas
4401         */
4402        obj.updateFormulas = function(referencesToUpdate) {
4403            // Update formulas
4404            for (var j = 0; j < obj.options.data.length; j++) {
4405                for (var i = 0; i < obj.options.data[0].length; i++) {
4406                    var value = '' + obj.options.data[j][i];
4407                    // Is formula
4408                    if (value.substr(0,1) == '=') {
4409                        // Replace tokens
4410                        var newFormula = obj.updateFormula(value, referencesToUpdate);
4411                        if (newFormula != value) {
4412                            obj.options.data[j][i] = newFormula;
4413                        }
4414                    }
4415                }
4416            }
4417    
4418            // Update formula chain
4419            var formula = [];
4420            var keys = Object.keys(obj.formula);
4421            for (var j = 0; j < keys.length; j++) {
4422                // Current key and values
4423                var key = keys[j];
4424                var value = obj.formula[key];
4425                // Update key
4426                if (referencesToUpdate[key]) {
4427                    key = referencesToUpdate[key];
4428                }
4429                // Update values
4430                formula[key] = [];
4431                for (var i = 0; i < value.length; i++) {
4432                    var letter = value[i];
4433                    if (referencesToUpdate[letter]) {
4434                        letter = referencesToUpdate[letter];
4435                    }
4436                    formula[key].push(letter);
4437                }
4438            }
4439            obj.formula = formula;
4440        }
4441    
4442        /**
4443         * Update formula
4444         */
4445        obj.updateFormula = function(formula, referencesToUpdate) {
4446            var testLetter = /[A-Z]/;
4447            var testNumber = /[0-9]/;
4448    
4449            var newFormula = '';
4450            var letter = null;
4451            var number = null;
4452            var token = '';
4453    
4454            for (var index = 0; index < formula.length; index++) {
4455                if (testLetter.exec(formula[index])) {
4456                    letter = 1;
4457                    number = 0;
4458                    token += formula[index];
4459                } else if (testNumber.exec(formula[index])) {
4460                    number = letter ? 1 : 0;
4461                    token += formula[index];
4462                } else {
4463                    if (letter && number) {
4464                        token = referencesToUpdate[token] ? referencesToUpdate[token] : token;
4465                    }
4466                    newFormula += token;
4467                    newFormula += formula[index];
4468                    letter = 0;
4469                    number = 0;
4470                    token = '';
4471                }
4472            }
4473    
4474            if (token) {
4475                if (letter && number) {
4476                    token = referencesToUpdate[token] ? referencesToUpdate[token] : token;
4477                }
4478                newFormula += token;
4479            }
4480    
4481            return newFormula;
4482        }
4483    
4484        /**
4485         * Parse formulas
4486         */
4487        obj.executeFormula = function(expression, x, y) {
4488    
4489            var formulaResults = [];
4490            var formulaLoopProtection = [];
4491    
4492            // Execute formula with loop protection
4493            var execute = function(expression, x, y) {
4494             // Parent column identification
4495                var parentId = jexcel.getColumnNameFromId([x, y]);
4496    
4497                // Code protection
4498                if (formulaLoopProtection[parentId]) {
4499                    console.error('Reference loop detected');
4500                    return '#ERROR';
4501                }
4502    
4503                formulaLoopProtection[parentId] = true;
4504    
4505                // Convert range tokens
4506                var tokensUpdate = function(tokens) {
4507                    for (var index = 0; index < tokens.length; index++) {
4508                        var f = [];
4509                        var token = tokens[index].split(':');
4510                        var e1 = jexcel.getIdFromColumnName(token[0], true);
4511                        var e2 = jexcel.getIdFromColumnName(token[1], true);
4512    
4513                        if (e1[0] <= e2[0]) {
4514                            var x1 = e1[0];
4515                            var x2 = e2[0];
4516                        } else {
4517                            var x1 = e2[0];
4518                            var x2 = e1[0];
4519                        }
4520    
4521                        if (e1[1] <= e2[1]) {
4522                            var y1 = e1[1];
4523                            var y2 = e2[1];
4524                        } else {
4525                            var y1 = e2[1];
4526                            var y2 = e1[1];
4527                        }
4528    
4529                        for (var j = y1; j <= y2; j++) {
4530                            for (var i = x1; i <= x2; i++) {
4531                                f.push(jexcel.getColumnNameFromId([i, j]));
4532                            }
4533                        }
4534    
4535                        expression = expression.replace(tokens[index], f.join(','));
4536                    }
4537                }
4538    
4539                var tokens = expression.match(/([A-Z]+[0-9]+)\:([A-Z]+[0-9]+)/g);
4540                if (tokens && tokens.length) {
4541                    tokensUpdate(tokens);
4542                }
4543    
4544                // String
4545                var evalstring = '';
4546    
4547                // Get tokens
4548                var tokens = expression.match(/([A-Z]+[0-9]+)/g);
4549    
4550                // Direct self-reference protection
4551                if (tokens && tokens.indexOf(parentId) > -1) {
4552                    console.error('Self Reference detected');
4553                    return '#ERROR';
4554                } else {
4555                    if (tokens) {
4556                        for (var i = 0; i < tokens.length; i++) {
4557                            // Keep chain
4558                            if (! obj.formula[tokens[i]]) {
4559                                obj.formula[tokens[i]] = [];
4560                            }
4561                            // Is already in the register
4562                            if (obj.formula[tokens[i]].indexOf(parentId) < 0) {
4563                                obj.formula[tokens[i]].push(parentId);
4564                            }
4565    
4566                            // Do not calculate again
4567                            if (eval('typeof(' + tokens[i] + ') == "undefined"')) {
4568                                // Coords
4569                                var position = jexcel.getIdFromColumnName(tokens[i], 1);
4570                                // Get value
4571                                if (typeof(obj.options.data[position[1]]) != 'undefined' && typeof(obj.options.data[position[1]][position[0]]) != 'undefined') {
4572                                    var value = obj.options.data[position[1]][position[0]];
4573                                } else {
4574                                    var value = '';
4575                                }
4576                                // Get column data
4577                                if ((''+value).substr(0,1) == '=') {
4578                                    if (formulaResults[tokens[i]]) {
4579                                        value = formulaResults[tokens[i]];
4580                                    } else {
4581                                        value = execute(value, position[0], position[1]);
4582                                        formulaResults[tokens[i]] = value;
4583                                    }
4584                                }
4585                                // Type!
4586                                if ((''+value).trim() == '') {
4587                                    // Null
4588                                    evalstring += "var " + tokens[i] + " = null;";
4589                                } else {
4590                                    if (value == Number(value) && obj.options.autoCasting == true) {
4591                                        // Number
4592                                        evalstring += "var " + tokens[i] + " = " + Number(value) + ";";
4593                                    } else {
4594                                        // Trying any formatted number
4595                                        var number = obj.parseNumber(value, position[0])
4596                                        if (obj.options.autoCasting == true && number) {
4597                                            // Render as number
4598                                            evalstring += "var " + tokens[i] + " = " + number + ";";
4599                                        } else {
4600                                            // Render as string
4601                                            evalstring += "var " + tokens[i] + " = '" + value + "';";
4602                                        }
4603                                    }
4604                                }
4605                            }
4606                        }
4607                    }
4608    
4609                    // Convert formula to javascript
4610                    try {
4611                        evalstring += "function COLUMN() { return parseInt(x) + 1; }; function ROW() { return parseInt(y) + 1; }; function CELL() { return parentId; };";
4612    
4613                        var res = eval(evalstring + expression.substr(1));
4614                    } catch (e) {
4615                        var res = '#ERROR';
4616                    }
4617    
4618                    return res;
4619                }
4620            }
4621    
4622            return execute(expression, x, y);
4623        }
4624    
4625        /**
4626         * Trying to extract a number from a string
4627         */
4628        obj.parseNumber = function(value, columnNumber) {
4629            // Decimal point
4630            var decimal = columnNumber && obj.options.columns[columnNumber].decimal ? obj.options.columns[columnNumber].decimal : '.';
4631    
4632            // Parse both parts of the number
4633            var number = ('' + value);
4634            number = number.split(decimal);
4635            number[0] = number[0].match(/[+-]?[0-9]/g);
4636            if (number[0]) {
4637                number[0] = number[0].join('');
4638            }
4639            if (number[1]) {
4640                number[1] = number[1].match(/[0-9]*/g).join('');
4641            }
4642    
4643            // Is a valid number
4644            if (number[0] && Number(number[0]) >= 0) {
4645                if (! number[1]) {
4646                    var value = Number(number[0] + '.00');
4647                } else {
4648                    var value = Number(number[0] + '.' + number[1]);
4649                }
4650            } else {
4651                var value = null;
4652            }
4653    
4654            return value;
4655        }
4656    
4657        /**
4658         * Get row number
4659         */
4660        obj.row = function(cell) {
4661        }
4662    
4663        /**
4664         * Get col number
4665         */
4666        obj.col = function(cell) {
4667        }
4668    
4669        obj.up = function(shiftKey, ctrlKey) {
4670            if (shiftKey) {
4671                if (obj.selectedCell[3] > 0) {
4672                    obj.up.visible(1, ctrlKey ? 0 : 1)
4673                }
4674            } else {
4675                if (obj.selectedCell[1] > 0) {
4676                    obj.up.visible(0, ctrlKey ? 0 : 1)
4677                }
4678                obj.selectedCell[2] = obj.selectedCell[0];
4679                obj.selectedCell[3] = obj.selectedCell[1];
4680            }
4681    
4682            // Update selection
4683            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4684    
4685            // Change page
4686            if (obj.options.lazyLoading == true) {
4687                if (obj.selectedCell[1] == 0 || obj.selectedCell[3] == 0) {
4688                    obj.loadPage(0);
4689                    obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4690                } else {
4691                    if (obj.loadValidation()) {
4692                        obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4693                    } else {
4694                        var item = parseInt(obj.tbody.firstChild.getAttribute('data-y'));
4695                        if (obj.selectedCell[1] - item < 30) {
4696                            obj.loadUp();
4697                            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4698                        }
4699                    }
4700                }
4701            } else if (obj.options.pagination > 0) {
4702                var pageNumber = obj.whichPage(obj.selectedCell[3]);
4703                if (pageNumber != obj.pageNumber) {
4704                    obj.page(pageNumber);
4705                }
4706            }
4707    
4708            obj.updateScroll(1);
4709        }
4710    
4711        obj.up.visible = function(group, direction) {
4712            if (group == 0) {
4713                var x = parseInt(obj.selectedCell[0]);
4714                var y = parseInt(obj.selectedCell[1]);
4715            } else {
4716                var x = parseInt(obj.selectedCell[2]);
4717                var y = parseInt(obj.selectedCell[3]);
4718            }
4719    
4720            if (direction == 0) {
4721                for (var j = 0; j < y; j++) {
4722                    if (obj.records[j][x].style.display != 'none' && obj.rows[j].style.display != 'none') {
4723                        y = j;
4724                        break;
4725                    }
4726                }
4727            } else {
4728                y = obj.up.get(x, y);
4729            }
4730    
4731            if (group == 0) {
4732                obj.selectedCell[0] = x;
4733                obj.selectedCell[1] = y;
4734            } else {
4735                obj.selectedCell[2] = x;
4736                obj.selectedCell[3] = y;
4737            }
4738        }
4739    
4740        obj.up.get = function(x, y) {
4741            var x = parseInt(x);
4742            var y = parseInt(y);
4743            for (var j = (y - 1); j >= 0; j--) {
4744                if (obj.records[j][x].style.display != 'none' && obj.rows[j].style.display != 'none') {
4745                    if (obj.records[j][x].getAttribute('data-merged')) {
4746                        if (obj.records[j][x] == obj.records[y][x]) {
4747                            continue;
4748                        }
4749                    }
4750                    y = j;
4751                    break;
4752                }
4753            }
4754    
4755            return y;
4756        }
4757    
4758        obj.down = function(shiftKey, ctrlKey) {
4759            if (shiftKey) {
4760                if (obj.selectedCell[3] < obj.records.length - 1) {
4761                    obj.down.visible(1, ctrlKey ? 0 : 1)
4762                }
4763            } else {
4764                if (obj.selectedCell[1] < obj.records.length - 1) {
4765                    obj.down.visible(0, ctrlKey ? 0 : 1)
4766                }
4767                obj.selectedCell[2] = obj.selectedCell[0];
4768                obj.selectedCell[3] = obj.selectedCell[1];
4769            }
4770    
4771            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4772    
4773            // Change page
4774            if (obj.options.lazyLoading == true) {
4775                if ((obj.selectedCell[1] == obj.records.length - 1 || obj.selectedCell[3] == obj.records.length - 1)) {
4776                    obj.loadPage(-1);
4777                    obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4778                } else {
4779                    if (obj.loadValidation()) {
4780                        obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4781                    } else {
4782                        var item = parseInt(obj.tbody.lastChild.getAttribute('data-y'));
4783                        if (item - obj.selectedCell[3] < 30) {
4784                            obj.loadDown();
4785                            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4786                        }
4787                    }
4788                }
4789            } else if (obj.options.pagination > 0) {
4790                var pageNumber = obj.whichPage(obj.selectedCell[3]);
4791                if (pageNumber != obj.pageNumber) {
4792                    obj.page(pageNumber);
4793                }
4794            }
4795    
4796            obj.updateScroll(3);
4797        }
4798    
4799        obj.down.visible = function(group, direction) {
4800            if (group == 0) {
4801                var x = parseInt(obj.selectedCell[0]);
4802                var y = parseInt(obj.selectedCell[1]);
4803            } else {
4804                var x = parseInt(obj.selectedCell[2]);
4805                var y = parseInt(obj.selectedCell[3]);
4806            }
4807    
4808            if (direction == 0) {
4809                for (var j = obj.rows.length - 1; j > y; j--) {
4810                    if (obj.records[j][x].style.display != 'none' && obj.rows[j].style.display != 'none') {
4811                        y = j;
4812                        break;
4813                    }
4814                }
4815            } else {
4816                y = obj.down.get(x, y);
4817            }
4818    
4819            if (group == 0) {
4820                obj.selectedCell[0] = x;
4821                obj.selectedCell[1] = y;
4822            } else {
4823                obj.selectedCell[2] = x;
4824                obj.selectedCell[3] = y;
4825            }
4826        }
4827    
4828        obj.down.get = function(x, y) {
4829            var x = parseInt(x);
4830            var y = parseInt(y);
4831            for (var j = (y + 1); j < obj.rows.length; j++) {
4832                if (obj.records[j][x].style.display != 'none' && obj.rows[j].style.display != 'none') {
4833                    if (obj.records[j][x].getAttribute('data-merged')) {
4834                        if (obj.records[j][x] == obj.records[y][x]) {
4835                            continue;
4836                        }
4837                    }
4838                    y = j;
4839                    break;
4840                }
4841            }
4842    
4843            return y;
4844        }
4845    
4846        obj.right = function(shiftKey, ctrlKey) {
4847            if (shiftKey) {
4848                if (obj.selectedCell[2] < obj.headers.length - 1) {
4849                    obj.right.visible(1, ctrlKey ? 0 : 1)
4850                }
4851            } else {
4852                if (obj.selectedCell[0] < obj.headers.length - 1) {
4853                    obj.right.visible(0, ctrlKey ? 0 : 1)
4854                }
4855                obj.selectedCell[2] = obj.selectedCell[0];
4856                obj.selectedCell[3] = obj.selectedCell[1];
4857            }
4858    
4859            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4860            obj.updateScroll(2);
4861        }
4862    
4863        obj.right.visible = function(group, direction) {
4864            if (group == 0) {
4865                var x = parseInt(obj.selectedCell[0]);
4866                var y = parseInt(obj.selectedCell[1]);
4867            } else {
4868                var x = parseInt(obj.selectedCell[2]);
4869                var y = parseInt(obj.selectedCell[3]);
4870            }
4871    
4872            if (direction == 0) {
4873                for (var i = obj.headers.length - 1; i > x; i--) {
4874                    if (obj.records[y][i].style.display != 'none') {
4875                        x = i;
4876                        break;
4877                    }
4878                }
4879            } else {
4880                x = obj.right.get(x, y);
4881            }
4882    
4883            if (group == 0) {
4884                obj.selectedCell[0] = x;
4885                obj.selectedCell[1] = y;
4886            } else {
4887                obj.selectedCell[2] = x;
4888                obj.selectedCell[3] = y;
4889            }
4890        }
4891    
4892        obj.right.get = function(x, y) {
4893            var x = parseInt(x);
4894            var y = parseInt(y);
4895    
4896            for (var i = (x + 1); i < obj.headers.length; i++) {
4897                if (obj.records[y][i].style.display != 'none') {
4898                    if (obj.records[y][i].getAttribute('data-merged')) {
4899                        if (obj.records[y][i] == obj.records[y][x]) {
4900                            continue;
4901                        }
4902                    }
4903                    x = i;
4904                    break;
4905                }
4906            }
4907    
4908            return x;
4909        }
4910    
4911        obj.left = function(shiftKey, ctrlKey) {
4912            if (shiftKey) {
4913                if (obj.selectedCell[2] > 0) {
4914                    obj.left.visible(1, ctrlKey ? 0 : 1)
4915                }
4916            } else {
4917                if (obj.selectedCell[0] > 0) {
4918                    obj.left.visible(0, ctrlKey ? 0 : 1)
4919                }
4920                obj.selectedCell[2] = obj.selectedCell[0];
4921                obj.selectedCell[3] = obj.selectedCell[1];
4922            }
4923    
4924            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
4925            obj.updateScroll(0);
4926        }
4927    
4928        obj.left.visible = function(group, direction) {
4929            if (group == 0) {
4930                var x = parseInt(obj.selectedCell[0]);
4931                var y = parseInt(obj.selectedCell[1]);
4932            } else {
4933                var x = parseInt(obj.selectedCell[2]);
4934                var y = parseInt(obj.selectedCell[3]);
4935            }
4936    
4937            if (direction == 0) {
4938                for (var i = 0; i < x; i++) {
4939                    if (obj.records[y][i].style.display != 'none') {
4940                        x = i;
4941                        break;
4942                    }
4943                }
4944            } else {
4945                x = obj.left.get(x, y);
4946            }
4947    
4948            if (group == 0) {
4949                obj.selectedCell[0] = x;
4950                obj.selectedCell[1] = y;
4951            } else {
4952                obj.selectedCell[2] = x;
4953                obj.selectedCell[3] = y;
4954            }
4955        }
4956    
4957        obj.left.get = function(x, y) {
4958            var x = parseInt(x);
4959            var y = parseInt(y);
4960            for (var i = (x - 1); i >= 0; i--) {
4961                if (obj.records[y][i].style.display != 'none') {
4962                    if (obj.records[y][i].getAttribute('data-merged')) {
4963                        if (obj.records[y][i] == obj.records[y][x]) {
4964                            continue;
4965                        }
4966                    }
4967                    x = i;
4968                    break;
4969                }
4970            }
4971    
4972            return x;
4973        }
4974    
4975        obj.first = function(shiftKey, ctrlKey) {
4976            if (shiftKey) {
4977                if (ctrlKey) {
4978                    obj.selectedCell[3] = 0;
4979                } else {
4980                    obj.left.visible(1, 0);
4981                }
4982            } else {
4983                if (ctrlKey) {
4984                    obj.selectedCell[1] = 0;
4985                } else {
4986                    obj.left.visible(0, 0);
4987                }
4988                obj.selectedCell[2] = obj.selectedCell[0];
4989                obj.selectedCell[3] = obj.selectedCell[1];
4990            }
4991    
4992            // Change page
4993            if (obj.options.lazyLoading == true && (obj.selectedCell[1] == 0 || obj.selectedCell[3] == 0)) {
4994                obj.loadPage(0);
4995            } else if (obj.options.pagination > 0) {
4996                var pageNumber = obj.whichPage(obj.selectedCell[3]);
4997                if (pageNumber != obj.pageNumber) {
4998                    obj.page(pageNumber);
4999                }
5000            }
5001    
5002            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5003            obj.updateScroll(1);
5004        }
5005    
5006        obj.last = function(shiftKey, ctrlKey) {
5007            if (shiftKey) {
5008                if (ctrlKey) {
5009                    obj.selectedCell[3] = obj.records.length - 1;
5010                } else {
5011                    obj.right.visible(1, 0);
5012                }
5013            } else {
5014                if (ctrlKey) {
5015                    obj.selectedCell[1] = obj.records.length - 1;
5016                } else {
5017                    obj.right.visible(0, 0);
5018                }
5019                obj.selectedCell[2] = obj.selectedCell[0];
5020                obj.selectedCell[3] = obj.selectedCell[1];
5021            }
5022    
5023            // Change page
5024            if (obj.options.lazyLoading == true && (obj.selectedCell[1] == obj.records.length - 1 || obj.selectedCell[3] == obj.records.length - 1)) {
5025                obj.loadPage(-1);
5026            } else if (obj.options.pagination > 0) {
5027                var pageNumber = obj.whichPage(obj.selectedCell[3]);
5028                if (pageNumber != obj.pageNumber) {
5029                    obj.page(pageNumber);
5030                }
5031            }
5032    
5033            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5034            obj.updateScroll(3);
5035        }
5036    
5037        obj.selectAll = function() {
5038            if (! obj.selectedCell) {
5039                obj.selectedCell = [];
5040            }
5041    
5042            obj.selectedCell[0] = 0;
5043            obj.selectedCell[1] = 0;
5044            obj.selectedCell[2] = obj.headers.length - 1;
5045            obj.selectedCell[3] = obj.records.length - 1;
5046    
5047            obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5048        }
5049    
5050        /**
5051         * Go to a page in a lazyLoading
5052         */
5053        obj.loadPage = function(pageNumber) {
5054            // Search
5055            if (obj.options.search == true && obj.results) {
5056                var results = obj.results;
5057            } else {
5058                var results = obj.rows;
5059            }
5060    
5061            // Per page
5062            var quantityPerPage = 100;
5063    
5064            // pageNumber
5065            if (pageNumber == null || pageNumber == -1) {
5066                // Last page
5067                pageNumber = Math.ceil(results.length / quantityPerPage); 
5068            }
5069    
5070            var startRow = (pageNumber * quantityPerPage);
5071            var finalRow = (pageNumber * quantityPerPage) + quantityPerPage;
5072            if (finalRow > results.length) {
5073                finalRow = results.length;
5074            }
5075            startRow = finalRow - 100;
5076            if (startRow < 0) {
5077                startRow = 0;
5078            }
5079    
5080            // Appeding items
5081            for (var j = startRow; j < finalRow; j++) {
5082                if (obj.options.search == true && obj.results) {
5083                    obj.tbody.appendChild(obj.rows[results[j]]);
5084                } else {
5085                    obj.tbody.appendChild(obj.rows[j]);
5086                }
5087    
5088                if (obj.tbody.children.length > quantityPerPage) {
5089                    obj.tbody.removeChild(obj.tbody.firstChild);
5090                }
5091            }
5092        }
5093    
5094        obj.loadUp = function() {
5095            // Search
5096            if (obj.options.search == true && obj.results) {
5097                var results = obj.results;
5098            } else {
5099                var results = obj.rows;
5100            }
5101            var test = 0;
5102            if (results.length > 100) {
5103                // Get the first element in the page
5104                var item = parseInt(obj.tbody.firstChild.getAttribute('data-y'));
5105                if (obj.options.search == true && obj.results) {
5106                    item = results.indexOf(item);
5107                }
5108                if (item > 0) {
5109                    for (var j = 0; j < 30; j++) {
5110                        item = item - 1;
5111                        if (item > -1) {
5112                            if (obj.options.search == true && obj.results) {
5113                                obj.tbody.insertBefore(obj.rows[results[item]], obj.tbody.firstChild);
5114                            } else {
5115                                obj.tbody.insertBefore(obj.rows[item], obj.tbody.firstChild);
5116                            }
5117                            if (obj.tbody.children.length > 100) {
5118                                obj.tbody.removeChild(obj.tbody.lastChild);
5119                                test = 1;
5120                            }
5121                        }
5122                    }
5123                }
5124            }
5125            return test;
5126        }
5127    
5128        obj.loadDown = function() {
5129            // Search
5130            if (obj.options.search == true && obj.results) {
5131                var results = obj.results;
5132            } else {
5133                var results = obj.rows;
5134            }
5135            var test = 0;
5136            if (results.length > 100) {
5137                // Get the last element in the page
5138                var item = parseInt(obj.tbody.lastChild.getAttribute('data-y'));
5139                if (obj.options.search == true && obj.results) {
5140                    item = results.indexOf(item);
5141                }
5142                if (item < obj.rows.length - 1) {
5143                    for (var j = 0; j <= 30; j++) {
5144                        if (item < results.length) {
5145                            if (obj.options.search == true && obj.results) {
5146                                obj.tbody.appendChild(obj.rows[results[item]]);
5147                            } else {
5148                                obj.tbody.appendChild(obj.rows[item]);
5149                            }
5150                            if (obj.tbody.children.length > 100) {
5151                                obj.tbody.removeChild(obj.tbody.firstChild);
5152                                test = 1;
5153                            }
5154                        }
5155                        item = item + 1;
5156                    }
5157                }
5158            }
5159    
5160            return test;
5161        }
5162    
5163        obj.loadValidation = function() {
5164            if (obj.selectedCell) {
5165                var currentPage = parseInt(obj.tbody.firstChild.getAttribute('data-y')) / 100;
5166                var selectedPage = parseInt(obj.selectedCell[3] / 100);
5167                var totalPages = parseInt(obj.rows.length / 100);
5168    
5169                if (currentPage != selectedPage && selectedPage <= totalPages) {
5170                    if (! Array.prototype.indexOf.call(obj.tbody.children, obj.rows[obj.selectedCell[3]])) {
5171                        obj.loadPage(selectedPage);
5172                        return true;
5173                    }
5174                }
5175            }
5176    
5177            return false;
5178        }
5179    
5180        /**
5181         * Reset search
5182         */
5183        obj.resetSearch = function() {
5184            obj.searchInput.value = '';
5185            obj.search('');
5186            obj.results = null;
5187        }
5188    
5189        /**
5190         * Search
5191         */
5192        obj.search = function(query) {
5193            // Query
5194            if (query) {
5195                var query = query.toLowerCase();
5196            }
5197    
5198            // Reset selection
5199            obj.resetSelection();
5200    
5201            // Total of results
5202            obj.pageNumber = 0;
5203            obj.results = [];
5204    
5205            if (query) {
5206                // Search filter
5207                var search = function(item, query, index) {
5208                    for (var i = 0; i < item.length; i++) {
5209                        if ((''+item[i]).toLowerCase().search(query) >= 0 ||
5210                            (''+obj.records[index][i].innerHTML).toLowerCase().search(query) >= 0) {
5211                            return true;
5212                        }
5213                    }
5214                    return false;
5215                }
5216    
5217                // Result
5218                var addToResult = function(k) {
5219                    if (obj.results.indexOf(k) == -1) {
5220                        obj.results.push(k);
5221                    }
5222                }
5223    
5224                // Filter
5225                var data = obj.options.data.filter(function(v, k) {
5226                    if (search(v, query, k)) {
5227                        // Merged rows found
5228                        var rows = obj.isRowMerged(k);
5229                        if (rows.length) {
5230                            for (var i = 0; i < rows.length; i++) {
5231                                var row = jexcel.getIdFromColumnName(rows[i], true);
5232                                for (var j = 0; j < obj.options.mergeCells[rows[i]][1]; j++) {
5233                                    addToResult(row[1]+j);
5234                                }
5235                            }
5236                        } else {
5237                            // Normal row found
5238                            addToResult(k);
5239                        }
5240                        return true;
5241                    } else {
5242                        return false;
5243                    }
5244                });
5245            } else {
5246                obj.results = null;
5247            }
5248    
5249            var total = 0;
5250            var index = 0;
5251    
5252            // Page 1
5253            if (obj.options.lazyLoading == true) {
5254                total = 100;
5255            } else if (obj.options.pagination > 0) {
5256                total = obj.options.pagination;
5257            } else {
5258                if (obj.results) {
5259                    total = obj.results.length;
5260                } else {
5261                    total = obj.rows.length;
5262                }
5263            }
5264    
5265            // Reset current nodes
5266            while (obj.tbody.firstChild) {
5267                obj.tbody.removeChild(obj.tbody.firstChild);
5268            }
5269    
5270            // Hide all records from the table
5271            for (var j = 0; j < obj.rows.length; j++) {
5272                if (! obj.results || obj.results.indexOf(j) > -1) {
5273                    if (index < total) {
5274                        obj.tbody.appendChild(obj.rows[j]);
5275                        index++;
5276                    }
5277                    obj.rows[j].style.display = '';
5278                } else {
5279                    obj.rows[j].style.display = 'none';
5280                }
5281            }
5282    
5283            // Update pagination
5284            if (obj.options.pagination > 0) {
5285                obj.updatePagination();
5286            }
5287    
5288            return total;
5289        }
5290    
5291        /**
5292         * Which page the cell is
5293         */
5294        obj.whichPage = function(cell) {
5295            // Search
5296            if (obj.options.search == true && obj.results) {
5297                cell = obj.results.indexOf(cell);
5298            }
5299    
5300            return (Math.ceil((parseInt(cell) + 1) / parseInt(obj.options.pagination))) - 1;
5301        }
5302    
5303        /**
5304         * Go to page
5305         */
5306        obj.page = function(pageNumber) {
5307            var oldPage = obj.pageNumber;
5308
5309            // Search
5310            if (obj.options.search == true && obj.results) {
5311                var results = obj.results;
5312            } else {
5313                var results = obj.rows;
5314            }
5315    
5316            // Per page
5317            var quantityPerPage = parseInt(obj.options.pagination);
5318    
5319            // pageNumber
5320            if (pageNumber == null || pageNumber == -1) {
5321                // Last page
5322                pageNumber = Math.ceil(results.length / quantityPerPage); 
5323            }
5324    
5325            // Page number
5326            obj.pageNumber = pageNumber;
5327    
5328            var startRow = (pageNumber * quantityPerPage);
5329            var finalRow = (pageNumber * quantityPerPage) + quantityPerPage;
5330            if (finalRow > results.length) {
5331                finalRow = results.length;
5332            }
5333            if (startRow < 0) {
5334                startRow = 0;
5335            }
5336    
5337            // Reset container
5338            while (obj.tbody.firstChild) {
5339                obj.tbody.removeChild(obj.tbody.firstChild);
5340            }
5341    
5342            // Appeding items
5343            for (var j = startRow; j < finalRow; j++) {
5344                if (obj.options.search == true && obj.results) {
5345                    obj.tbody.appendChild(obj.rows[results[j]]);
5346                } else {
5347                    obj.tbody.appendChild(obj.rows[j]);
5348                }
5349            }
5350    
5351            if (obj.options.pagination > 0) {
5352                obj.updatePagination();
5353            }
5354    
5355            // Update corner position
5356            obj.updateCornerPosition();
5357
5358            if (typeof(obj.options.onchangepage) == 'function') {
5359                obj.options.onchangepage(el, pageNumber, oldPage);
5360            }
5361        }
5362    
5363        /**
5364         * Update the pagination
5365         */
5366        obj.updatePagination = function() {
5367            // Reset container
5368            obj.pagination.children[0].innerHTML = '';
5369            obj.pagination.children[1].innerHTML = '';
5370    
5371            // Start pagination
5372            if (obj.options.pagination) {
5373                // Searchable
5374                if (obj.options.search == true && obj.results) {
5375                    var results = obj.results.length;
5376                } else {
5377                    var results = obj.rows.length;
5378                }
5379    
5380                if (! results) {
5381                    // No records found
5382                    obj.pagination.children[0].innerHTML = obj.options.text.noRecordsFound;
5383                } else {
5384                    // Pagination container
5385                    var quantyOfPages = Math.ceil(results / obj.options.pagination);
5386    
5387                    if (obj.pageNumber < 6) {
5388                        var startNumber = 1;
5389                        var finalNumber = quantyOfPages < 10 ? quantyOfPages : 10;
5390                    } else if (quantyOfPages - obj.pageNumber < 5) {
5391                        var startNumber = quantyOfPages - 9;
5392                        var finalNumber = quantyOfPages;
5393                        if (startNumber < 1) {
5394                            startNumber = 1;
5395                        }
5396                    } else {
5397                        var startNumber = obj.pageNumber - 4;
5398                        var finalNumber = obj.pageNumber + 5;
5399                    }
5400    
5401                    // First
5402                    if (startNumber > 1) {
5403                        var paginationItem = document.createElement('div');
5404                        paginationItem.className = 'jexcel_page';
5405                        paginationItem.innerHTML = '<';
5406                        paginationItem.title = 1;
5407                        obj.pagination.children[1].appendChild(paginationItem);
5408                    }
5409    
5410                    // Get page links
5411                    for (var i = startNumber; i <= finalNumber; i++) {
5412                        var paginationItem = document.createElement('div');
5413                        paginationItem.className = 'jexcel_page';
5414                        paginationItem.innerHTML = i;
5415                        obj.pagination.children[1].appendChild(paginationItem);
5416    
5417                        if (obj.pageNumber == (i-1)) {
5418                            paginationItem.classList.add('jexcel_page_selected');
5419                        }
5420                    }
5421    
5422                    // Last
5423                    if (finalNumber < quantyOfPages) {
5424                        var paginationItem = document.createElement('div');
5425                        paginationItem.className = 'jexcel_page';
5426                        paginationItem.innerHTML = '>';
5427                        paginationItem.title = quantyOfPages;
5428                        obj.pagination.children[1].appendChild(paginationItem);
5429                    }
5430    
5431                    // Text
5432                    var format = function(format) {
5433                        var args = Array.prototype.slice.call(arguments, 1);
5434                        return format.replace(/{(\d+)}/g, function(match, number) {
5435                          return typeof args[number] != 'undefined'
5436                            ? args[number]
5437                            : match
5438                          ;
5439                        });
5440                    };
5441    
5442                    obj.pagination.children[0].innerHTML = format(obj.options.text.showingPage, obj.pageNumber + 1, quantyOfPages)
5443                }
5444            }
5445        }
5446    
5447        /**
5448         * Download CSV table
5449         * 
5450         * @return null
5451         */
5452        obj.download = function(includeHeaders) {
5453            if (obj.options.allowExport == false) {
5454                console.error('Export not allowed');
5455            } else {
5456                // Data
5457                var data = '';
5458                if (includeHeaders == true || obj.options.includeHeadersOnDownload == true) {
5459                    data += obj.getHeaders();
5460                    data += "\r\n";
5461                }
5462
5463                // Get data
5464                data += obj.copy(false, obj.options.csvDelimiter, true);
5465
5466                // Download element
5467                var blob = new Blob(["\uFEFF"+data], {type: 'text/csv;charset=utf-8;'});
5468
5469                // IE Compatibility
5470                if (window.navigator && window.navigator.msSaveOrOpenBlob) {
5471                    window.navigator.msSaveOrOpenBlob(blob, options.csvFileName + '.csv');
5472                } else {
5473                    // Download element
5474                    var pom = document.createElement('a');
5475                    var url = URL.createObjectURL(blob);
5476                    pom.href = url;
5477                    pom.setAttribute('download', obj.options.csvFileName + '.csv');
5478                    document.body.appendChild(pom);
5479                    pom.click();
5480                    pom.parentNode.removeChild(pom);
5481                }
5482            }
5483        }
5484    
5485        /**
5486         * Initializes a new history record for undo/redo
5487         * 
5488         * @return null
5489         */
5490        obj.setHistory = function(changes) {
5491            if (obj.ignoreHistory != true) {
5492                // Increment and get the current history index
5493                var index = ++obj.historyIndex;
5494    
5495                // Slice the array to discard undone changes
5496                obj.history = (obj.history = obj.history.slice(0, index + 1));
5497    
5498                // Keep history
5499                obj.history[index] = changes;
5500            }
5501        }
5502    
5503        /**
5504         * Copy method
5505         * 
5506         * @param bool highlighted - Get only highlighted cells
5507         * @param delimiter - \t default to keep compatibility with excel
5508         * @return string value
5509         */
5510        obj.copy = function(highlighted, delimiter, returnData) {
5511            if (! delimiter) {
5512                delimiter = "\t";
5513            }
5514    
5515            // Controls
5516            var col = [];
5517            var colLabel = [];
5518            var row = [];
5519            var rowLabel = [];
5520            var x = obj.options.data[0].length
5521            var y = obj.options.data.length
5522            var tmp = '';
5523    
5524            // Reset container
5525            obj.style = [];
5526    
5527            // Go through the columns to get the data
5528            for (var j = 0; j < y; j++) {
5529                col = [];
5530                colLabel = [];
5531    
5532                for (var i = 0; i < x; i++) {
5533                    // If cell is highlighted
5534                    if (! highlighted || obj.records[j][i].classList.contains('highlight')) {
5535                        // Values
5536                        var value = obj.options.data[j][i];
5537                        if (value.match && (value.match(/,/g) || value.match(/\n/) || value.match(/\"/))) {
5538                            value = value.replace(new RegExp('"', 'g'), '""');
5539                            value = '"' + value + '"';
5540                        }
5541                        col.push(value);
5542    
5543                        // Labels
5544                        if (obj.options.columns[i].type == 'checkbox' || obj.options.columns[i].type == 'radio') {
5545                            var label = value;
5546                        } else {
5547                            var label = obj.records[j][i].innerHTML;
5548                            if (label.match && (label.match(/,/g) || label.match(/\n/) || label.match(/\"/))) {
5549                                // Scape double quotes
5550                                label = label.replace(new RegExp('"', 'g'), '""');
5551                                label = '"' + label + '"';
5552                            }
5553                        }
5554                        colLabel.push(label);
5555    
5556                        // Get style
5557                        tmp = obj.records[j][i].getAttribute('style');
5558                        obj.style.push(tmp ? tmp : '');
5559                    }
5560                }
5561    
5562                if (col.length) {
5563                    row.push(col.join(delimiter));
5564                }
5565                if (colLabel.length) {
5566                    rowLabel.push(colLabel.join(delimiter));
5567                }
5568            }
5569    
5570            // Final string
5571            var str = row.join("\r\n");
5572            var strLabel = rowLabel.join("\r\n");
5573
5574            // Create a hidden textarea to copy the values
5575            if (! returnData) {
5576                if (obj.options.copyCompatibility == true) {
5577                    obj.textarea.value = strLabel;
5578                } else {
5579                    obj.textarea.value = str;
5580                }
5581                obj.textarea.select();
5582                document.execCommand("copy");
5583            }
5584    
5585            // Keep data
5586            if (obj.options.copyCompatibility == true) {
5587                obj.data = strLabel;
5588            } else {
5589                obj.data = str;
5590            }
5591            // Keep non visible information
5592            obj.hashString = obj.hash(obj.data);
5593    
5594            return obj.data;
5595        }
5596    
5597        /**
5598         * jExcel paste method
5599         * 
5600         * @param integer row number
5601         * @return string value
5602         */
5603        obj.paste = function(x, y, data) {
5604            // Paste filter
5605            if (typeof(obj.options.onbeforepaste) == 'function') {
5606                var data = obj.options.onbeforepaste(el, data);
5607            }
5608    
5609            // Controls
5610            var hash = obj.hash(data);
5611            var style = (hash == obj.hashString) ? obj.style : null;
5612    
5613            // Depending on the behavior
5614            if (obj.options.copyCompatibility == true && hash == obj.hashString) {
5615                var data = obj.data;
5616            }
5617    
5618            // Split new line
5619            var data = obj.parseCSV(data, "\t");
5620    
5621            if (x != null && y != null && data) {
5622                // Records
5623                var i = 0;
5624                var j = 0;
5625                var records = [];
5626                var newStyle = {};
5627                var oldStyle = {};
5628                var styleIndex = 0;
5629    
5630                // Index
5631                var colIndex = parseInt(x);
5632                var rowIndex = parseInt(y);
5633                var row = null;
5634    
5635                // Go through the columns to get the data
5636                while (row = data[j]) {
5637                    i = 0;
5638                    colIndex = parseInt(x);
5639    
5640                    while (row[i] != null) {
5641                        // Update and keep history
5642                        var record = obj.updateCell(colIndex, rowIndex, row[i]);
5643                        // Keep history
5644                        records.push(record);
5645                        // Update all formulas in the chain
5646                        obj.updateFormulaChain(colIndex, rowIndex, records);
5647                        // Style
5648                        if (style && style[styleIndex]) {
5649                            var columnName = jexcel.getColumnNameFromId([colIndex, rowIndex]);
5650                            newStyle[columnName] = style[styleIndex];
5651                            oldStyle[columnName] = obj.getStyle(columnName);
5652                            obj.records[rowIndex][colIndex].setAttribute('style', style[styleIndex]);
5653                            styleIndex++
5654                        }
5655                        i++;
5656                        if (row[i] != null) {
5657                            if (colIndex >= obj.headers.length - 1) {
5658                                obj.insertColumn();
5659                            }
5660                            colIndex = obj.right.get(colIndex, rowIndex);
5661                        }
5662                    }
5663    
5664                    j++;
5665                    if (data[j]) {
5666                        if (rowIndex >= obj.rows.length-1) {
5667                            obj.insertRow();
5668                        }
5669                        rowIndex = obj.down.get(x, rowIndex);
5670                    }
5671                }
5672    
5673                // Select the new cells
5674                obj.updateSelectionFromCoords(x, y, colIndex, rowIndex);
5675    
5676                // Update history
5677                obj.setHistory({
5678                    action:'setValue',
5679                    records:records,
5680                    selection:obj.selectedCell,
5681                    newStyle:newStyle,
5682                    oldStyle:oldStyle,
5683                });
5684    
5685                // Update table
5686                obj.updateTable();
5687    
5688                // Paste event
5689                if (typeof(obj.options.onpaste) == 'function') {
5690                    obj.options.onpaste(el, records);
5691                }
5692    
5693                // On after changes
5694                obj.onafterchanges(el, records);
5695            }
5696        }
5697    
5698        /**
5699         * Process row
5700         */
5701        obj.historyProcessRow = function(type, historyRecord) {
5702            var rowIndex = (! historyRecord.insertBefore) ? historyRecord.rowNumber + 1 : historyRecord.rowNumber;
5703    
5704            if (obj.options.search == true) {
5705                if (obj.results && obj.results.length != obj.rows.length) {
5706                    obj.resetSearch();
5707                }
5708            }
5709    
5710            // Remove row
5711            if (type == 1) {
5712                var numOfRows = historyRecord.numOfRows;
5713                // Remove nodes
5714                for (var j = rowIndex; j < (numOfRows + rowIndex); j++) {
5715                    obj.rows[j].parentNode.removeChild(obj.rows[j]);
5716                }
5717                // Remove references
5718                obj.records.splice(rowIndex, numOfRows);
5719                obj.options.data.splice(rowIndex, numOfRows);
5720                obj.rows.splice(rowIndex, numOfRows);
5721    
5722                obj.conditionalSelectionUpdate(1, rowIndex, (numOfRows + rowIndex) - 1);
5723            } else {
5724                // Insert data
5725                obj.records = jexcel.injectArray(obj.records, rowIndex, historyRecord.rowRecords);
5726                obj.options.data = jexcel.injectArray(obj.options.data, rowIndex, historyRecord.rowData);
5727                obj.rows = jexcel.injectArray(obj.rows, rowIndex, historyRecord.rowNode);
5728                // Insert nodes
5729                var index = 0
5730                for (var j = rowIndex; j < (historyRecord.numOfRows + rowIndex); j++) {
5731                    obj.tbody.insertBefore(historyRecord.rowNode[index], obj.tbody.children[j]);
5732                    index++;
5733                }
5734            }
5735    
5736            // Respect pagination
5737            if (obj.options.pagination > 0) {
5738                obj.page(obj.pageNumber);
5739            }
5740    
5741            obj.updateTableReferences();
5742        }
5743    
5744        /**
5745         * Process column
5746         */
5747        obj.historyProcessColumn = function(type, historyRecord) {
5748            var columnIndex = (! historyRecord.insertBefore) ? historyRecord.columnNumber + 1 : historyRecord.columnNumber;
5749    
5750            // Remove column
5751            if (type == 1) {
5752                var numOfColumns = historyRecord.numOfColumns;
5753    
5754                obj.options.columns.splice(columnIndex, numOfColumns);
5755                for (var i = columnIndex; i < (numOfColumns + columnIndex); i++) {
5756                    obj.headers[i].parentNode.removeChild(obj.headers[i]);
5757                    obj.colgroup[i].parentNode.removeChild(obj.colgroup[i]);
5758                }
5759                obj.headers.splice(columnIndex, numOfColumns);
5760                obj.colgroup.splice(columnIndex, numOfColumns);
5761                for (var j = 0; j < historyRecord.data.length; j++) {
5762                    for (var i = columnIndex; i < (numOfColumns + columnIndex); i++) {
5763                        obj.records[j][i].parentNode.removeChild(obj.records[j][i]);
5764                    }
5765                    obj.records[j].splice(columnIndex, numOfColumns);
5766                    obj.options.data[j].splice(columnIndex, numOfColumns);
5767                }
5768    
5769                obj.conditionalSelectionUpdate(0, columnIndex, (numOfColumns + columnIndex) - 1);
5770            } else {
5771                // Insert data
5772                obj.options.columns = jexcel.injectArray(obj.options.columns, columnIndex, historyRecord.columns);
5773                obj.headers = jexcel.injectArray(obj.headers, columnIndex, historyRecord.headers);
5774                obj.colgroup = jexcel.injectArray(obj.colgroup, columnIndex, historyRecord.colgroup);
5775    
5776                var index = 0
5777                for (var i = columnIndex; i < (historyRecord.numOfColumns + columnIndex); i++) {
5778                    obj.headerContainer.insertBefore(historyRecord.headers[index], obj.headerContainer.children[i+1]);
5779                    obj.colgroupContainer.insertBefore(historyRecord.colgroup[index], obj.colgroupContainer.children[i+1]);
5780                    index++;
5781                }
5782    
5783                for (var j = 0; j < historyRecord.data.length; j++) {
5784                    obj.options.data[j] = jexcel.injectArray(obj.options.data[j], columnIndex, historyRecord.data[j]);
5785                    obj.records[j] = jexcel.injectArray(obj.records[j], columnIndex, historyRecord.records[j]);
5786                    var index = 0
5787                    for (var i = columnIndex; i < (historyRecord.numOfColumns + columnIndex); i++) {
5788                        obj.rows[j].insertBefore(historyRecord.records[j][index], obj.rows[j].children[i+1]);
5789                        index++;
5790                    }
5791                }
5792            }
5793    
5794            // Adjust nested headers
5795            if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
5796                // Flexible way to handle nestedheaders
5797                if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) {
5798                    for (var j = 0; j < obj.options.nestedHeaders.length; j++) {
5799                        if (type == 1) {
5800                            var colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan) - historyRecord.numOfColumns;
5801                        } else {
5802                            var colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan) + historyRecord.numOfColumns;
5803                        }
5804                        obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan = colspan;
5805                        obj.thead.children[j].children[obj.thead.children[j].children.length-1].setAttribute('colspan', colspan);
5806                    }
5807                } else {
5808                    if (type == 1) {
5809                        var colspan = parseInt(obj.options.nestedHeaders[0].colspan) - historyRecord.numOfColumns;
5810                    } else {
5811                        var colspan = parseInt(obj.options.nestedHeaders[0].colspan) + historyRecord.numOfColumns;
5812                    }
5813                    obj.options.nestedHeaders[0].colspan = colspan;
5814                    obj.thead.children[0].children[obj.thead.children[0].children.length-1].setAttribute('colspan', colspan);
5815                }
5816            }
5817    
5818            obj.updateTableReferences();
5819        }
5820    
5821        /**
5822         * Undo last action
5823         */
5824        obj.undo = function() {
5825            // Ignore events and history
5826            var ignoreEvents = obj.ignoreEvents ? true : false;
5827            var ignoreHistory = obj.ignoreHistory ? true : false;
5828    
5829            obj.ignoreEvents = true;
5830            obj.ignoreHistory = true;
5831    
5832            // Records
5833            var records = [];
5834    
5835            // Update cells
5836            if (obj.historyIndex >= 0) {
5837                // History
5838                var historyRecord = obj.history[obj.historyIndex--];
5839    
5840                if (historyRecord.action == 'insertRow') {
5841                    obj.historyProcessRow(1, historyRecord);
5842                } else if (historyRecord.action == 'deleteRow') {
5843                    obj.historyProcessRow(0, historyRecord);
5844                } else if (historyRecord.action == 'insertColumn') {
5845                    obj.historyProcessColumn(1, historyRecord);
5846                } else if (historyRecord.action == 'deleteColumn') {
5847                    obj.historyProcessColumn(0, historyRecord);
5848                } else if (historyRecord.action == 'moveRow') {
5849                    obj.moveRow(historyRecord.newValue, historyRecord.oldValue);
5850                } else if (historyRecord.action == 'moveColumn') {
5851                    obj.moveColumn(historyRecord.newValue, historyRecord.oldValue);
5852                } else if (historyRecord.action == 'setMerge') {
5853                    obj.removeMerge(historyRecord.column, historyRecord.data);
5854                } else if (historyRecord.action == 'setStyle') {
5855                    obj.setStyle(historyRecord.oldValue, null, null, 1);
5856                } else if (historyRecord.action == 'setWidth') {
5857                    obj.setWidth(historyRecord.column, historyRecord.oldValue);
5858                } else if (historyRecord.action == 'setHeight') {
5859                    obj.setHeight(historyRecord.row, historyRecord.oldValue);
5860                } else if (historyRecord.action == 'setHeader') {
5861                    obj.setHeader(historyRecord.column, historyRecord.oldValue);
5862                } else if (historyRecord.action == 'setComments') {
5863                    obj.setComments(historyRecord.column, historyRecord.oldValue[0], historyRecord.oldValue[1]);
5864                } else if (historyRecord.action == 'orderBy') {
5865                    var rows = [];
5866                    for (var j = 0; j < historyRecord.rows.length; j++) {
5867                        rows[historyRecord.rows[j]] = j;
5868                    }
5869                    obj.updateOrderArrow(historyRecord.column, historyRecord.order ? 0 : 1);
5870                    obj.updateOrder(rows);
5871                } else if (historyRecord.action == 'setValue') {
5872                    // Redo for changes in cells
5873                    for (var i = 0; i < historyRecord.records.length; i++) {
5874                        obj.updateCell(historyRecord.records[i].col, historyRecord.records[i].row, historyRecord.records[i].oldValue);
5875                        obj.updateFormulaChain(historyRecord.records[i].col, historyRecord.records[i].row, records);
5876                        if (historyRecord.oldStyle) {
5877                            obj.resetStyle(historyRecord.oldStyle, true);
5878                        }
5879                    }
5880                    // Update selection
5881                    if (! historyRecord.selection) {
5882                        historyRecord.selection = [historyRecord.records[0].col, historyRecord.records[0].row];
5883                    }
5884                    obj.updateSelectionFromCoords(historyRecord.selection[0], historyRecord.selection[1], historyRecord.selection[2], historyRecord.selection[3]);
5885                    // Update table
5886                    obj.updateTable();
5887                }
5888            }
5889            obj.ignoreEvents = ignoreEvents;
5890            obj.ignoreHistory = ignoreHistory;
5891    
5892            if (typeof(obj.options.onundo) == 'function') {
5893                obj.options.onundo(el, historyRecord);
5894            }
5895        }
5896    
5897        /**
5898         * Redo previously undone action
5899         */
5900        obj.redo = function() {
5901            // Ignore events and history
5902            var ignoreEvents = obj.ignoreEvents ? true : false;
5903            var ignoreHistory = obj.ignoreHistory ? true : false;
5904    
5905            obj.ignoreEvents = true;
5906            obj.ignoreHistory = true;
5907    
5908            // Records
5909            var records = [];
5910    
5911            // Update cells
5912            if (obj.historyIndex < obj.history.length - 1) {
5913                // History
5914                var historyRecord = obj.history[++obj.historyIndex];
5915    
5916                if (historyRecord.action == 'insertRow') {
5917                    obj.historyProcessRow(0, historyRecord);
5918                } else if (historyRecord.action == 'deleteRow') {
5919                    obj.historyProcessRow(1, historyRecord);
5920                } else if (historyRecord.action == 'insertColumn') {
5921                    obj.historyProcessColumn(0, historyRecord);
5922                } else if (historyRecord.action == 'deleteColumn') {
5923                    obj.historyProcessColumn(1, historyRecord);
5924                } else if (historyRecord.action == 'moveRow') {
5925                    obj.moveRow(historyRecord.oldValue, historyRecord.newValue);
5926                } else if (historyRecord.action == 'moveColumn') {
5927                    obj.moveColumn(historyRecord.oldValue, historyRecord.newValue);
5928                } else if (historyRecord.action == 'setMerge') {
5929                    obj.setMerge(historyRecord.column, historyRecord.colspan, historyRecord.rowspan, 1);
5930                } else if (historyRecord.action == 'setStyle') {
5931                    obj.setStyle(historyRecord.newValue, null, null, 1);
5932                } else if (historyRecord.action == 'setWidth') {
5933                    obj.setWidth(historyRecord.column, historyRecord.newValue);
5934                } else if (historyRecord.action == 'setHeight') {
5935                    obj.setHeight(historyRecord.row, historyRecord.newValue);
5936                } else if (historyRecord.action == 'setHeader') {
5937                    obj.setHeader(historyRecord.column, historyRecord.newValue);
5938                } else if (historyRecord.action == 'setComments') {
5939                    obj.setComments(historyRecord.column, historyRecord.newValue[0], historyRecord.newValue[1]);
5940                } else if (historyRecord.action == 'orderBy') {
5941                    obj.updateOrderArrow(historyRecord.column, historyRecord.order);
5942                    obj.updateOrder(historyRecord.rows);
5943                } else if (historyRecord.action == 'setValue') {
5944                    // Redo for changes in cells
5945                    for (var i = 0; i < historyRecord.records.length; i++) {
5946                        obj.updateCell(historyRecord.records[i].col, historyRecord.records[i].row, historyRecord.records[i].newValue);
5947                        obj.updateFormulaChain(historyRecord.records[i].col, historyRecord.records[i].row, records);
5948                        if (historyRecord.newStyle) {
5949                            obj.resetStyle(historyRecord.newStyle, true);
5950                        }
5951                    }
5952    
5953                    // Update selection
5954                    if (! historyRecord.selection) {
5955                        historyRecord.selection = [historyRecord.records[0].col, historyRecord.records[0].row];
5956                    }
5957                    obj.updateSelectionFromCoords(historyRecord.selection[0], historyRecord.selection[1], historyRecord.selection[2], historyRecord.selection[3]);
5958                    // Update table
5959                    obj.updateTable();
5960                }
5961            }
5962            obj.ignoreEvents = ignoreEvents;
5963            obj.ignoreHistory = ignoreHistory;
5964    
5965            if (typeof(obj.options.onredo) == 'function') {
5966                obj.options.onredo(el, historyRecord);
5967            }
5968        }
5969    
5970        /**
5971         * Get dropdown value from key
5972         */
5973        obj.getDropDownValue = function(column, key) {
5974            var value = [];
5975    
5976            if (obj.options.columns[column] && obj.options.columns[column].source) {
5977                // Create array from source
5978                var combo = [];
5979                var source = obj.options.columns[column].source;
5980    
5981                for (var i = 0; i < source.length; i++) {
5982                    if (typeof(source[i]) == 'object') {
5983                        combo[source[i].id] = source[i].name;
5984                    } else {
5985                        combo[source[i]] = source[i];
5986                    }
5987                }
5988    
5989                // Garante single multiple compatibily
5990                var keys = ('' + key).split(';')
5991    
5992                for (var i = 0; i < keys.length; i++) {
5993                    if (combo[keys[i]]) {
5994                        value.push(combo[keys[i]]);
5995                    }
5996                }
5997            } else {
5998                console.error('Invalid column');
5999            }
6000    
6001            return (value.length > 0) ? value.join('; ') : '';
6002        }
6003    
6004        /**
6005         * From starckoverflow contributions
6006         */
6007        obj.parseCSV = function(str, delimiter) {
6008            // Remove last line break
6009            str = str.replace(/\r?\n$|\r$|\n$/g, "");
vendor: 61,180 bytes, lines 6010-7314
6010            // Last caracter is the delimiter
6011            if (str.charCodeAt(str.length-1) == 9) {
6012                str += "\0";
6013            }
6014            // user-supplied delimeter or default comma
6015            delimiter = (delimiter || ",");
6016    
6017            var arr = [];
6018            var quote = false;  // true means we're inside a quoted field
6019            // iterate over each character, keep track of current row and column (of the returned array)
6020            for (var row = 0, col = 0, c = 0; c < str.length; c++) {
6021                var cc = str[c], nc = str[c+1];
6022                arr[row] = arr[row] || [];
6023                arr[row][col] = arr[row][col] || '';
6024    
6025                // If the current character is a quotation mark, and we're inside a quoted field, and the next character is also a quotation mark, add a quotation mark to the current column and skip the next character
6026                if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }  
6027    
6028                // If it's just one quotation mark, begin/end quoted field
6029                if (cc == '"') { quote = !quote; continue; }
6030    
6031                // If it's a comma and we're not in a quoted field, move on to the next column
6032                if (cc == delimiter && !quote) { ++col; continue; }
6033    
6034                // If it's a newline (CRLF) and we're not in a quoted field, skip the next character and move on to the next row and move to column 0 of that new row
6035                if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
6036    
6037                // If it's a newline (LF or CR) and we're not in a quoted field, move on to the next row and move to column 0 of that new row
6038                if (cc == '\n' && !quote) { ++row; col = 0; continue; }
6039                if (cc == '\r' && !quote) { ++row; col = 0; continue; }
6040    
6041                // Otherwise, append the current character to the current column
6042                arr[row][col] += cc;
6043            }
6044            return arr;
6045        }
6046    
6047        obj.hash = function(str) {
6048            var hash = 0, i, chr;
6049    
6050            if (str.length === 0) {
6051                return hash;
6052            } else {
6053                for (i = 0; i < str.length; i++) {
6054                  chr = str.charCodeAt(i);
6055                  hash = ((hash << 5) - hash) + chr;
6056                  hash |= 0;
6057                }
6058            }
6059            return hash;
6060        }
6061    
6062        obj.onafterchanges = function(el, records) {
6063            if (! obj.ignoreEvents) {
6064                // On after changes
6065                if (typeof(obj.options.onafterchanges) == 'function') {
6066                    obj.options.onafterchanges(el, records);
6067                }
6068            }
6069        }
6070    
6071        obj.destroy = function() {
6072            jexcel.destroy(el);
6073        }
6074
6075        /**
6076         * Initialization method
6077         */
6078        obj.init = function() {
6079            jexcel.current = obj;
6080    
6081            // Build handlers
6082            if (typeof(jexcel.build) == 'function') {
6083                jexcel.build();
6084                jexcel.build = null;
6085            }
6086    
6087            // Load the table data based on an CSV file
6088            if (obj.options.csv) {
6089                // Loading
6090                if (obj.options.loadingSpin == true) {
6091                    jSuites.loading.show();
6092                }
6093    
6094                // Load CSV file
6095                jSuites.ajax({
6096                    url: obj.options.csv,
6097                    method: 'GET',
6098                    dataType: 'text',
6099                    success: function(result) {
6100                        // Convert data
6101                        var newData = obj.parseCSV(result, obj.options.csvDelimiter)
6102    
6103                        // Headers
6104                        if (obj.options.csvHeaders == true && newData.length > 0) {
6105                            var headers = newData.shift();
6106                            for(var i = 0; i < headers.length; i++) {
6107                                if (! obj.options.columns[i]) {
6108                                    obj.options.columns[i] = { type:'text', align:obj.options.defaultColAlign, width:obj.options.defaultColWidth };
6109                                }
6110                                // Precedence over pre-configurated titles
6111                                if (typeof obj.options.columns[i].title === 'undefined') {
6112                                  obj.options.columns[i].title = headers[i];
6113                                }
6114                            }
6115                        }
6116                        // Data
6117                        obj.options.data = newData;
6118                        // Prepare table
6119                        obj.prepareTable();
6120                        // Hide spin
6121                        if (obj.options.loadingSpin == true) {
6122                            jSuites.loading.hide();
6123                        }
6124                    }
6125                });
6126            } else if (obj.options.url) {
6127                // Loading
6128                if (obj.options.loadingSpin == true) {
6129                    jSuites.loading.show();
6130                }
6131    
6132                jSuites.ajax({
6133                    url: obj.options.url,
6134                    method: 'GET',
6135                    dataType: 'json',
6136                    success: function(result) {
6137                        // Data
6138                        obj.options.data = (result.data) ? result.data : result;
6139                        // Prepare table
6140                        obj.prepareTable();
6141                        // Hide spin
6142                        if (obj.options.loadingSpin == true) {
6143                            jSuites.loading.hide();
6144                        }
6145                    }
6146                });
6147            } else {
6148                // Prepare table
6149                obj.prepareTable();
6150            }
6151        }
6152    
6153        // Context menu
6154        if (options && options.contextMenu != null) {
6155            obj.options.contextMenu = options.contextMenu;
6156        } else {
6157            obj.options.contextMenu = function(el, x, y, e) {
6158                var items = [];
6159    
6160                if (y == null) {
6161                    // Insert a new column
6162                    if (obj.options.allowInsertColumn == true) {
6163                        items.push({
6164                            title:obj.options.text.insertANewColumnBefore,
6165                            onclick:function() {
6166                                obj.insertColumn(1, parseInt(x), 1);
6167                            }
6168                        });
6169                    }
6170    
6171                    if (obj.options.allowInsertColumn == true) {
6172                        items.push({
6173                            title:obj.options.text.insertANewColumnAfter,
6174                            onclick:function() {
6175                                obj.insertColumn(1, parseInt(x), 0);
6176                            }
6177                        });
6178                    }
6179    
6180                    // Delete a column
6181                    if (obj.options.allowDeleteColumn == true) {
6182                        items.push({
6183                            title:obj.options.text.deleteSelectedColumns,
6184                            onclick:function() {
6185                                obj.deleteColumn(obj.getSelectedColumns().length ? undefined : parseInt(x));
6186                            }
6187                        });
6188                    }
6189    
6190                    // Rename column
6191                    if (obj.options.allowRenameColumn == true) {
6192                        items.push({
6193                            title:obj.options.text.renameThisColumn,
6194                            onclick:function() {
6195                                obj.setHeader(x);
6196                            }
6197                        });
6198                    }
6199    
6200                    // Sorting
6201                    if (obj.options.columnSorting == true) {
6202                        // Line
6203                        items.push({ type:'line' });
6204    
6205                        items.push({
6206                            title:obj.options.text.orderAscending,
6207                            onclick:function() {
6208                                obj.orderBy(x, 0);
6209                            }
6210                        });
6211                        items.push({
6212                            title:obj.options.text.orderDescending,
6213                            onclick:function() {
6214                                obj.orderBy(x, 1);
6215                            }
6216                        });
6217                    }
6218                } else {
6219                    // Insert new row
6220                    if (obj.options.allowInsertRow == true) {
6221                        items.push({
6222                            title:obj.options.text.insertANewRowBefore,
6223                            onclick:function() {
6224                                obj.insertRow(1, parseInt(y), 1);
6225                            }
6226                        });
6227    
6228                        items.push({
6229                            title:obj.options.text.insertANewRowAfter,
6230                            onclick:function() {
6231                                obj.insertRow(1, parseInt(y));
6232                            }
6233                        });
6234                    }
6235    
6236                    if (obj.options.allowDeleteRow == true) {
6237                        items.push({
6238                            title:obj.options.text.deleteSelectedRows,
6239                            onclick:function() {
6240                                obj.deleteRow(obj.getSelectedRows().length ? undefined : parseInt(y));
6241                            }
6242                        });
6243                    }
6244    
6245                    if (x) {
6246                        if (obj.options.allowComments == true) {
6247                            items.push({ type:'line' });
6248    
6249                            var title = obj.records[y][x].getAttribute('title') || '';
6250
6251                            items.push({
6252                                title: title ? obj.options.text.editComments : obj.options.text.addComments,
6253                                onclick:function() {
6254                                    var comment = prompt(obj.options.text.comments, title);
6255                                    if (comment) {
6256                                        obj.setComments([ x, y ], comment);
6257                                    }
6258                                }
6259                            });
6260    
6261                            if (title) {
6262                                items.push({
6263                                    title:obj.options.text.clearComments,
6264                                    onclick:function() {
6265                                        obj.setComments([ x, y ], '');
6266                                    }
6267                                });
6268                            }
6269                        }
6270                    }
6271                }
6272    
6273                // Line
6274                items.push({ type:'line' });
6275    
6276                // Copy
6277                items.push({
6278                    title:obj.options.text.copy,
6279                    shortcut:'Ctrl + C',
6280                    onclick:function() {
6281                        obj.copy(true);
6282                    }
6283                });
6284    
6285                // Paste
6286                if (navigator && navigator.clipboard) {
6287                    items.push({
6288                        title:obj.options.text.paste,
6289                        shortcut:'Ctrl + V',
6290                        onclick:function() {
6291                            if (obj.selectedCell) {
6292                                navigator.clipboard.readText().then(function(text) {
6293                                    if (text) {
6294                                        jexcel.current.paste(obj.selectedCell[0], obj.selectedCell[1], text);
6295                                    }
6296                                });
6297                            }
6298                        }
6299                    });
6300                }
6301    
6302                // Save
6303                if (obj.options.allowExport) {
6304                    items.push({
6305                        title: obj.options.text.saveAs,
6306                        shortcut: 'Ctrl + S',
6307                        onclick: function () {
6308                            obj.download();
6309                        }
6310                    });
6311                }
6312    
6313                // About
6314                if (obj.options.about) {
6315                    items.push({
6316                        title:obj.options.text.about,
6317                        onclick:function() {
6318                            alert(obj.options.about);
6319                        }
6320                    });
6321                }
6322    
6323                return items;
6324            }
6325        }
6326    
6327        obj.scrollControls = function(e) {
6328            if (obj.options.lazyLoading == true) {
6329                if (jexcel.timeControlLoading == null) {
6330                    jexcel.timeControlLoading = setTimeout(function() {
6331                        if (obj.content.scrollTop + obj.content.clientHeight >= obj.content.scrollHeight) {
6332                            if (obj.loadDown()) {
6333                                if (obj.content.scrollTop + obj.content.clientHeight > obj.content.scrollHeight - 10) {
6334                                    obj.content.scrollTop = obj.content.scrollTop - obj.content.clientHeight;
6335                                }
6336                                obj.updateCornerPosition();
6337                            }
6338                        } else if (obj.content.scrollTop <= obj.content.clientHeight) {
6339                            if (obj.loadUp()) {
6340                                if (obj.content.scrollTop < 10) {
6341                                    obj.content.scrollTop = obj.content.scrollTop + obj.content.clientHeight;
6342                                }
6343                                obj.updateCornerPosition();
6344                            }
6345                        }
6346    
6347                        jexcel.timeControlLoading = null;
6348                    }, 100);
6349                }
6350            }
6351    
6352            // Close editor
6353            if (obj.options.lazyLoading == true || obj.options.tableOverflow == true) {
6354                if (obj.edition && e.target.className.substr(0,9) != 'jdropdown') {
6355                    obj.closeEditor(obj.edition[0], true);
6356                }
6357            }
6358        }
6359    
6360        el.addEventListener("DOMMouseScroll", obj.scrollControls);
6361        el.addEventListener("mousewheel", obj.scrollControls);
6362    
6363        el.jexcel = obj;
6364    
6365        obj.init();
6366    
6367        return obj;
6368    });
6369    
6370    jexcel.current = null;
6371    jexcel.timeControl = null;
6372    jexcel.timeControlLoading = null;
6373    
6374    jexcel.destroy = function(element, destroyEventHandlers) {
6375        if (element.jexcel) {
6376            element.removeEventListener("DOMMouseScroll", element.jexcel.scrollControls);
6377            element.removeEventListener("mousewheel", element.jexcel.scrollControls);
6378            element.jexcel = null;
6379            element.innerHTML = '';
6380    
6381            if (destroyEventHandlers) {
6382                document.removeEventListener("keydown", jexcel.keyDownControls);
6383                document.removeEventListener("mouseup", jexcel.mouseUpControls);
6384                document.removeEventListener("mousedown", jexcel.mouseDownControls);
6385                document.removeEventListener("mousemove", jexcel.mouseMoveControls);
6386                document.removeEventListener("mouseover", jexcel.mouseOverControls);
6387                document.removeEventListener("dblclick", jexcel.doubleClickControls);
6388                document.removeEventListener("paste", jexcel.pasteControls);
6389                document.removeEventListener("contextmenu", jexcel.contextMenuControls);
6390                document.removeEventListener("touchstart", jexcel.touchStartControls);
6391                document.removeEventListener("touchend", jexcel.touchEndControls);
6392                document.removeEventListener("touchcancel", jexcel.touchEndControls);
6393                jexcel = null;
6394            }
6395        }
6396    }
6397    
6398    jexcel.build = function() {
6399        document.addEventListener("keydown", jexcel.keyDownControls);
6400        document.addEventListener("mouseup", jexcel.mouseUpControls);
6401        document.addEventListener("mousedown", jexcel.mouseDownControls);
6402        document.addEventListener("mousemove", jexcel.mouseMoveControls);
6403        document.addEventListener("mouseover", jexcel.mouseOverControls);
6404        document.addEventListener("dblclick", jexcel.doubleClickControls);
6405        document.addEventListener("paste", jexcel.pasteControls);
6406        document.addEventListener("contextmenu", jexcel.contextMenuControls);
6407        document.addEventListener("touchstart", jexcel.touchStartControls);
6408        document.addEventListener("touchend", jexcel.touchEndControls);
6409        document.addEventListener("touchcancel", jexcel.touchEndControls);
6410        document.addEventListener("touchmove", jexcel.touchEndControls);
6411    }
6412    
6413    /**
6414     * Events
6415     */
6416    jexcel.keyDownControls = function(e) {
6417        if (jexcel.current) {
6418            if (jexcel.current.edition) {
6419                if (e.which == 27) {
6420                    // Escape
6421                    if (jexcel.current.edition) {
6422                        // Exit without saving
6423                        jexcel.current.closeEditor(jexcel.current.edition[0], false);
6424                    }
6425                    e.preventDefault();
6426                } else if (e.which == 13) {
6427                    // Enter
6428                    if (jexcel.current.options.columns[jexcel.current.edition[2]].type == 'calendar') {
6429                        jexcel.current.editor[0].children[0].calendar.close(1);
6430                    } else if (jexcel.current.options.columns[jexcel.current.edition[2]].type == 'dropdown' ||
6431                               jexcel.current.options.columns[jexcel.current.edition[2]].type == 'autocomplete') {
6432                        // Do nothing
6433                    } else {
6434                        // Alt enter -> do not close editor
6435                        if ((jexcel.current.options.wordWrap == true ||
6436                             jexcel.current.options.columns[jexcel.current.edition[2]].wordWrap == true ||
6437                             jexcel.current.options.data[jexcel.current.edition[3]][jexcel.current.edition[2]].length > 200) && e.altKey) {
6438                            // Add new line to the editor
6439                            var editorTextarea = jexcel.current.edition[0].children[0];
6440                            var editorValue = jexcel.current.edition[0].children[0].value;
6441                            var editorIndexOf = editorTextarea.selectionStart;
6442                            editorValue = editorValue.slice(0, editorIndexOf) + "\n" + editorValue.slice(editorIndexOf);
6443                            editorTextarea.value = editorValue;
6444                            editorTextarea.focus();
6445                            editorTextarea.selectionStart = editorIndexOf + 1;
6446                            editorTextarea.selectionEnd = editorIndexOf + 1;
6447                        } else {
6448                            jexcel.current.edition[0].children[0].blur();
6449                        }
6450                    }
6451                } else if (e.which == 9) {
6452                    // Tab
6453                    if (jexcel.current.options.columns[jexcel.current.edition[2]].type == 'calendar') {
6454                        jexcel.current.edition[0].children[0].calendar.close(1);
6455                    } else {
6456                        jexcel.current.edition[0].children[0].blur();
6457                    }
6458                }
6459            }
6460    
6461            if (! jexcel.current.edition && jexcel.current.selectedCell) {
6462                // Which key
6463                if (e.which == 37) {
6464                    jexcel.current.left(e.shiftKey, e.ctrlKey);
6465                    e.preventDefault();
6466                } else if (e.which == 39) {
6467                    jexcel.current.right(e.shiftKey, e.ctrlKey);
6468                    e.preventDefault();
6469                } else if (e.which == 38) {
6470                    jexcel.current.up(e.shiftKey, e.ctrlKey);
6471                    e.preventDefault();
6472                } else if (e.which == 40) {
6473                    jexcel.current.down(e.shiftKey, e.ctrlKey);
6474                    e.preventDefault();
6475                } else if (e.which == 36) {
6476                    jexcel.current.first(e.shiftKey, e.ctrlKey);
6477                    e.preventDefault();
6478                } else if (e.which == 35) {
6479                    jexcel.current.last(e.shiftKey, e.ctrlKey);
6480                    e.preventDefault();
6481                } else if (e.which == 32) {
6482                    if (jexcel.current.options.editable == true) {
6483                        jexcel.current.setCheckRadioValue();
6484                    }
6485                    e.preventDefault();
6486                } else if (e.which == 46) {
6487                    // Delete
6488                    if (jexcel.current.options.editable == true) {
6489                        if (jexcel.current.selectedRow) {
6490                            if (jexcel.current.options.allowDeleteRow == true) {
6491                                if (confirm(jexcel.current.options.text.areYouSureToDeleteTheSelectedRows)) {
6492                                    jexcel.current.deleteRow();
6493                                }
6494                            }
6495                        } else if (jexcel.current.selectedHeader) {
6496                            if (jexcel.current.options.allowDeleteColumn == true) {
6497                                if (confirm(jexcel.current.options.text.areYouSureToDeleteTheSelectedColumns)) {
6498                                    jexcel.current.deleteColumn();
6499                                }
6500                            }
6501                        } else {
6502                            // Change value
6503                            jexcel.current.setValue(jexcel.current.highlighted, '');
6504                        }
6505                    }
6506                } else if (e.which == 13) {
6507                    // Move cursor
6508                    if (e.shiftKey) {
6509                        jexcel.current.up();
6510                    } else {
6511                        if (jexcel.current.options.allowInsertRow == true) {
6512                            if (jexcel.current.options.allowManualInsertRow == true) {
6513                                if (jexcel.current.selectedCell[1] == jexcel.current.options.data.length - 1) {
6514                                    // New record in case selectedCell in the last row
6515                                    jexcel.current.insertRow();
6516                                }
6517                            }
6518                        }
6519    
6520                        jexcel.current.down();
6521                    }
6522                    e.preventDefault();
6523                } else if (e.which == 9) {
6524                    // Tab
6525                    if (e.shiftKey) {
6526                        jexcel.current.left();
6527                    } else {
6528                        if (jexcel.current.options.allowInsertColumn == true) {
6529                            if (jexcel.current.options.allowManualInsertColumn == true) {
6530                                if (jexcel.current.selectedCell[0] == jexcel.current.options.data[0].length - 1) {
6531                                    // New record in case selectedCell in the last column
6532                                    jexcel.current.insertColumn();
6533                                }
6534                            }
6535                        }
6536    
6537                        jexcel.current.right();
6538                    }
6539                    e.preventDefault();
6540                } else {
6541                    if ((e.ctrlKey || e.metaKey) && ! e.shiftKey) {
6542                        if (e.which == 65) {
6543                            // Ctrl + A
6544                            jexcel.current.selectAll();
6545                            e.preventDefault();
6546                        } else if (e.which == 83) {
6547                            // Ctrl + S
6548                            jexcel.current.download();
6549                            e.preventDefault();
6550                        } else if (e.which == 89) {
6551                            // Ctrl + Y
6552                            jexcel.current.redo();
6553                            e.preventDefault();
6554                        } else if (e.which == 90) {
6555                            // Ctrl + Z
6556                            jexcel.current.undo();
6557                            e.preventDefault();
6558                        } else if (e.which == 67) {
6559                            // Ctrl + C
6560                            jexcel.current.copy(true);
6561                            e.preventDefault();
6562                        } else if (e.which == 67) {
6563                            // Ctrl + C
6564                            jexcel.current.copy(true);
6565                            e.preventDefault();
6566                        } else if (e.which == 88) {
6567                            // Ctrl + X
6568                            if (jexcel.current.options.editable == true) {
6569                                jexcel.cutControls();
6570                            } else {
6571                                jexcel.copyControls();
6572                            }
6573                            e.preventDefault();
6574                        } else if (e.which == 86) {
6575                            // Ctrl + V
6576                            jexcel.pasteControls();
6577                        }
6578                    } else {
6579                        if (jexcel.current.selectedCell) {
6580                            if (jexcel.current.options.editable == true) {
6581                                var rowId = jexcel.current.selectedCell[1];
6582                                var columnId = jexcel.current.selectedCell[0];
6583    
6584                                // If is not readonly
6585                                if (jexcel.current.options.columns[columnId].type != 'readonly') {
6586                                    // Characters able to start a edition
6587                                    if (e.keyCode == 32) {
6588                                        // Space
6589                                        if (jexcel.current.options.columns[columnId].type == 'checkbox' ||
6590                                            jexcel.current.options.columns[columnId].type == 'radio') {
6591                                            e.preventDefault();
6592                                        } else {
6593                                            // Start edition
6594                                            jexcel.current.openEditor(jexcel.current.records[rowId][columnId], true);
6595                                        }
6596                                    } else if (e.keyCode == 113) {
6597                                        // Start edition with current content F2
6598                                        jexcel.current.openEditor(jexcel.current.records[rowId][columnId], false);
6599                                    } else if ((e.keyCode == 8) ||
6600                                               (e.keyCode >= 48 && e.keyCode <= 57) ||
6601                                               (e.keyCode >= 96 && e.keyCode <= 111) ||
6602                                               (e.keyCode == 187) ||
6603                                               (e.keyCode == 189) ||
6604                                               ((String.fromCharCode(e.keyCode) == e.key || String.fromCharCode(e.keyCode).toLowerCase() == e.key.toLowerCase()) && jexcel.validLetter(String.fromCharCode(e.keyCode)))) {
6605                                        // Start edition
6606                                        jexcel.current.openEditor(jexcel.current.records[rowId][columnId], true);
6607                                        // Prevent entries in the calendar
6608                                        if (jexcel.current.options.columns[columnId].type == 'calendar') {
6609                                            e.preventDefault();
6610                                        }
6611                                    }
6612                                }
6613                            }
6614                        }
6615                    }
6616                }
6617            } else {
6618                if (e.target.classList.contains('jexcel_search')) {
6619                    if (jexcel.timeControl) {
6620                        clearTimeout(jexcel.timeControl);
6621                    }
6622    
6623                    jexcel.timeControl = setTimeout(function() {
6624                        jexcel.current.search(e.target.value);
6625                    }, 200);
6626                }
6627            }
6628        }
6629    }
6630    
6631    jexcel.isMouseAction = false;
6632    
6633    jexcel.mouseDownControls = function(e) {
6634        e = e || window.event;
6635        if (e.buttons) {
6636            var mouseButton = e.buttons;
6637        } else if (e.button) {
6638            var mouseButton = e.button;
6639        } else {
6640            var mouseButton = e.which;
6641        }
6642    
6643        // Get elements
6644        var jexcelTable = jexcel.getElement(e.target);
6645    
6646        if (jexcelTable[0]) {
6647            if (jexcel.current != jexcelTable[0].jexcel) {
6648                if (jexcel.current) {
6649                    jexcel.current.resetSelection();
6650                }
6651                jexcel.current = jexcelTable[0].jexcel;
6652            }
6653        } else {
6654            if (jexcel.current) {
6655                jexcel.current.resetSelection(true);
6656                jexcel.current = null;
6657            }
6658        }
6659    
6660        if (jexcel.current && mouseButton == 1) {
6661            if (e.target.classList.contains('jexcel_selectall')) {
6662                if (jexcel.current) {
6663                    jexcel.current.selectAll();
6664                }
6665            } else if (e.target.classList.contains('jexcel_corner')) {
6666                if (jexcel.current.options.editable == true) {
6667                    jexcel.current.selectedCorner = true;
6668                }
6669            } else {
6670                // Header found
6671                if (jexcelTable[1] == 1) {
6672                    var columnId = e.target.getAttribute('data-x');
6673                    if (columnId) {
6674                        // Update cursor
6675                        var info = e.target.getBoundingClientRect();
6676                        if (jexcel.current.options.columnResize == true && info.width - e.offsetX < 6) {
6677                            // Resize helper
6678                            jexcel.current.resizing = {
6679                                mousePosition: e.pageX,
6680                                column: columnId,
6681                                width: info.width,
6682                            };
6683    
6684                            // Border indication
6685                            jexcel.current.headers[columnId].classList.add('resizing');
6686                            for (var j = 0; j < jexcel.current.records.length; j++) {
6687                                if (jexcel.current.records[j][columnId]) {
6688                                    jexcel.current.records[j][columnId].classList.add('resizing');
6689                                }
6690                            }
6691                        } else if (jexcel.current.options.columnDrag == true && info.height - e.offsetY < 6) {
6692                            if (jexcel.current.isColMerged(columnId).length) {
6693                                console.error('JEXCEL: This column is part of a merged cell.');
6694                            } else {
6695                                // Reset selection
6696                                jexcel.current.resetSelection();
6697                                // Drag helper
6698                                jexcel.current.dragging = {
6699                                    element: e.target,
6700                                    column:columnId,
6701                                    destination:columnId,
6702                                };
6703                                // Border indication
6704                                jexcel.current.headers[columnId].classList.add('dragging');
6705                                for (var j = 0; j < jexcel.current.records.length; j++) {
6706                                    if (jexcel.current.records[j][columnId]) {
6707                                        jexcel.current.records[j][columnId].classList.add('dragging');
6708                                    }
6709                                }
6710                            }
6711                        } else {
6712                            if (jexcel.current.selectedHeader && (e.shiftKey || e.ctrlKey)) {
6713                                var o = jexcel.current.selectedHeader;
6714                                var d = columnId;
6715                            } else {
6716                                // Press to rename
6717                                if (jexcel.current.selectedHeader == columnId && jexcel.current.options.allowRenameColumn == true) {
6718                                    jexcel.timeControl = setTimeout(function() {
6719                                        jexcel.current.setHeader(columnId);
6720                                    }, 800);
6721                                }
6722    
6723                                // Keep track of which header was selected first
6724                                jexcel.current.selectedHeader = columnId;
6725    
6726                                // Update selection single column
6727                                var o = columnId;
6728                                var d = columnId;
6729                            }
6730    
6731                            // Update selection
6732                            jexcel.current.updateSelectionFromCoords(o, 0, d, jexcel.current.options.data.length - 1);
6733                        }
6734                    } else {
6735                        if (e.target.parentNode.classList.contains('jexcel_nested')) {
6736                            if (e.target.getAttribute('data-column')) {
6737                                var column = e.target.getAttribute('data-column').split(',');
6738                                var c1 = parseInt(column[0]);
6739                                var c2 = parseInt(column[column.length-1]);
6740                            } else {
6741                                var c1 = 0;
6742                                var c2 = jexcel.current.options.columns.length - 1;
6743                            }
6744                            jexcel.current.updateSelectionFromCoords(c1, 0, c2, jexcel.current.options.data.length - 1);
6745                        }
6746                    }
6747                } else {
6748                    jexcel.current.selectedHeader = false;
6749                }
6750    
6751                // Body found
6752                if (jexcelTable[1] == 2) {
6753                    var rowId = e.target.getAttribute('data-y');
6754    
6755                    if (e.target.classList.contains('jexcel_row')) {
6756                        var info = e.target.getBoundingClientRect();
6757                        if (jexcel.current.options.rowResize == true && info.height - e.offsetY < 6) {
6758                            // Resize helper
6759                            jexcel.current.resizing = {
6760                                element: e.target.parentNode,
6761                                mousePosition: e.pageY,
6762                                row: rowId,
6763                                height: info.height,
6764                            };
6765                            // Border indication
6766                            e.target.parentNode.classList.add('resizing');
6767                        } else if (jexcel.current.options.rowDrag == true && info.width - e.offsetX < 6) {
6768                            if (jexcel.current.isRowMerged(rowId).length) {
6769                                console.error('JEXCEL: This row is part of a merged cell');
6770                            } else if (jexcel.current.options.search == true && jexcel.current.results) {
6771                                console.error('JEXCEL: Please clear your search before perform this action');
6772                            } else {
6773                                // Reset selection
6774                                jexcel.current.resetSelection();
6775                                // Drag helper
6776                                jexcel.current.dragging = {
6777                                    element: e.target.parentNode,
6778                                    row:rowId,
6779                                    destination:rowId,
6780                                };
6781                                // Border indication
6782                                e.target.parentNode.classList.add('dragging');
6783                            }
6784                        } else {
6785                            if (jexcel.current.selectedRow && (e.shiftKey || e.ctrlKey)) {
6786                                var o = jexcel.current.selectedRow;
6787                                var d = rowId;
6788                            } else {
6789                                // Keep track of which header was selected first
6790                                jexcel.current.selectedRow = rowId;
6791    
6792                                // Update selection single column
6793                                var o = rowId;
6794                                var d = rowId;
6795                            }
6796    
6797                            // Update selection
6798                            jexcel.current.updateSelectionFromCoords(0, o, jexcel.current.options.data[0].length - 1, d);
6799                        }
6800                    } else {
6801                        // Jclose
6802                        if (e.target.classList.contains('jclose') && e.target.clientWidth - e.offsetX < 50 && e.offsetY < 50) {
6803                            jexcel.current.closeEditor(jexcel.current.edition[0], true);
6804                        } else {
6805                            var getCellCoords = function(element) {
6806                                var x = element.getAttribute('data-x');
6807                                var y = element.getAttribute('data-y');
6808                                if (x && y) {
6809                                    return [x, y];
6810                                } else {
6811                                    if (element.parentNode) {
6812                                        return getCellCoords(element.parentNode);
6813                                    }
6814                                }
6815                            };
6816    
6817                            var position = getCellCoords(e.target);
6818                            if (position) {
6819                                var columnId = position[0];
6820                                var rowId = position[1];
6821                                // Close edition
6822                                if (jexcel.current.edition) {
6823                                    if (jexcel.current.edition[2] != columnId || jexcel.current.edition[3] != rowId) {
6824                                        jexcel.current.closeEditor(jexcel.current.edition[0], true);
6825                                    }
6826                                }
6827    
6828                                if (! jexcel.current.edition) {
6829                                    // Update cell selection
6830                                    if (e.shiftKey) {
6831                                        jexcel.current.updateSelectionFromCoords(jexcel.current.selectedCell[0], jexcel.current.selectedCell[1], columnId, rowId);
6832                                    } else {
6833                                        jexcel.current.updateSelectionFromCoords(columnId, rowId);
6834                                    }
6835                                }
6836    
6837                                // No full row selected
6838                                jexcel.current.selectedHeader = null;
6839                                jexcel.current.selectedRow = null;
6840                            }
6841                        }
6842                    }
6843                } else {
6844                    jexcel.current.selectedRow = false;
6845                }
6846    
6847                // Pagination
6848                if (e.target.classList.contains('jexcel_page')) {
6849                    if (e.target.innerText == '<') {
6850                        jexcel.current.page(0);
6851                    } else if (e.target.innerText == '>') {
6852                        jexcel.current.page(e.target.getAttribute('title') - 1);
6853                    } else {
6854                        jexcel.current.page(e.target.innerText - 1);
6855                    }
6856                }
6857            }
6858    
6859            if (jexcel.current.edition) {
6860                jexcel.isMouseAction = false;
6861            } else {
6862                jexcel.isMouseAction = true;
6863            }
6864        } else {
6865            jexcel.isMouseAction = false;
6866        }
6867    }
6868    
6869    jexcel.mouseUpControls = function(e) {
6870        if (jexcel.current) {
6871            // Update cell size
6872            if (jexcel.current.resizing) {
6873                // Columns to be updated
6874                if (jexcel.current.resizing.column) {
6875                    // Remove Class
6876                    jexcel.current.headers[jexcel.current.resizing.column].classList.remove('resizing');
6877                    var newWidth = jexcel.current.colgroup[jexcel.current.resizing.column].getAttribute('width');
6878                    jexcel.current.setWidth(jexcel.current.resizing.column, newWidth, jexcel.current.resizing.width);
6879                    // Remove border
6880                    jexcel.current.headers[jexcel.current.resizing.column].classList.remove('resizing');
6881                    for (var j = 0; j < jexcel.current.records.length; j++) {
6882                        if (jexcel.current.records[j][jexcel.current.resizing.column]){
6883                            jexcel.current.records[j][jexcel.current.resizing.column].classList.remove('resizing');
6884                        }
6885                    }
6886                } else {
6887                    // Remove Class
6888                    jexcel.current.rows[jexcel.current.resizing.row].children[0].classList.remove('resizing');
6889                    var newHeight = jexcel.current.rows[jexcel.current.resizing.row].getAttribute('height');
6890                    jexcel.current.setHeight(jexcel.current.resizing.row, newHeight, jexcel.current.resizing.height);
6891                    // Remove border
6892                    jexcel.current.resizing.element.classList.remove('resizing');
6893                }
6894                // Reset resizing helper
6895                jexcel.current.resizing = null;
6896            } else if (jexcel.current.dragging) {
6897                // Reset dragging helper
6898                if (jexcel.current.dragging) {
6899                    if (jexcel.current.dragging.column) {
6900                        // Target
6901                        var columnId = e.target.getAttribute('data-x');
6902                        // Remove move style
6903                        jexcel.current.headers[jexcel.current.dragging.column].classList.remove('dragging');
6904                        for (var j = 0; j < jexcel.current.rows.length; j++) {
6905                            if (jexcel.current.records[j][jexcel.current.dragging.column]) {
6906                                jexcel.current.records[j][jexcel.current.dragging.column].classList.remove('dragging');
6907                            }
6908                        }
6909                        for (var i = 0; i < jexcel.current.headers.length; i++) {
6910                            jexcel.current.headers[i].classList.remove('dragging-left');
6911                            jexcel.current.headers[i].classList.remove('dragging-right');
6912                        }
6913                        // Update position
6914                        if (columnId) {
6915                            if (jexcel.current.dragging.column != jexcel.current.dragging.destination) {
6916                                jexcel.current.moveColumn(jexcel.current.dragging.column, jexcel.current.dragging.destination);
6917                            }
6918                        }
6919                    } else {
6920                        if (jexcel.current.dragging.element.nextSibling) {
6921                            var position = parseInt(jexcel.current.dragging.element.nextSibling.getAttribute('data-y'));
6922                            if (jexcel.current.dragging.row < position) {
6923                                position -= 1;
6924                            }
6925                        } else {
6926                            var position = parseInt(jexcel.current.dragging.element.previousSibling.getAttribute('data-y'));
6927                        }
6928                        if (jexcel.current.dragging.row != position) {
6929                            jexcel.current.moveRow(jexcel.current.dragging.row, position, true);
6930                        }
6931                        jexcel.current.dragging.element.classList.remove('dragging');
6932                    }
6933                    jexcel.current.dragging = null;
6934                }
6935            } else {
6936                // Close any corner selection
6937                if (jexcel.current.selectedCorner) {
6938                    jexcel.current.selectedCorner = false;
6939    
6940                    // Data to be copied
6941                    if (jexcel.current.selection.length > 0) {
6942                        // Copy data
6943                        jexcel.current.copyData(jexcel.current.selection[0], jexcel.current.selection[jexcel.current.selection.length - 1]);
6944    
6945                        // Remove selection
6946                        jexcel.current.removeCopySelection();
6947                    }
6948                }
6949            }
6950        }
6951    
6952        // Clear any time control
6953        if (jexcel.timeControl) {
6954            clearTimeout(jexcel.timeControl);
6955            jexcel.timeControl = null;
6956        }
6957    
6958        // Mouse up
6959        jexcel.isMouseAction = false;
6960    }
6961    
6962    // Mouse move controls
6963    jexcel.mouseMoveControls = function(e) {
6964        e = e || window.event;
6965        if (e.buttons) {
6966            var mouseButton = e.buttons;
6967        } else if (e.button) {
6968            var mouseButton = e.button;
6969        } else {
6970            var mouseButton = e.which;
6971        }
6972    
6973        if (! mouseButton) {
6974            jexcel.isMouseAction = false;
6975        }
6976    
6977        if (jexcel.current) {
6978            if (jexcel.isMouseAction == true) {
6979                // Resizing is ongoing
6980                if (jexcel.current.resizing) {
6981                    if (jexcel.current.resizing.column) {
6982                        var width = e.pageX - jexcel.current.resizing.mousePosition;
6983    
6984                        if (jexcel.current.resizing.width + width > 0) {
6985                            var tempWidth = jexcel.current.resizing.width + width;
6986                            jexcel.current.colgroup[jexcel.current.resizing.column].setAttribute('width', tempWidth);
6987    
6988                            jexcel.current.updateCornerPosition();
6989                        }
6990                    } else {
6991                        var height = e.pageY - jexcel.current.resizing.mousePosition;
6992    
6993                        if (jexcel.current.resizing.height + height > 0) {
6994                            var tempHeight = jexcel.current.resizing.height + height;
6995                            jexcel.current.rows[jexcel.current.resizing.row].setAttribute('height', tempHeight);
6996    
6997                            jexcel.current.updateCornerPosition();
6998                        }
6999                    }
7000                }
7001            } else {
7002                var x = e.target.getAttribute('data-x');
7003                var y = e.target.getAttribute('data-y');
7004                var rect = e.target.getBoundingClientRect();
7005    
7006                if (jexcel.current.cursor) {
7007                    jexcel.current.cursor.style.cursor = '';
7008                    jexcel.current.cursor = null;
7009                }
7010    
7011                if (e.target.parentNode.parentNode && e.target.parentNode.parentNode.className) {
7012                    if (e.target.parentNode.parentNode.classList.contains('resizable')) {
7013                        if (e.target && x && ! y && (rect.width - (e.clientX - rect.left) < 6)) {
7014                            jexcel.current.cursor = e.target;
7015                            jexcel.current.cursor.style.cursor = 'col-resize';
7016                        } else if (e.target && ! x && y && (rect.height - (e.clientY - rect.top) < 6)) {
7017                            jexcel.current.cursor = e.target;
7018                            jexcel.current.cursor.style.cursor = 'row-resize';
7019                        }
7020                    }
7021    
7022                    if (e.target.parentNode.parentNode.classList.contains('draggable')) {
7023                        if (e.target && ! x && y && (rect.width - (e.clientX - rect.left) < 6)) {
7024                            jexcel.current.cursor = e.target;
7025                            jexcel.current.cursor.style.cursor = 'move';
7026                        } else if (e.target && x && ! y && (rect.height - (e.clientY - rect.top) < 6)) {
7027                            jexcel.current.cursor = e.target;
7028                            jexcel.current.cursor.style.cursor = 'move';
7029                        }
7030                    }
7031                }
7032            }
7033        }
7034    }
7035    
7036    jexcel.mouseOverControls = function(e) {
7037        e = e || window.event;
7038        if (e.buttons) {
7039            var mouseButton = e.buttons;
7040        } else if (e.button) {
7041            var mouseButton = e.button;
7042        } else {
7043            var mouseButton = e.which;
7044        }
7045    
7046        if (! mouseButton) {
7047            jexcel.isMouseAction = false;
7048        }
7049    
7050        if (jexcel.current && jexcel.isMouseAction == true) {
7051            // Get elements
7052            var jexcelTable = jexcel.getElement(e.target);
7053    
7054            if (jexcelTable[0]) {
7055                // Avoid cross reference
7056                if (jexcel.current != jexcelTable[0].jexcel) {
7057                    if (jexcel.current) {
7058                        return false;
7059                    }
7060                }
7061    
7062                var columnId = e.target.getAttribute('data-x');
7063                var rowId = e.target.getAttribute('data-y');
7064    
7065                if (jexcel.current.dragging) {
7066                    if (jexcel.current.dragging.column) {
7067                        if (columnId) {
7068                            if (jexcel.current.isColMerged(columnId).length) {
7069                                console.error('JEXCEL: This column is part of a merged cell.');
7070                            } else {
7071                                for (var i = 0; i < jexcel.current.headers.length; i++) {
7072                                    jexcel.current.headers[i].classList.remove('dragging-left');
7073                                    jexcel.current.headers[i].classList.remove('dragging-right');
7074                                }
7075    
7076                                if (jexcel.current.dragging.column == columnId) {
7077                                    jexcel.current.dragging.destination = parseInt(columnId);
7078                                } else {
7079                                    if (e.target.clientWidth / 2 > e.offsetX) {
7080                                        if (jexcel.current.dragging.column < columnId) {
7081                                            jexcel.current.dragging.destination = parseInt(columnId) - 1;
7082                                        } else {
7083                                            jexcel.current.dragging.destination = parseInt(columnId);
7084                                        }
7085                                        jexcel.current.headers[columnId].classList.add('dragging-left');
7086                                    } else {
7087                                        if (jexcel.current.dragging.column < columnId) {
7088                                            jexcel.current.dragging.destination = parseInt(columnId);
7089                                        } else {
7090                                            jexcel.current.dragging.destination = parseInt(columnId) + 1;
7091                                        }
7092                                        jexcel.current.headers[columnId].classList.add('dragging-right');
7093                                    }
7094                                }
7095                            }
7096                        }
7097                    } else {
7098                        if (rowId) {
7099                            if (jexcel.current.isRowMerged(rowId).length) {
7100                                console.error('JEXCEL: This row is part of a merged cell.');
7101                            } else {
7102                                var target = (e.target.clientHeight / 2 > e.offsetY) ? e.target.parentNode.nextSibling : e.target.parentNode;
7103                                e.target.parentNode.parentNode.insertBefore(jexcel.current.dragging.element, target);
7104                            }
7105                        }
7106                    }
7107                } else if (jexcel.current.resizing) {
7108                } else {
7109                    // Header found
7110                    if (jexcelTable[1] == 1) {
7111                        if (jexcel.current.selectedHeader) {
7112                            var columnId = e.target.getAttribute('data-x');
7113                            var o = jexcel.current.selectedHeader;
7114                            var d = columnId;
7115                            // Update selection
7116                            jexcel.current.updateSelectionFromCoords(o, 0, d, jexcel.current.options.data.length - 1);
7117                        }
7118                    }
7119    
7120                    // Body found
7121                    if (jexcelTable[1] == 2) {
7122                        if (e.target.classList.contains('jexcel_row')) {
7123                            if (jexcel.current.selectedRow) {
7124                                var o = jexcel.current.selectedRow;
7125                                var d = rowId;
7126                                // Update selection
7127                                jexcel.current.updateSelectionFromCoords(0, o, jexcel.current.options.data[0].length - 1, d);
7128                            }
7129                        } else {
7130                            // Do not select edtion is in progress
7131                            if (! jexcel.current.edition) {
7132                                if (columnId && rowId) {
7133                                    if (jexcel.current.selectedCorner) {
7134                                        jexcel.current.updateCopySelection(columnId, rowId);
7135                                    } else {
7136                                        if (jexcel.current.selectedCell) {
7137                                            jexcel.current.updateSelectionFromCoords(jexcel.current.selectedCell[0], jexcel.current.selectedCell[1], columnId, rowId);
7138                                        }
7139                                    }
7140                                }
7141                            }
7142                        }
7143                    }
7144                }
7145            }
7146        }
7147    
7148        // Clear any time control
7149        if (jexcel.timeControl) {
7150            clearTimeout(jexcel.timeControl);
7151            jexcel.timeControl = null;
7152        }
7153    }
7154    
7155    /**
7156     * Double click event handler: controls the double click in the corner, cell edition or column re-ordering.
7157     */
7158    jexcel.doubleClickControls = function(e) {
7159        // Jexcel is selected
7160        if (jexcel.current) {
7161            // Corner action
7162            if (e.target.classList.contains('jexcel_corner')) {
7163                // Any selected cells
7164                if (jexcel.current.highlighted.length > 0) {
7165                    // Copy from this
7166                    var x1 = jexcel.current.highlighted[0].getAttribute('data-x');
7167                    var y1 = parseInt(jexcel.current.highlighted[jexcel.current.highlighted.length - 1].getAttribute('data-y')) + 1;
7168                    // Until this
7169                    var x2 = jexcel.current.highlighted[jexcel.current.highlighted.length - 1].getAttribute('data-x');
7170                    var y2 = jexcel.current.records.length - 1
7171                    // Execute copy
7172                    jexcel.current.copyData(jexcel.current.records[y1][x1], jexcel.current.records[y2][x2]);
7173                }
7174            } else {
7175                // Get table
7176                var jexcelTable = jexcel.getElement(e.target);
7177    
7178                // Double click over header
7179                if (jexcelTable[1] == 1 && jexcel.current.options.columnSorting == true) {
7180                    // Check valid column header coords
7181                    var columnId = e.target.getAttribute('data-x');
7182                    if (columnId) {
7183                        jexcel.current.orderBy(columnId);
7184                    }
7185                }
7186    
7187                // Double click over body
7188                if (jexcelTable[1] == 2 && jexcel.current.options.editable == true) {
7189                    if (! jexcel.current.edition) {
7190                        var getCellCoords = function(element) {
7191                            if (element.parentNode) {
7192                                var x = element.getAttribute('data-x');
7193                                var y = element.getAttribute('data-y');
7194                                if (x && y) {
7195                                    return element;
7196                                } else {
7197                                    return getCellCoords(element.parentNode);
7198                                }
7199                            }
7200                        }
7201                        var cell = getCellCoords(e.target);
7202                        if (cell && cell.classList.contains('highlight')) {
7203                            jexcel.current.openEditor(cell);
7204                        }
7205                    }
7206                }
7207            }
7208        }
7209    }
7210    
7211    jexcel.copyControls = function(e) {
7212        if (jexcel.current && jexcel.copyControls.enabled) {
7213            if (! jexcel.current.edition) {
7214                jexcel.current.copy(true);
7215            }
7216        }
7217    }
7218    
7219    jexcel.cutControls = function(e) {
7220        if (jexcel.current) {
7221            if (! jexcel.current.edition) {
7222                jexcel.current.copy(true);
7223                if (jexcel.current.options.editable == true) {
7224                    jexcel.current.setValue(jexcel.current.highlighted, '');
7225                }
7226            }
7227        }
7228    }
7229    
7230    jexcel.pasteControls = function(e) {
7231        if (jexcel.current && jexcel.current.selectedCell) {
7232            if (! jexcel.current.edition) {
7233                if (jexcel.current.options.editable == true) {
7234                    if (e && e.clipboardData) {
7235                        jexcel.current.paste(jexcel.current.selectedCell[0], jexcel.current.selectedCell[1], e.clipboardData.getData('text'));
7236                        e.preventDefault();
7237                    } else if (window.clipboardData) {
7238                        jexcel.current.paste(jexcel.current.selectedCell[0], jexcel.current.selectedCell[1], window.clipboardData.getData('text'));
7239                    }
7240                }
7241            }
7242        }
7243    }
7244    
7245    jexcel.contextMenuControls = function(e) {
7246        e = e || window.event;
7247        if ("buttons" in e) {
7248            var mouseButton = e.buttons;
7249        } else {
7250            var mouseButton = e.which || e.button;
7251        }
7252    
7253        if (jexcel.current) {
7254            if (jexcel.current.edition) {
7255                e.preventDefault();
7256            } else if (jexcel.current.options.contextMenu) {
7257                jexcel.current.contextMenu.contextmenu.close();
7258    
7259                if (jexcel.current) {
7260                    var x = e.target.getAttribute('data-x');
7261                    var y = e.target.getAttribute('data-y');
7262    
7263                    if (x || y) {
7264                        // Table found
7265                        var items = jexcel.current.options.contextMenu(jexcel.current, x, y, e);
7266                        // The id is depending on header and body
7267                        jexcel.current.contextMenu.contextmenu.open(e, items);
7268                        // Avoid the real one
7269                        e.preventDefault();
7270                    }
7271                }
7272            }
7273        }
7274    }
7275
7276    jexcel.touchStartControls = function(e) {
7277        var jexcelTable = jexcel.getElement(e.target);
7278    
7279        if (jexcelTable[0]) {
7280            if (jexcel.current != jexcelTable[0].jexcel) {
7281                if (jexcel.current) {
7282                    jexcel.current.resetSelection();
7283                }
7284                jexcel.current = jexcelTable[0].jexcel;
7285            }
7286        } else {
7287            if (jexcel.current) {
7288                jexcel.current.resetSelection();
7289                jexcel.current = null;
7290            }
7291        }
7292    
7293        if (jexcel.current) {
7294            if (! jexcel.current.edition) {
7295                var columnId = e.target.getAttribute('data-x');
7296                var rowId = e.target.getAttribute('data-y');
7297    
7298                if (columnId && rowId) {
7299                    jexcel.current.updateSelectionFromCoords(columnId, rowId);
7300    
7301                    jexcel.timeControl = setTimeout(function() {
7302                        // Keep temporary reference to the element
7303                        if (jexcel.current.options.columns[columnId].type == 'color') {
7304                            jexcel.tmpElement = null;
7305                        } else {
7306                            jexcel.tmpElement = e.target;
7307                        }
7308                        jexcel.current.openEditor(e.target, false, e);
7309                    }, 500);
7310                }
7311            }
7312        }
7313    }
7314    
7315    jexcel.touchEndControls = function(e) {
7316        // Clear any time control
7317        if (jexcel.timeControl) {
7318            clearTimeout(jexcel.timeControl);
7319            jexcel.timeControl = null;
7320            // Element
7321            if (jexcel.tmpElement && jexcel.tmpElement.children[0].tagName == 'INPUT') {
7322                jexcel.tmpElement.children[0].focus();
7323            }
7324            jexcel.tmpElement = null;
7325        }
7326    }
7327    
7328    /**
7329     * Jexcel extensions
7330     */
7331    
7332    jexcel.createTabs = function(tabs, result) {
7333        // Create tab container
7334        tabs.innerHTML = '';
7335        tabs.classList.add('jexcel_tabs');
7336        var spreadsheet = []
7337        var link = [];
7338        for (var i = 0; i < result.length; i++) {
7339            // Spreadsheet container
7340            spreadsheet[i] = document.createElement('div');
7341            spreadsheet[i].classList.add('jexcel_tab');
7342            // Tab link
7343            link[i] = document.createElement('div');
7344            link[i].classList.add('jexcel_tab_link');
7345            link[i].setAttribute('data-spreadsheet', i);
7346            link[i].innerHTML = result[i].sheetName;
7347            link[i].onclick = function() {
7348                for (var j = 0; j < spreadsheet.length; j++) {
7349                    spreadsheet[j].style.display = 'none';
7350                    link[j].classList.remove('selected');
7351                }
7352                var i = this.getAttribute('data-spreadsheet');
7353                spreadsheet[i].style.display = 'block';
7354                link[i].classList.add('selected')
7355            }
7356            tabs.appendChild(link[i]);
7357        }
7358    
7359        // Append spreadsheet
7360        for (var i = 0; i < spreadsheet.length - 1; i++) {
7361            tabs.appendChild(spreadsheet[i]);
7362            jexcel(spreadsheet[i], result[i]);
7363        }
7364    
7365        // First tab
7366        spreadsheet[0].style.display = 'block';
7367        link[0].classList.add('selected')
7368    }
7369    
7370    jexcel.fromSpreadsheet = function(file, __callback) {
7371        var convert = function(workbook) {
7372            var spreadsheets = [];
7373            workbook.SheetNames.forEach(function(sheetName) {
7374                var spreadsheet = {};
7375                spreadsheet.rows = [];
7376                spreadsheet.columns = [];
7377                spreadsheet.data = [];
7378                spreadsheet.style = {};
7379                spreadsheet.sheetName = sheetName;
7380    
7381                // Column widths
7382                var temp = workbook.Sheets[sheetName]['!cols'];
7383                if (temp && temp.length) {
7384                    for (var i = 0; i < temp.length; i++) {
7385                        spreadsheet.columns[i] = {};
7386                        if (temp[i] && temp[i].wpx) {
7387                            spreadsheet.columns[i].width = temp[i].wpx + 'px';
7388                        }
7389                     }
7390                }
7391                // Rows heights
7392                var temp = workbook.Sheets[sheetName]['!rows'];
7393                if (temp && temp.length) {
7394                    for (var i = 0; i < temp.length; i++) {
7395                        if (temp[i] && temp[i].hpx) {
7396                            spreadsheet.rows[i] = {};
7397                            spreadsheet.rows[i].height = temp[i].hpx + 'px';
7398                        }
7399                    }
7400                }
7401                // Merge cells
7402                var temp = workbook.Sheets[sheetName]['!merges'];
7403                if (temp && temp.length > 0) {
7404                    spreadsheet.mergeCells = [];
7405                    for (var i = 0; i < temp.length; i++) {
7406                        var x1 = temp[i].s.c;
7407                        var y1 = temp[i].s.r;
7408                        var x2 = temp[i].e.c;
7409                        var y2 = temp[i].e.r;
7410                        var key = jexcel.getColumnNameFromId([x1,y1]);
7411                        spreadsheet.mergeCells[key] = [ x2-x1+1, y2-y1+1 ];
7412                    }
7413                }
7414                // Data container
7415                var max_x = 0;
7416                var max_y = 0;
7417                var temp = Object.keys(workbook.Sheets[sheetName]);
7418                for (var i = 0; i < temp.length; i++) {
7419                    if (temp[i].substr(0,1) != '!') {
7420                        var cell = workbook.Sheets[sheetName][temp[i]];
7421                        var info = jexcel.getIdFromColumnName(temp[i], true);
vendor: 207,886 bytes, lines 7422-13182
7422                        if (! spreadsheet.data[info[1]]) {
7423                            spreadsheet.data[info[1]] = [];
7424                        }
7425                        spreadsheet.data[info[1]][info[0]] = cell.f ? '=' + cell.f : cell.w;
7426                        if (max_x < info[0]) {
7427                            max_x = info[0];
7428                        }
7429                        if (max_y < info[1]) {
7430                            max_y = info[1];
7431                        }
7432                        // Style
7433                        if (cell.style && Object.keys(cell.style).length > 0) {
7434                            spreadsheet.style[temp[i]] = cell.style;
7435                        }
7436                        if (cell.s && cell.s.fgColor) {
7437                            if (spreadsheet.style[temp[i]]) {
7438                                spreadsheet.style[temp[i]] += ';';
7439                            }
7440                            spreadsheet.style[temp[i]] += 'background-color:#' + cell.s.fgColor.rgb;
7441                        }
7442                    }
7443                }
7444                var numColumns = spreadsheet.columns;
7445                for (var j = 0; j <= max_y; j++) {
7446                    for (var i = 0; i <= max_x; i++) {
7447                        if (! spreadsheet.data[j]) {
7448                            spreadsheet.data[j] = [];
7449                        }
7450                        if (! spreadsheet.data[j][i]) {
7451                            if (numColumns < i) {
7452                                spreadsheet.data[j][i] = '';
7453                            }
7454                        }
7455                    }
7456                }
7457                spreadsheets.push(spreadsheet);
7458            });
7459    
7460            return spreadsheets;
7461        }
7462    
7463        var oReq;
7464        oReq = new XMLHttpRequest();
7465        oReq.open("GET", file, true);
7466    
7467        if(typeof Uint8Array !== 'undefined') {
7468            oReq.responseType = "arraybuffer";
7469            oReq.onload = function(e) {
7470                var arraybuffer = oReq.response;
7471                var data = new Uint8Array(arraybuffer);
7472                var wb = XLSX.read(data, {type:"array", cellFormula:true, cellStyles:true });
7473                __callback(convert(wb))
7474            };
7475        } else {
7476            oReq.setRequestHeader("Accept-Charset", "x-user-defined");  
7477            oReq.onreadystatechange = function() { if(oReq.readyState == 4 && oReq.status == 200) {
7478                var ff = convertResponseBodyToText(oReq.responseBody);
7479                var wb = XLSX.read(ff, {type:"binary", cellFormula:true, cellStyles:true });
7480                __callback(convert(wb))
7481            }};
7482        }
7483    
7484        oReq.send();
7485    }
7486    
7487    /**
7488     * Valid international letter
7489     */
7490    
7491    jexcel.validLetter = function (text) {
7492        var regex = /([\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC-\u0400-\u04FF']+)/g;
7493        return text.match(regex) ? 1 : 0;
7494    }
7495    
7496    /**
7497     * Helper injectArray
7498     */
7499    jexcel.injectArray = function(o, idx, arr) {
7500        return o.slice(0, idx).concat(arr).concat(o.slice(idx));
7501    }
7502    
7503    /**
7504     * Get letter based on a number
7505     * 
7506     * @param integer i
7507     * @return string letter
7508     */
7509    jexcel.getColumnName = function(i) {
7510        var letter = '';
7511        if (i > 701) {
7512            letter += String.fromCharCode(64 + parseInt(i / 676));
7513            letter += String.fromCharCode(64 + parseInt((i % 676) / 26));
7514        } else if (i > 25) {
7515            letter += String.fromCharCode(64 + parseInt(i / 26));
7516        }
7517        letter += String.fromCharCode(65 + (i % 26));
7518    
7519        return letter;
7520    }
7521    
7522    /**
7523     * Convert excel like column to jexcel id
7524     * 
7525     * @param string id
7526     * @return string id
7527     */
7528    jexcel.getIdFromColumnName = function (id, arr) {
7529        // Get the letters
7530        var t = /^[a-zA-Z]+/.exec(id);
7531    
7532        if (t) {
7533            // Base 26 calculation
7534            var code = 0;
7535            for (var i = 0; i < t[0].length; i++) {
7536                code += parseInt(t[0].charCodeAt(i) - 64) * Math.pow(26, (t[0].length - 1 - i));
7537            }
7538            code--;
7539            // Make sure jexcel starts on zero
7540            if (code < 0) {
7541                code = 0;
7542            }
7543    
7544            // Number
7545            var number = parseInt(/[0-9]+$/.exec(id));
7546            if (number > 0) {
7547                number--;
7548            }
7549    
7550            if (arr == true) {
7551                id = [ code, number ];
7552            } else {
7553                id = code + '-' + number;
7554            }
7555        }
7556    
7557        return id;
7558    }
7559    
7560    /**
7561     * Convert jexcel id to excel like column name
7562     * 
7563     * @param string id
7564     * @return string id
7565     */
7566    jexcel.getColumnNameFromId = function (cellId) {
7567        if (! Array.isArray(cellId)) {
7568            cellId = cellId.split('-');
7569        }
7570    
7571        return jexcel.getColumnName(parseInt(cellId[0])) + (parseInt(cellId[1]) + 1);
7572    }
7573    
7574    /**
7575     * Verify element inside jexcel table
7576     * 
7577     * @param string id
7578     * @return string id
7579     */
7580    jexcel.getElement = function(element) {
7581        var jexcelSection = 0;
7582        var jexcelElement = 0;
7583    
7584        function path (element) {
7585            if (element.className) {
7586                if (element.classList.contains('jexcel_container')) {
7587                    jexcelElement = element;
7588                }
7589            }
7590    
7591            if (element.tagName == 'THEAD') {
7592                jexcelSection = 1;
7593            } else if (element.tagName == 'TBODY') {
7594                jexcelSection = 2;
7595            }
7596    
7597            if (element.parentNode) {
7598                path(element.parentNode);
7599            }
7600        }
7601    
7602        path(element);
7603    
7604        return [ jexcelElement, jexcelSection ];
7605    }
7606
7607    jexcel.doubleDigitFormat = function(v) {
7608        v = ''+v;
7609        if (v.length == 1) {
7610            v = '0'+v;
7611        }
7612        return v;
7613    }
7614
7615    /**
7616     * Jquery Support
7617     */
7618    if (typeof(jQuery) != 'undefined') {
7619        (function($){
7620            $.fn.jexcel = function(method) {
7621                var spreadsheetContainer = $(this).get(0);
7622                if (! spreadsheetContainer.jexcel) {
7623                    return jexcel($(this).get(0), arguments[0]);
7624                } else {
7625                    return spreadsheetContainer.jexcel[method].apply(this, Array.prototype.slice.call( arguments, 1 ));
7626                }
7627            };
7628    
7629        })(jQuery);
7630    }
7631    
7632    
7633    // Based on sutoiku work (https://github.com/sutoiku)
7634    
7635    var error = (function() {
7636        var exports = {};
7637    
7638        exports.nil = new Error('#NULL!');
7639        exports.div0 = new Error('#DIV/0!');
7640        exports.value = new Error('#VALUE!');
7641        exports.ref = new Error('#REF!');
7642        exports.name = new Error('#NAME?');
7643        exports.num = new Error('#NUM!');
7644        exports.na = new Error('#N/A');
7645        exports.error = new Error('#ERROR!');
7646        exports.data = new Error('#GETTING_DATA');
7647    
7648        return exports;
7649    })();
7650    
7651    var utils = (function() {
7652        var exports = {};
7653    
7654        exports.flattenShallow = function(array) {
7655            if (!array || !array.reduce) {
7656                return array;
7657            }
7658    
7659            return array.reduce(function(a, b) {
7660                var aIsArray = Array.isArray(a);
7661                var bIsArray = Array.isArray(b);
7662    
7663                if (aIsArray && bIsArray) {
7664                    return a.concat(b);
7665                }
7666                if (aIsArray) {
7667                    a.push(b);
7668    
7669                    return a;
7670                }
7671                if (bIsArray) {
7672                    return [ a ].concat(b);
7673                }
7674    
7675                return [ a, b ];
7676            });
7677        };
7678    
7679        exports.isFlat = function(array) {
7680            if (!array) {
7681                return false;
7682            }
7683    
7684            for (var i = 0; i < array.length; ++i) {
7685                if (Array.isArray(array[i])) {
7686                    return false;
7687                }
7688            }
7689    
7690            return true;
7691        };
7692    
7693        exports.flatten = function() {
7694            var result = exports.argsToArray.apply(null, arguments);
7695    
7696            while (!exports.isFlat(result)) {
7697                result = exports.flattenShallow(result);
7698            }
7699    
7700            return result;
7701        };
7702    
7703        exports.argsToArray = function(args) {
7704            var result = [];
7705    
7706            exports.arrayEach(args, function(value) {
7707                result.push(value);
7708            });
7709    
7710            return result;
7711        };
7712    
7713        exports.numbers = function() {
7714            var possibleNumbers = this.flatten.apply(null, arguments);
7715            return possibleNumbers.filter(function(el) {
7716                return typeof el === 'number';
7717            });
7718        };
7719    
7720        exports.cleanFloat = function(number) {
7721            var power = 1e14;
7722            return Math.round(number * power) / power;
7723        };
7724    
7725        exports.parseBool = function(bool) {
7726            if (typeof bool === 'boolean') {
7727                return bool;
7728            }
7729    
7730            if (bool instanceof Error) {
7731                return bool;
7732            }
7733    
7734            if (typeof bool === 'number') {
7735                return bool !== 0;
7736            }
7737    
7738            if (typeof bool === 'string') {
7739                var up = bool.toUpperCase();
7740                if (up === 'TRUE') {
7741                    return true;
7742                }
7743    
7744                if (up === 'FALSE') {
7745                    return false;
7746                }
7747            }
7748    
7749            if (bool instanceof Date && !isNaN(bool)) {
7750                return true;
7751            }
7752    
7753            return error.value;
7754        };
7755    
7756        exports.parseNumber = function(string) {
7757            if (string === undefined || string === '') {
7758                return error.value;
7759            }
7760            if (!isNaN(string)) {
7761                return parseFloat(string);
7762            }
7763    
7764            return error.value;
7765        };
7766    
7767        exports.parseNumberArray = function(arr) {
7768            var len;
7769    
7770            if (!arr || (len = arr.length) === 0) {
7771                return error.value;
7772            }
7773    
7774            var parsed;
7775    
7776            while (len--) {
7777                parsed = exports.parseNumber(arr[len]);
7778                if (parsed === error.value) {
7779                    return parsed;
7780                }
7781                arr[len] = parsed;
7782            }
7783    
7784            return arr;
7785        };
7786    
7787        exports.parseMatrix = function(matrix) {
7788            var n;
7789    
7790            if (!matrix || (n = matrix.length) === 0) {
7791                return error.value;
7792            }
7793            var pnarr;
7794    
7795            for (var i = 0; i < matrix.length; i++) {
7796                pnarr = exports.parseNumberArray(matrix[i]);
7797                matrix[i] = pnarr;
7798    
7799                if (pnarr instanceof Error) {
7800                    return pnarr;
7801                }
7802            }
7803    
7804            return matrix;
7805        };
7806    
7807        var d1900 = new Date(Date.UTC(1900, 0, 1));
7808        exports.parseDate = function(date) {
7809            if (!isNaN(date)) {
7810                if (date instanceof Date) {
7811                    return new Date(date);
7812                }
7813                var d = parseInt(date, 10);
7814                if (d < 0) {
7815                    return error.num;
7816                }
7817                if (d <= 60) {
7818                    return new Date(d1900.getTime() + (d - 1) * 86400000);
7819                }
7820                return new Date(d1900.getTime() + (d - 2) * 86400000);
7821            }
7822            if (typeof date === 'string') {
7823                date = new Date(date);
7824                if (!isNaN(date)) {
7825                    return date;
7826                }
7827            }
7828            return error.value;
7829        };
7830    
7831        exports.parseDateArray = function(arr) {
7832            var len = arr.length;
7833            var parsed;
7834            while (len--) {
7835                parsed = this.parseDate(arr[len]);
7836                if (parsed === error.value) {
7837                    return parsed;
7838                }
7839                arr[len] = parsed;
7840            }
7841            return arr;
7842        };
7843    
7844        exports.anyIsError = function() {
7845            var n = arguments.length;
7846            while (n--) {
7847                if (arguments[n] instanceof Error) {
7848                    return true;
7849                }
7850            }
7851            return false;
7852        };
7853    
7854        exports.arrayValuesToNumbers = function(arr) {
7855            var n = arr.length;
7856            var el;
7857            while (n--) {
7858                el = arr[n];
7859                if (typeof el === 'number') {
7860                    continue;
7861                }
7862                if (el === true) {
7863                    arr[n] = 1;
7864                    continue;
7865                }
7866                if (el === false) {
7867                    arr[n] = 0;
7868                    continue;
7869                }
7870                if (typeof el === 'string') {
7871                    var number = this.parseNumber(el);
7872                    if (number instanceof Error) {
7873                        arr[n] = 0;
7874                    } else {
7875                        arr[n] = number;
7876                    }
7877                }
7878            }
7879            return arr;
7880        };
7881    
7882        exports.rest = function(array, idx) {
7883            idx = idx || 1;
7884            if (!array || typeof array.slice !== 'function') {
7885                return array;
7886            }
7887            return array.slice(idx);
7888        };
7889    
7890        exports.initial = function(array, idx) {
7891            idx = idx || 1;
7892            if (!array || typeof array.slice !== 'function') {
7893                return array;
7894            }
7895            return array.slice(0, array.length - idx);
7896        };
7897    
7898        exports.arrayEach = function(array, iteratee) {
7899            var index = -1, length = array.length;
7900    
7901            while (++index < length) {
7902                if (iteratee(array[index], index, array) === false) {
7903                    break;
7904                }
7905            }
7906    
7907            return array;
7908        };
7909    
7910        exports.transpose = function(matrix) {
7911            if (!matrix) {
7912                return error.value;
7913            }
7914    
7915            return matrix[0].map(function(col, i) {
7916                return matrix.map(function(row) {
7917                    return row[i];
7918                });
7919            });
7920        };
7921    
7922        return exports;
7923    })();
7924    
7925    jexcel.methods = {};
7926    
7927    jexcel.methods.datetime = (function() {
7928        var exports = {};
7929    
7930        var d1900 = new Date(1900, 0, 1);
7931        var WEEK_STARTS = [
7932            undefined,
7933            0,
7934            1,
7935            undefined,
7936            undefined,
7937            undefined,
7938            undefined,
7939            undefined,
7940            undefined,
7941            undefined,
7942            undefined,
7943            undefined,
7944            1,
7945            2,
7946            3,
7947            4,
7948            5,
7949            6,
7950            0
7951        ];
7952        var WEEK_TYPES = [
7953            [],
7954            [1, 2, 3, 4, 5, 6, 7],
7955            [7, 1, 2, 3, 4, 5, 6],
7956            [6, 0, 1, 2, 3, 4, 5],
7957            [],
7958            [],
7959            [],
7960            [],
7961            [],
7962            [],
7963            [],
7964            [7, 1, 2, 3, 4, 5, 6],
7965            [6, 7, 1, 2, 3, 4, 5],
7966            [5, 6, 7, 1, 2, 3, 4],
7967            [4, 5, 6, 7, 1, 2, 3],
7968            [3, 4, 5, 6, 7, 1, 2],
7969            [2, 3, 4, 5, 6, 7, 1],
7970            [1, 2, 3, 4, 5, 6, 7]
7971        ];
7972        var WEEKEND_TYPES = [
7973            [],
7974            [6, 0],
7975            [0, 1],
7976            [1, 2],
7977            [2, 3],
7978            [3, 4],
7979            [4, 5],
7980            [5, 6],
7981            undefined,
7982            undefined,
7983            undefined, [0, 0],
7984            [1, 1],
7985            [2, 2],
7986            [3, 3],
7987            [4, 4],
7988            [5, 5],
7989            [6, 6]
7990        ];
7991    
7992        exports.DATE = function(year, month, day) {
7993            year = utils.parseNumber(year);
7994            month = utils.parseNumber(month);
7995            day = utils.parseNumber(day);
7996            if (utils.anyIsError(year, month, day)) {
7997                return error.value;
7998            }
7999            if (year < 0 || month < 0 || day < 0) {
8000                return error.num;
8001            }
8002            var date = new Date(year, month - 1, day);
8003            return date;
8004        };
8005    
8006        exports.DATEVALUE = function(date_text) {
8007            if (typeof date_text !== 'string') {
8008                return error.value;
8009            }
8010            var date = Date.parse(date_text);
8011            if (isNaN(date)) {
8012                return error.value;
8013            }
8014            if (date <= -2203891200000) {
8015                return (date - d1900) / 86400000 + 1;
8016            }
8017            return (date - d1900) / 86400000 + 2;
8018        };
8019    
8020        exports.DAY = function(serial_number) {
8021            var date = utils.parseDate(serial_number);
8022            if (date instanceof Error) {
8023                return date;
8024            }
8025            return date.getDate();
8026        };
8027    
8028        exports.DAYS = function(end_date, start_date) {
8029            end_date = utils.parseDate(end_date);
8030            start_date = utils.parseDate(start_date);
8031            if (end_date instanceof Error) {
8032                return end_date;
8033            }
8034            if (start_date instanceof Error) {
8035                return start_date;
8036            }
8037            return serial(end_date) - serial(start_date);
8038        };
8039    
8040        exports.DAYS360 = function(start_date, end_date, method) {
8041        };
8042    
8043        exports.EDATE = function(start_date, months) {
8044            start_date = utils.parseDate(start_date);
8045            if (start_date instanceof Error) {
8046                return start_date;
8047            }
8048            if (isNaN(months)) {
8049                return error.value;
8050            }
8051            months = parseInt(months, 10);
8052            start_date.setMonth(start_date.getMonth() + months);
8053            return serial(start_date);
8054        };
8055    
8056        exports.EOMONTH = function(start_date, months) {
8057            start_date = utils.parseDate(start_date);
8058            if (start_date instanceof Error) {
8059                return start_date;
8060            }
8061            if (isNaN(months)) {
8062                return error.value;
8063            }
8064            months = parseInt(months, 10);
8065            return serial(new Date(start_date.getFullYear(), start_date.getMonth() + months + 1, 0));
8066        };
8067    
8068        exports.HOUR = function(serial_number) {
8069            serial_number = utils.parseDate(serial_number);
8070            if (serial_number instanceof Error) {
8071                return serial_number;
8072            }
8073            return serial_number.getHours();
8074        };
8075    
8076        exports.INTERVAL = function(second) {
8077            if (typeof second !== 'number' && typeof second !== 'string') {
8078                return error.value;
8079            } else {
8080                second = parseInt(second, 10);
8081            }
8082    
8083            var year  = Math.floor(second/946080000);
8084            second    = second%946080000;
8085            var month = Math.floor(second/2592000);
8086            second    = second%2592000;
8087            var day   = Math.floor(second/86400);
8088            second    = second%86400;
8089    
8090            var hour  = Math.floor(second/3600);
8091            second    = second%3600;
8092            var min   = Math.floor(second/60);
8093            second    = second%60;
8094            var sec   = second;
8095    
8096            year  = (year  > 0) ? year  + 'Y' : '';
8097            month = (month > 0) ? month + 'M' : '';
8098            day   = (day   > 0) ? day   + 'D' : '';
8099            hour  = (hour  > 0) ? hour  + 'H' : '';
8100            min   = (min   > 0) ? min   + 'M' : '';
8101            sec   = (sec   > 0) ? sec   + 'S' : '';
8102    
8103            return 'P' + year + month + day + 'T' + hour + min + sec;
8104        };
8105    
8106        exports.ISOWEEKNUM = function(date) {
8107            date = utils.parseDate(date);
8108            if (date instanceof Error) {
8109                return date;
8110            }
8111    
8112            date.setHours(0, 0, 0);
8113            date.setDate(date.getDate() + 4 - (date.getDay() || 7));
8114            var yearStart = new Date(date.getFullYear(), 0, 1);
8115            return Math.ceil((((date - yearStart) / 86400000) + 1) / 7);
8116        };
8117    
8118        exports.MINUTE = function(serial_number) {
8119            serial_number = utils.parseDate(serial_number);
8120            if (serial_number instanceof Error) {
8121                return serial_number;
8122            }
8123            return serial_number.getMinutes();
8124        };
8125    
8126        exports.MONTH = function(serial_number) {
8127            serial_number = utils.parseDate(serial_number);
8128            if (serial_number instanceof Error) {
8129                return serial_number;
8130            }
8131            return serial_number.getMonth() + 1;
8132        };
8133    
8134        exports.NETWORKDAYS = function(start_date, end_date, holidays) {
8135        };
8136    
8137        exports.NETWORKDAYS.INTL = function(start_date, end_date, weekend, holidays) {
8138        };
8139    
8140        exports.NOW = function() {
8141            return new Date();
8142        };
8143    
8144        exports.SECOND = function(serial_number) {
8145            serial_number = utils.parseDate(serial_number);
8146            if (serial_number instanceof Error) {
8147                return serial_number;
8148            }
8149            return serial_number.getSeconds();
8150        };
8151    
8152        exports.TIME = function(hour, minute, second) {
8153            hour = utils.parseNumber(hour);
8154            minute = utils.parseNumber(minute);
8155            second = utils.parseNumber(second);
8156            if (utils.anyIsError(hour, minute, second)) {
8157                return error.value;
8158            }
8159            if (hour < 0 || minute < 0 || second < 0) {
8160                return error.num;
8161            }
8162            return (3600 * hour + 60 * minute + second) / 86400;
8163        };
8164    
8165        exports.TIMEVALUE = function(time_text) {
8166            time_text = utils.parseDate(time_text);
8167            if (time_text instanceof Error) {
8168                return time_text;
8169            }
8170            return (3600 * time_text.getHours() + 60 * time_text.getMinutes() + time_text.getSeconds()) / 86400;
8171        };
8172    
8173        exports.TODAY = function() {
8174            return new Date();
8175        };
8176    
8177        exports.WEEKDAY = function(serial_number, return_type) {
8178            serial_number = utils.parseDate(serial_number);
8179            if (serial_number instanceof Error) {
8180                return serial_number;
8181            }
8182            if (return_type === undefined) {
8183                return_type = 1;
8184            }
8185            var day = serial_number.getDay();
8186            return WEEK_TYPES[return_type][day];
8187        };
8188    
8189        exports.WEEKNUM = function(serial_number, return_type) {
8190        };
8191    
8192        exports.WORKDAY = function(start_date, days, holidays) {
8193        };
8194    
8195        exports.WORKDAY.INTL = function(start_date, days, weekend, holidays) {
8196        };
8197    
8198        exports.YEAR = function(serial_number) {
8199            serial_number = utils.parseDate(serial_number);
8200            if (serial_number instanceof Error) {
8201                return serial_number;
8202            }
8203            return serial_number.getFullYear();
8204        };
8205    
8206        function isLeapYear(year) {
8207            return new Date(year, 1, 29).getMonth() === 1;
8208        }
8209    
8210        exports.YEARFRAC = function(start_date, end_date, basis) {
8211        };
8212    
8213        function serial(date) {
8214            var addOn = (date > -2203891200000)?2:1;
8215            return (date - d1900) / 86400000 + addOn;
8216        }
8217    
8218        return exports;
8219    })();
8220    
8221    jexcel.methods.database = (function() {
8222        var exports = {};
8223    
8224        function compact(array) {
8225            if (!array) {
8226                return array;
8227            }
8228            var result = [];
8229            for (var i = 0; i < array.length; ++i) {
8230                if (!array[i]) {
8231                    continue;
8232                }
8233                result.push(array[i]);
8234            }
8235            return result;
8236        }
8237    
8238        exports.FINDFIELD = function(database, title) {
8239            var index = null;
8240            for (var i = 0; i < database.length; i++) {
8241                if (database[i][0] === title) {
8242                    index = i;
8243                    break;
8244                }
8245            }
8246    
8247            // Return error if the input field title is incorrect
8248            if (index == null) {
8249                return error.value;
8250            }
8251            return index;
8252        };
8253    
8254        function findResultIndex(database, criterias) {
8255            var matches = {};
8256            for (var i = 1; i < database[0].length; ++i) {
8257                matches[i] = true;
8258            }
8259            var maxCriteriaLength = criterias[0].length;
8260            for (i = 1; i < criterias.length; ++i) {
8261                if (criterias[i].length > maxCriteriaLength) {
8262                    maxCriteriaLength = criterias[i].length;
8263                }
8264            }
8265    
8266            for (var k = 1; k < database.length; ++k) {
8267                for (var l = 1; l < database[k].length; ++l) {
8268                    var currentCriteriaResult = false;
8269                    var hasMatchingCriteria = false;
8270                    for (var j = 0; j < criterias.length; ++j) {
8271                        var criteria = criterias[j];
8272                        if (criteria.length < maxCriteriaLength) {
8273                            continue;
8274                        }
8275    
8276                        var criteriaField = criteria[0];
8277                        if (database[k][0] !== criteriaField) {
8278                            continue;
8279                        }
8280                        hasMatchingCriteria = true;
8281                        for (var p = 1; p < criteria.length; ++p) {
8282                            currentCriteriaResult = currentCriteriaResult
8283                                    || eval(database[k][l] + criteria[p]); // jshint
8284                                                                            // ignore:line
8285                        }
8286                    }
8287                    if (hasMatchingCriteria) {
8288                        matches[l] = matches[l] && currentCriteriaResult;
8289                    }
8290                }
8291            }
8292    
8293            var result = [];
8294            for (var n = 0; n < database[0].length; ++n) {
8295                if (matches[n]) {
8296                    result.push(n - 1);
8297                }
8298            }
8299            return result;
8300        }
8301    
8302        // Database functions
8303        exports.DAVERAGE = function(database, field, criteria) {
8304            // Return error if field is not a number and not a string
8305            if (isNaN(field) && (typeof field !== "string")) {
8306                return error.value;
8307            }
8308            var resultIndexes = findResultIndex(database, criteria);
8309            var targetFields = [];
8310            if (typeof field === "string") {
8311                var index = exports.FINDFIELD(database, field);
8312                targetFields = utils.rest(database[index]);
8313            } else {
8314                targetFields = utils.rest(database[field]);
8315            }
8316            var sum = 0;
8317            for (var i = 0; i < resultIndexes.length; i++) {
8318                sum += targetFields[resultIndexes[i]];
8319            }
8320            return resultIndexes.length === 0 ? error.div0 : sum / resultIndexes.length;
8321        };
8322    
8323        exports.DCOUNT = function(database, field, criteria) {
8324        };
8325    
8326        exports.DCOUNTA = function(database, field, criteria) {
8327        };
8328    
8329        exports.DGET = function(database, field, criteria) {
8330            // Return error if field is not a number and not a string
8331            if (isNaN(field) && (typeof field !== "string")) {
8332                return error.value;
8333            }
8334            var resultIndexes = findResultIndex(database, criteria);
8335            var targetFields = [];
8336            if (typeof field === "string") {
8337                var index = exports.FINDFIELD(database, field);
8338                targetFields = utils.rest(database[index]);
8339            } else {
8340                targetFields = utils.rest(database[field]);
8341            }
8342            // Return error if no record meets the criteria
8343            if (resultIndexes.length === 0) {
8344                return error.value;
8345            }
8346            // Returns the #NUM! error value because more than one record meets the
8347            // criteria
8348            if (resultIndexes.length > 1) {
8349                return error.num;
8350            }
8351    
8352            return targetFields[resultIndexes[0]];
8353        };
8354    
8355        exports.DMAX = function(database, field, criteria) {
8356            // Return error if field is not a number and not a string
8357            if (isNaN(field) && (typeof field !== "string")) {
8358                return error.value;
8359            }
8360            var resultIndexes = findResultIndex(database, criteria);
8361            var targetFields = [];
8362            if (typeof field === "string") {
8363                var index = exports.FINDFIELD(database, field);
8364                targetFields = utils.rest(database[index]);
8365            } else {
8366                targetFields = utils.rest(database[field]);
8367            }
8368            var maxValue = targetFields[resultIndexes[0]];
8369            for (var i = 1; i < resultIndexes.length; i++) {
8370                if (maxValue < targetFields[resultIndexes[i]]) {
8371                    maxValue = targetFields[resultIndexes[i]];
8372                }
8373            }
8374            return maxValue;
8375        };
8376    
8377        exports.DMIN = function(database, field, criteria) {
8378            // Return error if field is not a number and not a string
8379            if (isNaN(field) && (typeof field !== "string")) {
8380                return error.value;
8381            }
8382            var resultIndexes = findResultIndex(database, criteria);
8383            var targetFields = [];
8384            if (typeof field === "string") {
8385                var index = exports.FINDFIELD(database, field);
8386                targetFields = utils.rest(database[index]);
8387            } else {
8388                targetFields = utils.rest(database[field]);
8389            }
8390            var minValue = targetFields[resultIndexes[0]];
8391            for (var i = 1; i < resultIndexes.length; i++) {
8392                if (minValue > targetFields[resultIndexes[i]]) {
8393                    minValue = targetFields[resultIndexes[i]];
8394                }
8395            }
8396            return minValue;
8397        };
8398    
8399        exports.DPRODUCT = function(database, field, criteria) {
8400            // Return error if field is not a number and not a string
8401            if (isNaN(field) && (typeof field !== "string")) {
8402                return error.value;
8403            }
8404            var resultIndexes = findResultIndex(database, criteria);
8405            var targetFields = [];
8406            if (typeof field === "string") {
8407                var index = exports.FINDFIELD(database, field);
8408                targetFields = utils.rest(database[index]);
8409            } else {
8410                targetFields = utils.rest(database[field]);
8411            }
8412            var targetValues = [];
8413            for (var i = 0; i < resultIndexes.length; i++) {
8414                targetValues[i] = targetFields[resultIndexes[i]];
8415            }
8416            targetValues = compact(targetValues);
8417            var result = 1;
8418            for (i = 0; i < targetValues.length; i++) {
8419                result *= targetValues[i];
8420            }
8421            return result;
8422        };
8423    
8424        exports.DSTDEV = function(database, field, criteria) {
8425        };
8426    
8427        exports.DSTDEVP = function(database, field, criteria) {
8428        };
8429    
8430        exports.DSUM = function(database, field, criteria) {
8431        };
8432    
8433        exports.DVAR = function(database, field, criteria) {
8434        };
8435    
8436        exports.DVARP = function(database, field, criteria) {
8437        };
8438    
8439        exports.MATCH = function(lookupValue, lookupArray, matchType) {
8440            if (!lookupValue && !lookupArray) {
8441                return error.na;
8442            }
8443            if (arguments.length === 2) {
8444                matchType = 1;
8445            }
8446            if (!(lookupArray instanceof Array)) {
8447                return error.na;
8448            }
8449            if (matchType !== -1 && matchType !== 0 && matchType !== 1) {
8450                return error.na;
8451            }
8452    
8453            var index;
8454            var indexValue;
8455    
8456            for (var idx = 0; idx < lookupArray.length; idx++) {
8457                if (matchType === 1) {
8458                    if (lookupArray[idx] === lookupValue) {
8459                        return idx + 1;
8460                    } else if (lookupArray[idx] < lookupValue) {
8461                        if (!indexValue) {
8462                            index = idx + 1;
8463                            indexValue = lookupArray[idx];
8464                        } else if (lookupArray[idx] > indexValue) {
8465                            index = idx + 1;
8466                            indexValue = lookupArray[idx];
8467                        }
8468                    }
8469                } else if (matchType === 0) {
8470                    if (typeof lookupValue === 'string') {
8471                        lookupValue = lookupValue.replace(/\?/g, '.');
8472                        if (lookupArray[idx].toLowerCase().match(lookupValue.toLowerCase())) {
8473                            return idx + 1;
8474                        }
8475                    } else {
8476                        if (lookupArray[idx] === lookupValue) {
8477                            return idx + 1;
8478                        }
8479                    }
8480                } else if (matchType === -1) {
8481                    if (lookupArray[idx] === lookupValue) {
8482                        return idx + 1;
8483                    } else if (lookupArray[idx] > lookupValue) {
8484                        if (!indexValue) {
8485                            index = idx + 1;
8486                            indexValue = lookupArray[idx];
8487                        } else if (lookupArray[idx] < indexValue) {
8488                            index = idx + 1;
8489                            indexValue = lookupArray[idx];
8490                        }
8491                    }
8492                }
8493            }
8494    
8495            return index ? index : error.na;
8496        };
8497    
8498        return exports;
8499    })();
8500    
8501    jexcel.methods.engineering = (function() {
8502        var exports = {};
8503    
8504        function isValidBinaryNumber(number) {
8505            return (/^[01]{1,10}$/).test(number);
8506        }
8507    
8508        exports.BESSELI = function(x, n) {
8509        };
8510    
8511        exports.BESSELJ = function(x, n) {
8512        };
8513    
8514        exports.BESSELK = function(x, n) {
8515        };
8516    
8517        exports.BESSELY = function(x, n) {
8518        };
8519    
8520        exports.BIN2DEC = function(number) {
8521            // Return error if number is not binary or contains more than 10
8522            // characters (10 digits)
8523            if (!isValidBinaryNumber(number)) {
8524                return error.num;
8525            }
8526    
8527            // Convert binary number to decimal
8528            var result = parseInt(number, 2);
8529    
8530            // Handle negative numbers
8531            var stringified = number.toString();
8532            if (stringified.length === 10 && stringified.substring(0, 1) === '1') {
8533                return parseInt(stringified.substring(1), 2) - 512;
8534            } else {
8535                return result;
8536            }
8537        };
8538    
8539        exports.BIN2HEX = function(number, places) {
8540            // Return error if number is not binary or contains more than 10
8541            // characters (10 digits)
8542            if (!isValidBinaryNumber(number)) {
8543                return error.num;
8544            }
8545    
8546            // Ignore places and return a 10-character hexadecimal number if number
8547            // is negative
8548            var stringified = number.toString();
8549            if (stringified.length === 10 && stringified.substring(0, 1) === '1') {
8550                return (1099511627264 + parseInt(stringified.substring(1), 2)).toString(16);
8551            }
8552    
8553            // Convert binary number to hexadecimal
8554            var result = parseInt(number, 2).toString(16);
8555    
8556            // Return hexadecimal number using the minimum number of characters
8557            // necessary if places is undefined
8558            if (places === undefined) {
8559                return result;
8560            } else {
8561                // Return error if places is nonnumeric
8562                if (isNaN(places)) {
8563                  return error.value;
8564                }
8565    
8566                // Return error if places is negative
8567                if (places < 0) {
8568                  return error.num;
8569                }
8570    
8571                // Truncate places in case it is not an integer
8572                places = Math.floor(places);
8573    
8574                // Pad return value with leading 0s (zeros) if necessary (using
8575                // Underscore.string)
8576                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
8577            }
8578        };
8579    
8580        exports.BIN2OCT = function(number, places) {
8581            // Return error if number is not binary or contains more than 10
8582            // characters (10 digits)
8583            if (!isValidBinaryNumber(number)) {
8584                return error.num;
8585            }
8586    
8587            // Ignore places and return a 10-character octal number if number is
8588            // negative
8589            var stringified = number.toString();
8590            if (stringified.length === 10 && stringified.substring(0, 1) === '1') {
8591                return (1073741312 + parseInt(stringified.substring(1), 2)).toString(8);
8592            }
8593    
8594            // Convert binary number to octal
8595            var result = parseInt(number, 2).toString(8);
8596    
8597            // Return octal number using the minimum number of characters necessary
8598            // if places is undefined
8599            if (places === undefined) {
8600                return result;
8601            } else {
8602                // Return error if places is nonnumeric
8603                if (isNaN(places)) {
8604                  return error.value;
8605                }
8606    
8607                // Return error if places is negative
8608                if (places < 0) {
8609                  return error.num;
8610                }
8611    
8612                // Truncate places in case it is not an integer
8613                places = Math.floor(places);
8614    
8615                // Pad return value with leading 0s (zeros) if necessary (using
8616                // Underscore.string)
8617                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
8618            }
8619        };
8620    
8621        exports.BITAND = function(number1, number2) {
8622            // Return error if either number is a non-numeric value
8623            number1 = utils.parseNumber(number1);
8624            number2 = utils.parseNumber(number2);
8625            if (utils.anyIsError(number1, number2)) {
8626                return error.value;
8627            }
8628    
8629            // Return error if either number is less than 0
8630            if (number1 < 0 || number2 < 0) {
8631                return error.num;
8632            }
8633    
8634            // Return error if either number is a non-integer
8635            if (Math.floor(number1) !== number1 || Math.floor(number2) !== number2) {
8636                return error.num;
8637            }
8638    
8639            // Return error if either number is greater than (2^48)-1
8640            if (number1 > 281474976710655 || number2 > 281474976710655) {
8641                return error.num;
8642            }
8643    
8644            // Return bitwise AND of two numbers
8645            return number1 & number2;
8646        };
8647    
8648        exports.BITLSHIFT = function(number, shift) {
8649            number = utils.parseNumber(number);
8650            shift = utils.parseNumber(shift);
8651            if (utils.anyIsError(number, shift)) {
8652                return error.value;
8653            }
8654    
8655            // Return error if number is less than 0
8656            if (number < 0) {
8657                return error.num;
8658            }
8659    
8660            // Return error if number is a non-integer
8661            if (Math.floor(number) !== number) {
8662                return error.num;
8663            }
8664    
8665            // Return error if number is greater than (2^48)-1
8666            if (number > 281474976710655) {
8667                return error.num;
8668            }
8669    
8670            // Return error if the absolute value of shift is greater than 53
8671            if (Math.abs(shift) > 53) {
8672                return error.num;
8673            }
8674    
8675            // Return number shifted by shift bits to the left or to the right if
8676            // shift is negative
8677            return (shift >= 0) ? number << shift : number >> -shift;
8678        };
8679    
8680        exports.BITOR = function(number1, number2) {
8681            number1 = utils.parseNumber(number1);
8682            number2 = utils.parseNumber(number2);
8683            if (utils.anyIsError(number1, number2)) {
8684                return error.value;
8685            }
8686    
8687            // Return error if either number is less than 0
8688            if (number1 < 0 || number2 < 0) {
8689                return error.num;
8690            }
8691    
8692            // Return error if either number is a non-integer
8693            if (Math.floor(number1) !== number1 || Math.floor(number2) !== number2) {
8694                return error.num;
8695            }
8696    
8697            // Return error if either number is greater than (2^48)-1
8698            if (number1 > 281474976710655 || number2 > 281474976710655) {
8699                return error.num;
8700            }
8701    
8702            // Return bitwise OR of two numbers
8703            return number1 | number2;
8704        };
8705    
8706        exports.BITRSHIFT = function(number, shift) {
8707            number = utils.parseNumber(number);
8708            shift = utils.parseNumber(shift);
8709            if (utils.anyIsError(number, shift)) {
8710                return error.value;
8711            }
8712    
8713            // Return error if number is less than 0
8714            if (number < 0) {
8715                return error.num;
8716            }
8717    
8718            // Return error if number is a non-integer
8719            if (Math.floor(number) !== number) {
8720                return error.num;
8721            }
8722    
8723            // Return error if number is greater than (2^48)-1
8724            if (number > 281474976710655) {
8725                return error.num;
8726            }
8727    
8728            // Return error if the absolute value of shift is greater than 53
8729            if (Math.abs(shift) > 53) {
8730                return error.num;
8731            }
8732    
8733            // Return number shifted by shift bits to the right or to the left if
8734            // shift is negative
8735            return (shift >= 0) ? number >> shift : number << -shift;
8736        };
8737    
8738        exports.BITXOR = function(number1, number2) {
8739            number1 = utils.parseNumber(number1);
8740            number2 = utils.parseNumber(number2);
8741            if (utils.anyIsError(number1, number2)) {
8742                return error.value;
8743            }
8744    
8745            // Return error if either number is less than 0
8746            if (number1 < 0 || number2 < 0) {
8747                return error.num;
8748            }
8749    
8750            // Return error if either number is a non-integer
8751            if (Math.floor(number1) !== number1 || Math.floor(number2) !== number2) {
8752                return error.num;
8753            }
8754    
8755            // Return error if either number is greater than (2^48)-1
8756            if (number1 > 281474976710655 || number2 > 281474976710655) {
8757                return error.num;
8758            }
8759    
8760            // Return bitwise XOR of two numbers
8761            return number1 ^ number2;
8762        };
8763    
8764        exports.COMPLEX = function(real, imaginary, suffix) {
8765            real = utils.parseNumber(real);
8766            imaginary = utils.parseNumber(imaginary);
8767            if (utils.anyIsError(real, imaginary)) {
8768                return real;
8769            }
8770    
8771            // Set suffix
8772            suffix = (suffix === undefined) ? 'i' : suffix;
8773    
8774            // Return error if suffix is neither "i" nor "j"
8775            if (suffix !== 'i' && suffix !== 'j') {
8776                return error.value;
8777            }
8778    
8779            // Return complex number
8780            if (real === 0 && imaginary === 0) {
8781                return 0;
8782            } else if (real === 0) {
8783                return (imaginary === 1) ? suffix : imaginary.toString() + suffix;
8784            } else if (imaginary === 0) {
8785                return real.toString();
8786            } else {
8787                var sign = (imaginary > 0) ? '+' : '';
8788                return real.toString() + sign + ((imaginary === 1) ? suffix : imaginary.toString() + suffix);
8789            }
8790        };
8791    
8792        exports.CONVERT = function(number, from_unit, to_unit) {
8793            number = utils.parseNumber(number);
8794            if (number instanceof Error) {
8795                return number;
8796            }
8797    
8798            // List of units supported by CONVERT and units defined by the
8799            // International System of Units
8800            // [Name, Symbol, Alternate symbols, Quantity, ISU, CONVERT, Conversion
8801            // ratio]
8802            var units = [
8803                ["a.u. of action", "?", null, "action", false, false, 1.05457168181818e-34],
8804                ["a.u. of charge", "e", null, "electric_charge", false, false, 1.60217653141414e-19],
8805                ["a.u. of energy", "Eh", null, "energy", false, false, 4.35974417757576e-18],
8806                ["a.u. of length", "a?", null, "length", false, false, 5.29177210818182e-11],
8807                ["a.u. of mass", "m?", null, "mass", false, false, 9.10938261616162e-31],
8808                ["a.u. of time", "?/Eh", null, "time", false, false, 2.41888432650516e-17],
8809                ["admiralty knot", "admkn", null, "speed", false, true, 0.514773333],
8810                ["ampere", "A", null, "electric_current", true, false, 1],
8811                ["ampere per meter", "A/m", null, "magnetic_field_intensity", true, false, 1],
8812                ["ångström", "Å", ["ang"], "length", false, true, 1e-10],
8813                ["are", "ar", null, "area", false, true, 100],
8814                ["astronomical unit", "ua", null, "length", false, false, 1.49597870691667e-11],
8815                ["bar", "bar", null, "pressure", false, false, 100000],
8816                ["barn", "b", null, "area", false, false, 1e-28],
8817                ["becquerel", "Bq", null, "radioactivity", true, false, 1],
8818                ["bit", "bit", ["b"], "information", false, true, 1],
8819                ["btu", "BTU", ["btu"], "energy", false, true, 1055.05585262],
8820                ["byte", "byte", null, "information", false, true, 8],
8821                ["candela", "cd", null, "luminous_intensity", true, false, 1],
8822                ["candela per square metre", "cd/m?", null, "luminance", true, false, 1],
8823                ["coulomb", "C", null, "electric_charge", true, false, 1],
8824                ["cubic ångström", "ang3", ["ang^3"], "volume", false, true, 1e-30],
8825                ["cubic foot", "ft3", ["ft^3"], "volume", false, true, 0.028316846592],
8826                ["cubic inch", "in3", ["in^3"], "volume", false, true, 0.000016387064],
8827                ["cubic light-year", "ly3", ["ly^3"], "volume", false, true, 8.46786664623715e-47],
8828                ["cubic metre", "m?", null, "volume", true, true, 1],
8829                ["cubic mile", "mi3", ["mi^3"], "volume", false, true, 4168181825.44058],
8830                ["cubic nautical mile", "Nmi3", ["Nmi^3"], "volume", false, true, 6352182208],
8831                ["cubic Pica", "Pica3", ["Picapt3", "Pica^3", "Picapt^3"], "volume", false, true, 7.58660370370369e-8],
8832                ["cubic yard", "yd3", ["yd^3"], "volume", false, true, 0.764554857984],
8833                ["cup", "cup", null, "volume", false, true, 0.0002365882365],
8834                ["dalton", "Da", ["u"], "mass", false, false, 1.66053886282828e-27],
8835                ["day", "d", ["day"], "time", false, true, 86400],
8836                ["degree", "°", null, "angle", false, false, 0.0174532925199433],
8837                ["degrees Rankine", "Rank", null, "temperature", false, true, 0.555555555555556],
8838                ["dyne", "dyn", ["dy"], "force", false, true, 0.00001],
8839                ["electronvolt", "eV", ["ev"], "energy", false, true, 1.60217656514141],
8840                ["ell", "ell", null, "length", false, true, 1.143],
8841                ["erg", "erg", ["e"], "energy", false, true, 1e-7],
8842                ["farad", "F", null, "electric_capacitance", true, false, 1],
8843                ["fluid ounce", "oz", null, "volume", false, true, 0.0000295735295625],
8844                ["foot", "ft", null, "length", false, true, 0.3048],
8845                ["foot-pound", "flb", null, "energy", false, true, 1.3558179483314],
8846                ["gal", "Gal", null, "acceleration", false, false, 0.01],
8847                ["gallon", "gal", null, "volume", false, true, 0.003785411784],
8848                ["gauss", "G", ["ga"], "magnetic_flux_density", false, true, 1],
8849                ["grain", "grain", null, "mass", false, true, 0.0000647989],
8850                ["gram", "g", null, "mass", false, true, 0.001],
8851                ["gray", "Gy", null, "absorbed_dose", true, false, 1],
8852                ["gross registered ton", "GRT", ["regton"], "volume", false, true, 2.8316846592],
8853                ["hectare", "ha", null, "area", false, true, 10000],
8854                ["henry", "H", null, "inductance", true, false, 1],
8855                ["hertz", "Hz", null, "frequency", true, false, 1],
8856                ["horsepower", "HP", ["h"], "power", false, true, 745.69987158227],
8857                ["horsepower-hour", "HPh", ["hh", "hph"], "energy", false, true, 2684519.538],
8858                ["hour", "h", ["hr"], "time", false, true, 3600],
8859                ["imperial gallon (U.K.)", "uk_gal", null, "volume", false, true, 0.00454609],
8860                ["imperial hundredweight", "lcwt", ["uk_cwt", "hweight"], "mass", false, true, 50.802345],
8861                ["imperial quart (U.K)", "uk_qt", null, "volume", false, true, 0.0011365225],
8862                ["imperial ton", "brton", ["uk_ton", "LTON"], "mass", false, true, 1016.046909],
8863                ["inch", "in", null, "length", false, true, 0.0254],
8864                ["international acre", "uk_acre", null, "area", false, true, 4046.8564224],
8865                ["IT calorie", "cal", null, "energy", false, true, 4.1868],
8866                ["joule", "J", null, "energy", true, true, 1],
8867                ["katal", "kat", null, "catalytic_activity", true, false, 1],
8868                ["kelvin", "K", ["kel"], "temperature", true, true, 1],
8869                ["kilogram", "kg", null, "mass", true, true, 1],
8870                ["knot", "kn", null, "speed", false, true, 0.514444444444444],
8871                ["light-year", "ly", null, "length", false, true, 9460730472580800],
8872                ["litre", "L", ["l", "lt"], "volume", false, true, 0.001],
8873                ["lumen", "lm", null, "luminous_flux", true, false, 1],
8874                ["lux", "lx", null, "illuminance", true, false, 1],
8875                ["maxwell", "Mx", null, "magnetic_flux", false, false, 1e-18],
8876                ["measurement ton", "MTON", null, "volume", false, true, 1.13267386368],
8877                ["meter per hour", "m/h", ["m/hr"], "speed", false, true, 0.00027777777777778],
8878                ["meter per second", "m/s", ["m/sec"], "speed", true, true, 1],
8879                ["meter per second squared", "m?s??", null, "acceleration", true, false, 1],
8880                ["parsec", "pc", ["parsec"], "length", false, true, 30856775814671900],
8881                ["meter squared per second", "m?/s", null, "kinematic_viscosity", true, false, 1],
8882                ["metre", "m", null, "length", true, true, 1],
8883                ["miles per hour", "mph", null, "speed", false, true, 0.44704],
8884                ["millimetre of mercury", "mmHg", null, "pressure", false, false, 133.322],
8885                ["minute", "?", null, "angle", false, false, 0.000290888208665722],
8886                ["minute", "min", ["mn"], "time", false, true, 60],
8887                ["modern teaspoon", "tspm", null, "volume", false, true, 0.000005],
8888                ["mole", "mol", null, "amount_of_substance", true, false, 1],
8889                ["morgen", "Morgen", null, "area", false, true, 2500],
8890                ["n.u. of action", "?", null, "action", false, false, 1.05457168181818e-34],
8891                ["n.u. of mass", "m?", null, "mass", false, false, 9.10938261616162e-31],
8892                ["n.u. of speed", "c?", null, "speed", false, false, 299792458],
8893                ["n.u. of time", "?/(me?c??)", null, "time", false, false, 1.28808866778687e-21],
8894                ["nautical mile", "M", ["Nmi"], "length", false, true, 1852],
8895                ["newton", "N", null, "force", true, true, 1],
8896                ["œrsted", "Oe ", null, "magnetic_field_intensity", false, false, 79.5774715459477],
8897                ["ohm", "Ω", null, "electric_resistance", true, false, 1],
8898                ["ounce mass", "ozm", null, "mass", false, true, 0.028349523125],
8899                ["pascal", "Pa", null, "pressure", true, false, 1],
8900                ["pascal second", "Pa?s", null, "dynamic_viscosity", true, false, 1],
8901                ["pferdestärke", "PS", null, "power", false, true, 735.49875],
8902                ["phot", "ph", null, "illuminance", false, false, 0.0001],
8903                ["pica (1/6 inch)", "pica", null, "length", false, true, 0.00035277777777778],
8904                ["pica (1/72 inch)", "Pica", ["Picapt"], "length", false, true, 0.00423333333333333],
8905                ["poise", "P", null, "dynamic_viscosity", false, false, 0.1],
8906                ["pond", "pond", null, "force", false, true, 0.00980665],
8907                ["pound force", "lbf", null, "force", false, true, 4.4482216152605],
8908                ["pound mass", "lbm", null, "mass", false, true, 0.45359237],
8909                ["quart", "qt", null, "volume", false, true, 0.000946352946],
8910                ["radian", "rad", null, "angle", true, false, 1],
8911                ["second", "?", null, "angle", false, false, 0.00000484813681109536],
8912                ["second", "s", ["sec"], "time", true, true, 1],
8913                ["short hundredweight", "cwt", ["shweight"], "mass", false, true, 45.359237],
8914                ["siemens", "S", null, "electrical_conductance", true, false, 1],
8915                ["sievert", "Sv", null, "equivalent_dose", true, false, 1],
8916                ["slug", "sg", null, "mass", false, true, 14.59390294],
8917                ["square ångström", "ang2", ["ang^2"], "area", false, true, 1e-20],
8918                ["square foot", "ft2", ["ft^2"], "area", false, true, 0.09290304],
8919                ["square inch", "in2", ["in^2"], "area", false, true, 0.00064516],
8920                ["square light-year", "ly2", ["ly^2"], "area", false, true, 8.95054210748189e+31],
8921                ["square meter", "m?", null, "area", true, true, 1],
8922                ["square mile", "mi2", ["mi^2"], "area", false, true, 2589988.110336],
8923                ["square nautical mile", "Nmi2", ["Nmi^2"], "area", false, true, 3429904],
8924                ["square Pica", "Pica2", ["Picapt2", "Pica^2", "Picapt^2"], "area", false, true, 0.00001792111111111],
8925                ["square yard", "yd2", ["yd^2"], "area", false, true, 0.83612736],
8926                ["statute mile", "mi", null, "length", false, true, 1609.344],
8927                ["steradian", "sr", null, "solid_angle", true, false, 1],
8928                ["stilb", "sb", null, "luminance", false, false, 0.0001],
8929                ["stokes", "St", null, "kinematic_viscosity", false, false, 0.0001],
8930                ["stone", "stone", null, "mass", false, true, 6.35029318],
8931                ["tablespoon", "tbs", null, "volume", false, true, 0.0000147868],
8932                ["teaspoon", "tsp", null, "volume", false, true, 0.00000492892],
8933                ["tesla", "T", null, "magnetic_flux_density", true, true, 1],
8934                ["thermodynamic calorie", "c", null, "energy", false, true, 4.184],
8935                ["ton", "ton", null, "mass", false, true, 907.18474],
8936                ["tonne", "t", null, "mass", false, false, 1000],
8937                ["U.K. pint", "uk_pt", null, "volume", false, true, 0.00056826125],
8938                ["U.S. bushel", "bushel", null, "volume", false, true, 0.03523907],
8939                ["U.S. oil barrel", "barrel", null, "volume", false, true, 0.158987295],
8940                ["U.S. pint", "pt", ["us_pt"], "volume", false, true, 0.000473176473],
8941                ["U.S. survey mile", "survey_mi", null, "length", false, true, 1609.347219],
8942                ["U.S. survey/statute acre", "us_acre", null, "area", false, true, 4046.87261],
8943                ["volt", "V", null, "voltage", true, false, 1],
8944                ["watt", "W", null, "power", true, true, 1],
8945                ["watt-hour", "Wh", ["wh"], "energy", false, true, 3600],
8946                ["weber", "Wb", null, "magnetic_flux", true, false, 1],
8947                ["yard", "yd", null, "length", false, true, 0.9144],
8948                ["year", "yr", null, "time", false, true, 31557600]
8949            ];
8950    
8951            // Binary prefixes
8952            // [Name, Prefix power of 2 value, Previx value, Abbreviation, Derived
8953            // from]
8954            var binary_prefixes = {
8955                Yi: ["yobi", 80, 1208925819614629174706176, "Yi", "yotta"],
8956                Zi: ["zebi", 70, 1180591620717411303424, "Zi", "zetta"],
8957                Ei: ["exbi", 60, 1152921504606846976, "Ei", "exa"],
8958                Pi: ["pebi", 50, 1125899906842624, "Pi", "peta"],
8959                Ti: ["tebi", 40, 1099511627776, "Ti", "tera"],
8960                Gi: ["gibi", 30, 1073741824, "Gi", "giga"],
8961                Mi: ["mebi", 20, 1048576, "Mi", "mega"],
8962                ki: ["kibi", 10, 1024, "ki", "kilo"]
8963            };
8964    
8965            // Unit prefixes
8966            // [Name, Multiplier, Abbreviation]
8967            var unit_prefixes = {
8968                Y: ["yotta", 1e+24, "Y"],
8969                Z: ["zetta", 1e+21, "Z"],
8970                E: ["exa", 1e+18, "E"],
8971                P: ["peta", 1e+15, "P"],
8972                T: ["tera", 1e+12, "T"],
8973                G: ["giga", 1e+09, "G"],
8974                M: ["mega", 1e+06, "M"],
8975                k: ["kilo", 1e+03, "k"],
8976                h: ["hecto", 1e+02, "h"],
8977                e: ["dekao", 1e+01, "e"],
8978                d: ["deci", 1e-01, "d"],
8979                c: ["centi", 1e-02, "c"],
8980                m: ["milli", 1e-03, "m"],
8981                u: ["micro", 1e-06, "u"],
8982                n: ["nano", 1e-09, "n"],
8983                p: ["pico", 1e-12, "p"],
8984                f: ["femto", 1e-15, "f"],
8985                a: ["atto", 1e-18, "a"],
8986                z: ["zepto", 1e-21, "z"],
8987                y: ["yocto", 1e-24, "y"]
8988            };
8989    
8990            // Initialize units and multipliers
8991            var from = null;
8992            var to = null;
8993            var base_from_unit = from_unit;
8994            var base_to_unit = to_unit;
8995            var from_multiplier = 1;
8996            var to_multiplier = 1;
8997            var alt;
8998    
8999            // Lookup from and to units
9000            for (var i = 0; i < units.length; i++) {
9001                alt = (units[i][2] === null) ? [] : units[i][2];
9002                if (units[i][1] === base_from_unit || alt.indexOf(base_from_unit) >= 0) {
9003                  from = units[i];
9004                }
9005                if (units[i][1] === base_to_unit || alt.indexOf(base_to_unit) >= 0) {
9006                  to = units[i];
9007                }
9008            }
9009    
9010            // Lookup from prefix
9011            if (from === null) {
9012                var from_binary_prefix = binary_prefixes[from_unit.substring(0, 2)];
9013                var from_unit_prefix = unit_prefixes[from_unit.substring(0, 1)];
9014    
9015                // Handle dekao unit prefix (only unit prefix with two characters)
9016                if (from_unit.substring(0, 2) === 'da') {
9017                  from_unit_prefix = ["dekao", 1e+01, "da"];
9018                }
9019    
9020                // Handle binary prefixes first (so that 'Yi' is processed before
9021                // 'Y')
9022                if (from_binary_prefix) {
9023                  from_multiplier = from_binary_prefix[2];
9024                  base_from_unit = from_unit.substring(2);
9025                } else if (from_unit_prefix) {
9026                  from_multiplier = from_unit_prefix[1];
9027                  base_from_unit = from_unit.substring(from_unit_prefix[2].length);
9028                }
9029    
9030                // Lookup from unit
9031                for (var j = 0; j < units.length; j++) {
9032                  alt = (units[j][2] === null) ? [] : units[j][2];
9033                  if (units[j][1] === base_from_unit || alt.indexOf(base_from_unit) >= 0) {
9034                      from = units[j];
9035                  }
9036                }
9037            }
9038    
9039            // Lookup to prefix
9040            if (to === null) {
9041                var to_binary_prefix = binary_prefixes[to_unit.substring(0, 2)];
9042                var to_unit_prefix = unit_prefixes[to_unit.substring(0, 1)];
9043    
9044                // Handle dekao unit prefix (only unit prefix with two characters)
9045                if (to_unit.substring(0, 2) === 'da') {
9046                  to_unit_prefix = ["dekao", 1e+01, "da"];
9047                }
9048    
9049                // Handle binary prefixes first (so that 'Yi' is processed before
9050                // 'Y')
9051                if (to_binary_prefix) {
9052                  to_multiplier = to_binary_prefix[2];
9053                  base_to_unit = to_unit.substring(2);
9054                } else if (to_unit_prefix) {
9055                  to_multiplier = to_unit_prefix[1];
9056                  base_to_unit = to_unit.substring(to_unit_prefix[2].length);
9057                }
9058    
9059                // Lookup to unit
9060                for (var k = 0; k < units.length; k++) {
9061                  alt = (units[k][2] === null) ? [] : units[k][2];
9062                  if (units[k][1] === base_to_unit || alt.indexOf(base_to_unit) >= 0) {
9063                      to = units[k];
9064                  }
9065                }
9066            }
9067    
9068            // Return error if a unit does not exist
9069            if (from === null || to === null) {
9070                return error.na;
9071            }
9072    
9073            // Return error if units represent different quantities
9074            if (from[3] !== to[3]) {
9075                return error.na;
9076            }
9077    
9078            // Return converted number
9079            return number * from[6] * from_multiplier / (to[6] * to_multiplier);
9080        };
9081    
9082        exports.DEC2BIN = function(number, places) {
9083            number = utils.parseNumber(number);
9084            if (number instanceof Error) {
9085                return number;
9086            }
9087    
9088            // Return error if number is not decimal, is lower than -512, or is
9089            // greater than 511
9090            if (!/^-?[0-9]{1,3}$/.test(number) || number < -512 || number > 511) {
9091                return error.num;
9092            }
9093    
9094            // Ignore places and return a 10-character binary number if number is
9095            // negative
9096            if (number < 0) {
9097                return '1' + REPT('0', 9 - (512 + number).toString(2).length) + (512 + number).toString(2);
9098            }
9099    
9100            // Convert decimal number to binary
9101            var result = parseInt(number, 10).toString(2);
9102    
9103            // Return binary number using the minimum number of characters necessary
9104            // if places is undefined
9105            if (typeof places === 'undefined') {
9106                return result;
9107            } else {
9108                // Return error if places is nonnumeric
9109                if (isNaN(places)) {
9110                  return error.value;
9111                }
9112    
9113                // Return error if places is negative
9114                if (places < 0) {
9115                  return error.num;
9116                }
9117    
9118                // Truncate places in case it is not an integer
9119                places = Math.floor(places);
9120    
9121                // Pad return value with leading 0s (zeros) if necessary (using
9122                // Underscore.string)
9123                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
9124            }
9125        };
9126    
9127        exports.DEC2HEX = function(number, places) {
9128            number = utils.parseNumber(number);
9129            if (number instanceof Error) {
9130                return number;
9131            }
9132    
9133            // Return error if number is not decimal, is lower than -549755813888,
9134            // or is greater than 549755813887
9135            if (!/^-?[0-9]{1,12}$/.test(number) || number < -549755813888 || number > 549755813887) {
9136                return error.num;
9137            }
9138    
9139            // Ignore places and return a 10-character hexadecimal number if number
9140            // is negative
9141            if (number < 0) {
9142                return (1099511627776 + number).toString(16);
9143            }
9144    
9145            // Convert decimal number to hexadecimal
9146            var result = parseInt(number, 10).toString(16);
9147    
9148            // Return hexadecimal number using the minimum number of characters
9149            // necessary if places is undefined
9150            if (typeof places === 'undefined') {
9151                return result;
9152            } else {
9153                // Return error if places is nonnumeric
9154                if (isNaN(places)) {
9155                  return error.value;
9156                }
9157    
9158                // Return error if places is negative
9159                if (places < 0) {
9160                  return error.num;
9161                }
9162    
9163                // Truncate places in case it is not an integer
9164                places = Math.floor(places);
9165    
9166                // Pad return value with leading 0s (zeros) if necessary (using
9167                // Underscore.string)
9168                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
9169            }
9170        };
9171    
9172        exports.DEC2OCT = function(number, places) {
9173            number = utils.parseNumber(number);
9174            if (number instanceof Error) {
9175                return number;
9176            }
9177    
9178            // Return error if number is not decimal, is lower than -549755813888,
9179            // or is greater than 549755813887
9180            if (!/^-?[0-9]{1,9}$/.test(number) || number < -536870912 || number > 536870911) {
9181                return error.num;
9182            }
9183    
9184            // Ignore places and return a 10-character octal number if number is
9185            // negative
9186            if (number < 0) {
9187                return (1073741824 + number).toString(8);
9188            }
9189    
9190            // Convert decimal number to octal
9191            var result = parseInt(number, 10).toString(8);
9192    
9193            // Return octal number using the minimum number of characters necessary
9194            // if places is undefined
9195            if (typeof places === 'undefined') {
9196                return result;
9197            } else {
9198                // Return error if places is nonnumeric
9199                if (isNaN(places)) {
9200                  return error.value;
9201                }
9202    
9203                // Return error if places is negative
9204                if (places < 0) {
9205                  return error.num;
9206                }
9207    
9208                // Truncate places in case it is not an integer
9209                places = Math.floor(places);
9210    
9211                // Pad return value with leading 0s (zeros) if necessary (using
9212                // Underscore.string)
9213                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
9214            }
9215        };
9216    
9217        exports.DELTA = function(number1, number2) {
9218            // Set number2 to zero if undefined
9219            number2 = (number2 === undefined) ? 0 : number2;
9220            number1 = utils.parseNumber(number1);
9221            number2 = utils.parseNumber(number2);
9222            if (utils.anyIsError(number1, number2)) {
9223                return error.value;
9224            }
9225    
9226            // Return delta
9227            return (number1 === number2) ? 1 : 0;
9228        };
9229    
9230        exports.ERF = function(lower_bound, upper_bound) {
9231        };
9232    
9233        exports.ERF.PRECISE = function() {
9234        };
9235    
9236        exports.ERFC = function(x) {
9237        };
9238    
9239        exports.ERFC.PRECISE = function() {
9240        };
9241    
9242        exports.GESTEP = function(number, step) {
9243            step = step || 0;
9244            number = utils.parseNumber(number);
9245            if (utils.anyIsError(step, number)) {
9246                return number;
9247            }
9248    
9249            // Return delta
9250            return (number >= step) ? 1 : 0;
9251        };
9252    
9253        exports.HEX2BIN = function(number, places) {
9254            // Return error if number is not hexadecimal or contains more than ten
9255            // characters (10 digits)
9256            if (!/^[0-9A-Fa-f]{1,10}$/.test(number)) {
9257                return error.num;
9258            }
9259    
9260            // Check if number is negative
9261            var negative = (number.length === 10 && number.substring(0, 1).toLowerCase() === 'f') ? true : false;
9262    
9263            // Convert hexadecimal number to decimal
9264            var decimal = (negative) ? parseInt(number, 16) - 1099511627776 : parseInt(number, 16);
9265    
9266            // Return error if number is lower than -512 or greater than 511
9267            if (decimal < -512 || decimal > 511) {
9268                return error.num;
9269            }
9270    
9271            // Ignore places and return a 10-character binary number if number is
9272            // negative
9273            if (negative) {
9274                return '1' + REPT('0', 9 - (512 + decimal).toString(2).length) + (512 + decimal).toString(2);
9275            }
9276    
9277            // Convert decimal number to binary
9278            var result = decimal.toString(2);
9279    
9280            // Return binary number using the minimum number of characters necessary
9281            // if places is undefined
9282            if (places === undefined) {
9283                return result;
9284            } else {
9285                // Return error if places is nonnumeric
9286                if (isNaN(places)) {
9287                  return error.value;
9288                }
9289    
9290                // Return error if places is negative
9291                if (places < 0) {
9292                  return error.num;
9293                }
9294    
9295                // Truncate places in case it is not an integer
9296                places = Math.floor(places);
9297    
9298                // Pad return value with leading 0s (zeros) if necessary (using
9299                // Underscore.string)
9300                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
9301            }
9302        };
9303    
9304        exports.HEX2DEC = function(number) {
9305            // Return error if number is not hexadecimal or contains more than ten
9306            // characters (10 digits)
9307            if (!/^[0-9A-Fa-f]{1,10}$/.test(number)) {
9308                return error.num;
9309            }
9310    
9311            // Convert hexadecimal number to decimal
9312            var decimal = parseInt(number, 16);
9313    
9314            // Return decimal number
9315            return (decimal >= 549755813888) ? decimal - 1099511627776 : decimal;
9316        };
9317    
9318        exports.HEX2OCT = function(number, places) {
9319            // Return error if number is not hexadecimal or contains more than ten
9320            // characters (10 digits)
9321            if (!/^[0-9A-Fa-f]{1,10}$/.test(number)) {
9322                return error.num;
9323            }
9324    
9325            // Convert hexadecimal number to decimal
9326            var decimal = parseInt(number, 16);
9327    
9328            // Return error if number is positive and greater than 0x1fffffff
9329            // (536870911)
9330            if (decimal > 536870911 && decimal < 1098974756864) {
9331                return error.num;
9332            }
9333    
9334            // Ignore places and return a 10-character octal number if number is
9335            // negative
9336            if (decimal >= 1098974756864) {
9337                return (decimal - 1098437885952).toString(8);
9338            }
9339    
9340            // Convert decimal number to octal
9341            var result = decimal.toString(8);
9342    
9343            // Return octal number using the minimum number of characters necessary
9344            // if places is undefined
9345            if (places === undefined) {
9346                return result;
9347            } else {
9348                // Return error if places is nonnumeric
9349                if (isNaN(places)) {
9350                  return error.value;
9351                }
9352    
9353                // Return error if places is negative
9354                if (places < 0) {
9355                  return error.num;
9356                }
9357    
9358                // Truncate places in case it is not an integer
9359                places = Math.floor(places);
9360    
9361                // Pad return value with leading 0s (zeros) if necessary (using
9362                // Underscore.string)
9363                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
9364            }
9365        };
9366    
9367        exports.IMABS = function(inumber) {
9368            // Lookup real and imaginary coefficients using exports.js
9369            // [http://formulajs.org]
9370            var x = exports.IMREAL(inumber);
9371            var y = exports.IMAGINARY(inumber);
9372    
9373            // Return error if either coefficient is not a number
9374            if (utils.anyIsError(x, y)) {
9375                return error.value;
9376            }
9377    
9378            // Return absolute value of complex number
9379            return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
9380        };
9381    
9382        exports.IMAGINARY = function(inumber) {
9383            if (inumber === undefined || inumber === true || inumber === false) {
9384                return error.value;
9385            }
9386    
9387            // Return 0 if inumber is equal to 0
9388            if (inumber === 0 || inumber === '0') {
9389                return 0;
9390            }
9391    
9392            // Handle special cases
9393            if (['i', 'j'].indexOf(inumber) >= 0) {
9394                return 1;
9395            }
9396    
9397            // Normalize imaginary coefficient
9398            inumber = inumber.replace('+i', '+1i').replace('-i', '-1i').replace('+j', '+1j').replace('-j', '-1j');
9399    
9400            // Lookup sign
9401            var plus = inumber.indexOf('+');
9402            var minus = inumber.indexOf('-');
9403            if (plus === 0) {
9404                plus = inumber.indexOf('+', 1);
9405            }
9406    
9407            if (minus === 0) {
9408                minus = inumber.indexOf('-', 1);
9409            }
9410    
9411            // Lookup imaginary unit
9412            var last = inumber.substring(inumber.length - 1, inumber.length);
9413            var unit = (last === 'i' || last === 'j');
9414    
9415            if (plus >= 0 || minus >= 0) {
9416                // Return error if imaginary unit is neither i nor j
9417                if (!unit) {
9418                  return error.num;
9419                }
9420    
9421                // Return imaginary coefficient of complex number
9422                if (plus >= 0) {
9423                  return (isNaN(inumber.substring(0, plus)) || isNaN(inumber.substring(plus + 1, inumber.length - 1))) ?
9424                      error.num :
9425                      Number(inumber.substring(plus + 1, inumber.length - 1));
9426                } else {
9427                  return (isNaN(inumber.substring(0, minus)) || isNaN(inumber.substring(minus + 1, inumber.length - 1))) ?
9428                      error.num :
9429                      -Number(inumber.substring(minus + 1, inumber.length - 1));
9430                }
9431            } else {
9432                if (unit) {
9433                  return (isNaN(inumber.substring(0, inumber.length - 1))) ? error.num : inumber.substring(0, inumber.length - 1);
9434                } else {
9435                  return (isNaN(inumber)) ? error.num : 0;
9436                }
9437            }
9438        };
9439    
9440        exports.IMARGUMENT = function(inumber) {
9441            // Lookup real and imaginary coefficients using exports.js
9442            // [http://formulajs.org]
9443            var x = exports.IMREAL(inumber);
9444            var y = exports.IMAGINARY(inumber);
9445    
9446            // Return error if either coefficient is not a number
9447            if (utils.anyIsError(x, y)) {
9448                return error.value;
9449            }
9450    
9451            // Return error if inumber is equal to zero
9452            if (x === 0 && y === 0) {
9453                return error.div0;
9454            }
9455    
9456            // Return PI/2 if x is equal to zero and y is positive
9457            if (x === 0 && y > 0) {
9458                return Math.PI / 2;
9459            }
9460    
9461            // Return -PI/2 if x is equal to zero and y is negative
9462            if (x === 0 && y < 0) {
9463                return -Math.PI / 2;
9464            }
9465    
9466            // Return zero if x is negative and y is equal to zero
9467            if (y === 0 && x > 0) {
9468                return 0;
9469            }
9470    
9471            // Return zero if x is negative and y is equal to zero
9472            if (y === 0 && x < 0) {
9473                return -Math.PI;
9474            }
9475    
9476            // Return argument of complex number
9477            if (x > 0) {
9478                return Math.atan(y / x);
9479            } else if (x < 0 && y >= 0) {
9480                return Math.atan(y / x) + Math.PI;
9481            } else {
9482                return Math.atan(y / x) - Math.PI;
9483            }
9484        };
9485    
9486        exports.IMCONJUGATE = function(inumber) {
9487            // Lookup real and imaginary coefficients using exports.js
9488            // [http://formulajs.org]
9489            var x = exports.IMREAL(inumber);
9490            var y = exports.IMAGINARY(inumber);
9491    
9492            if (utils.anyIsError(x, y)) {
9493                return error.value;
9494            }
9495    
9496            // Lookup imaginary unit
9497            var unit = inumber.substring(inumber.length - 1);
9498            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9499    
9500            // Return conjugate of complex number
9501            return (y !== 0) ? exports.COMPLEX(x, -y, unit) : inumber;
9502        };
9503    
9504        exports.IMCOS = function(inumber) {
9505            // Lookup real and imaginary coefficients using exports.js
9506            // [http://formulajs.org]
9507            var x = exports.IMREAL(inumber);
9508            var y = exports.IMAGINARY(inumber);
9509    
9510            if (utils.anyIsError(x, y)) {
9511                return error.value;
9512            }
9513    
9514            // Lookup imaginary unit
9515            var unit = inumber.substring(inumber.length - 1);
9516            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9517    
9518            // Return cosine of complex number
9519            return exports.COMPLEX(Math.cos(x) * (Math.exp(y) + Math.exp(-y)) / 2, -Math.sin(x) * (Math.exp(y) - Math.exp(-y)) / 2, unit);
9520        };
9521    
9522        exports.IMCOSH = function(inumber) {
9523            // Lookup real and imaginary coefficients using exports.js
9524            // [http://formulajs.org]
9525            var x = exports.IMREAL(inumber);
9526            var y = exports.IMAGINARY(inumber);
9527    
9528            if (utils.anyIsError(x, y)) {
9529                return error.value;
9530            }
9531    
9532            // Lookup imaginary unit
9533            var unit = inumber.substring(inumber.length - 1);
9534            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9535    
9536            // Return hyperbolic cosine of complex number
9537            return exports.COMPLEX(Math.cos(y) * (Math.exp(x) + Math.exp(-x)) / 2, Math.sin(y) * (Math.exp(x) - Math.exp(-x)) / 2, unit);
9538        };
9539    
9540        exports.IMCOT = function(inumber) {
9541            // Lookup real and imaginary coefficients using Formula.js
9542            // [http://formulajs.org]
9543            var x = exports.IMREAL(inumber);
9544            var y = exports.IMAGINARY(inumber);
9545    
9546            if (utils.anyIsError(x, y)) {
9547                return error.value;
9548            }
9549    
9550            // Return cotangent of complex number
9551            return exports.IMDIV(exports.IMCOS(inumber), exports.IMSIN(inumber));
9552        };
9553    
9554        exports.IMDIV = function(inumber1, inumber2) {
9555            // Lookup real and imaginary coefficients using Formula.js
9556            // [http://formulajs.org]
9557            var a = exports.IMREAL(inumber1);
9558            var b = exports.IMAGINARY(inumber1);
9559            var c = exports.IMREAL(inumber2);
9560            var d = exports.IMAGINARY(inumber2);
9561    
9562            if (utils.anyIsError(a, b, c, d)) {
9563                return error.value;
9564            }
9565    
9566            // Lookup imaginary unit
9567            var unit1 = inumber1.substring(inumber1.length - 1);
9568            var unit2 = inumber2.substring(inumber2.length - 1);
9569            var unit = 'i';
9570            if (unit1 === 'j') {
9571                unit = 'j';
9572            } else if (unit2 === 'j') {
9573                unit = 'j';
9574            }
9575    
9576            // Return error if inumber2 is null
9577            if (c === 0 && d === 0) {
9578                return error.num;
9579            }
9580    
9581            // Return exponential of complex number
9582            var den = c * c + d * d;
9583            return exports.COMPLEX((a * c + b * d) / den, (b * c - a * d) / den, unit);
9584        };
9585    
9586        exports.IMEXP = function(inumber) {
9587            // Lookup real and imaginary coefficients using Formula.js
9588            // [http://formulajs.org]
9589            var x = exports.IMREAL(inumber);
9590            var y = exports.IMAGINARY(inumber);
9591    
9592            if (utils.anyIsError(x, y)) {
9593                return error.value;
9594            }
9595    
9596            // Lookup imaginary unit
9597            var unit = inumber.substring(inumber.length - 1);
9598            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9599    
9600            // Return exponential of complex number
9601            var e = Math.exp(x);
9602            return exports.COMPLEX(e * Math.cos(y), e * Math.sin(y), unit);
9603        };
9604    
9605        exports.IMLN = function(inumber) {
9606            // Lookup real and imaginary coefficients using Formula.js
9607            // [http://formulajs.org]
9608            var x = exports.IMREAL(inumber);
9609            var y = exports.IMAGINARY(inumber);
9610    
9611            if (utils.anyIsError(x, y)) {
9612                return error.value;
9613            }
9614    
9615            // Lookup imaginary unit
9616            var unit = inumber.substring(inumber.length - 1);
9617            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9618    
9619            // Return exponential of complex number
9620            return exports.COMPLEX(Math.log(Math.sqrt(x * x + y * y)), Math.atan(y / x), unit);
9621        };
9622    
9623        exports.IMLOG10 = function(inumber) {
9624            // Lookup real and imaginary coefficients using Formula.js
9625            // [http://formulajs.org]
9626            var x = exports.IMREAL(inumber);
9627            var y = exports.IMAGINARY(inumber);
9628    
9629            if (utils.anyIsError(x, y)) {
9630                return error.value;
9631            }
9632    
9633            // Lookup imaginary unit
9634            var unit = inumber.substring(inumber.length - 1);
9635            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9636    
9637            // Return exponential of complex number
9638            return exports.COMPLEX(Math.log(Math.sqrt(x * x + y * y)) / Math.log(10), Math.atan(y / x) / Math.log(10), unit);
9639        };
9640    
9641        exports.IMLOG2 = function(inumber) {
9642            // Lookup real and imaginary coefficients using Formula.js
9643            // [http://formulajs.org]
9644            var x = exports.IMREAL(inumber);
9645            var y = exports.IMAGINARY(inumber);
9646    
9647            if (utils.anyIsError(x, y)) {
9648                return error.value;
9649            }
9650    
9651            // Lookup imaginary unit
9652            var unit = inumber.substring(inumber.length - 1);
9653            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9654    
9655            // Return exponential of complex number
9656            return exports.COMPLEX(Math.log(Math.sqrt(x * x + y * y)) / Math.log(2), Math.atan(y / x) / Math.log(2), unit);
9657        };
9658    
9659        exports.IMPOWER = function(inumber, number) {
9660            number = utils.parseNumber(number);
9661            var x = exports.IMREAL(inumber);
9662            var y = exports.IMAGINARY(inumber);
9663            if (utils.anyIsError(number, x, y)) {
9664                return error.value;
9665            }
9666    
9667            // Lookup imaginary unit
9668            var unit = inumber.substring(inumber.length - 1);
9669            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9670    
9671            // Calculate power of modulus
9672            var p = Math.pow(exports.IMABS(inumber), number);
9673    
9674            // Calculate argument
9675            var t = exports.IMARGUMENT(inumber);
9676    
9677            // Return exponential of complex number
9678            return exports.COMPLEX(p * Math.cos(number * t), p * Math.sin(number * t), unit);
9679        };
9680    
9681        exports.IMPRODUCT = function() {
9682            // Initialize result
9683            var result = arguments[0];
9684    
9685            // Loop on all numbers
9686            for (var i = 1; i < arguments.length; i++) {
9687                // Lookup coefficients of two complex numbers
9688                var a = exports.IMREAL(result);
9689                var b = exports.IMAGINARY(result);
9690                var c = exports.IMREAL(arguments[i]);
9691                var d = exports.IMAGINARY(arguments[i]);
9692    
9693                if (utils.anyIsError(a, b, c, d)) {
9694                  return error.value;
9695                }
9696    
9697                // Complute product of two complex numbers
9698                result = exports.COMPLEX(a * c - b * d, a * d + b * c);
9699            }
9700    
9701            // Return product of complex numbers
9702            return result;
9703        };
9704    
9705        exports.IMREAL = function(inumber) {
9706            if (inumber === undefined || inumber === true || inumber === false) {
9707                return error.value;
9708            }
9709    
9710            // Return 0 if inumber is equal to 0
9711            if (inumber === 0 || inumber === '0') {
9712                return 0;
9713            }
9714    
9715            // Handle special cases
9716            if (['i', '+i', '1i', '+1i', '-i', '-1i', 'j', '+j', '1j', '+1j', '-j', '-1j'].indexOf(inumber) >= 0) {
9717                return 0;
9718            }
9719    
9720            // Lookup sign
9721            var plus = inumber.indexOf('+');
9722            var minus = inumber.indexOf('-');
9723            if (plus === 0) {
9724                plus = inumber.indexOf('+', 1);
9725            }
9726            if (minus === 0) {
9727                minus = inumber.indexOf('-', 1);
9728            }
9729    
9730            // Lookup imaginary unit
9731            var last = inumber.substring(inumber.length - 1, inumber.length);
9732            var unit = (last === 'i' || last === 'j');
9733    
9734            if (plus >= 0 || minus >= 0) {
9735                // Return error if imaginary unit is neither i nor j
9736                if (!unit) {
9737                  return error.num;
9738                }
9739    
9740                // Return real coefficient of complex number
9741                if (plus >= 0) {
9742                  return (isNaN(inumber.substring(0, plus)) || isNaN(inumber.substring(plus + 1, inumber.length - 1))) ?
9743                      error.num :
9744                      Number(inumber.substring(0, plus));
9745                } else {
9746                  return (isNaN(inumber.substring(0, minus)) || isNaN(inumber.substring(minus + 1, inumber.length - 1))) ?
9747                      error.num :
9748                      Number(inumber.substring(0, minus));
9749                }
9750            } else {
9751                if (unit) {
9752                  return (isNaN(inumber.substring(0, inumber.length - 1))) ? error.num : 0;
9753                } else {
9754                  return (isNaN(inumber)) ? error.num : inumber;
9755                }
9756            }
9757        };
9758    
9759        exports.IMSEC = function(inumber) {
9760            // Return error if inumber is a logical value
9761            if (inumber === true || inumber === false) {
9762                return error.value;
9763            }
9764    
9765            // Lookup real and imaginary coefficients using Formula.js
9766            // [http://formulajs.org]
9767            var x = exports.IMREAL(inumber);
9768            var y = exports.IMAGINARY(inumber);
9769    
9770            if (utils.anyIsError(x, y)) {
9771                return error.value;
9772            }
9773    
9774            // Return secant of complex number
9775            return exports.IMDIV('1', exports.IMCOS(inumber));
9776        };
9777    
9778        exports.IMSECH = function(inumber) {
9779            // Lookup real and imaginary coefficients using Formula.js
9780            // [http://formulajs.org]
9781            var x = exports.IMREAL(inumber);
9782            var y = exports.IMAGINARY(inumber);
9783    
9784            if (utils.anyIsError(x, y)) {
9785                return error.value;
9786            }
9787    
9788            // Return hyperbolic secant of complex number
9789            return exports.IMDIV('1', exports.IMCOSH(inumber));
9790        };
9791    
9792        exports.IMSIN = function(inumber) {
9793            // Lookup real and imaginary coefficients using Formula.js
9794            // [http://formulajs.org]
9795            var x = exports.IMREAL(inumber);
9796            var y = exports.IMAGINARY(inumber);
9797    
9798            if (utils.anyIsError(x, y)) {
9799                return error.value;
9800            }
9801    
9802            // Lookup imaginary unit
9803            var unit = inumber.substring(inumber.length - 1);
9804            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9805    
9806            // Return sine of complex number
9807            return exports.COMPLEX(Math.sin(x) * (Math.exp(y) + Math.exp(-y)) / 2, Math.cos(x) * (Math.exp(y) - Math.exp(-y)) / 2, unit);
9808        };
9809    
9810        exports.IMSINH = function(inumber) {
9811            // Lookup real and imaginary coefficients using Formula.js
9812            // [http://formulajs.org]
9813            var x = exports.IMREAL(inumber);
9814            var y = exports.IMAGINARY(inumber);
9815    
9816            if (utils.anyIsError(x, y)) {
9817                return error.value;
9818            }
9819    
9820            // Lookup imaginary unit
9821            var unit = inumber.substring(inumber.length - 1);
9822            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9823    
9824            // Return hyperbolic sine of complex number
9825            return exports.COMPLEX(Math.cos(y) * (Math.exp(x) - Math.exp(-x)) / 2, Math.sin(y) * (Math.exp(x) + Math.exp(-x)) / 2, unit);
9826        };
9827    
9828        exports.IMSQRT = function(inumber) {
9829            // Lookup real and imaginary coefficients using Formula.js
9830            // [http://formulajs.org]
9831            var x = exports.IMREAL(inumber);
9832            var y = exports.IMAGINARY(inumber);
9833    
9834            if (utils.anyIsError(x, y)) {
9835                return error.value;
9836            }
9837    
9838            // Lookup imaginary unit
9839            var unit = inumber.substring(inumber.length - 1);
9840            unit = (unit === 'i' || unit === 'j') ? unit : 'i';
9841    
9842            // Calculate power of modulus
9843            var s = Math.sqrt(exports.IMABS(inumber));
9844    
9845            // Calculate argument
9846            var t = exports.IMARGUMENT(inumber);
9847    
9848            // Return exponential of complex number
9849            return exports.COMPLEX(s * Math.cos(t / 2), s * Math.sin(t / 2), unit);
9850        };
9851    
9852        exports.IMCSC = function (inumber) {
9853            // Return error if inumber is a logical value
9854            if (inumber === true || inumber === false) {
9855                return error.value;
9856            }
9857    
9858            // Lookup real and imaginary coefficients using Formula.js
9859            // [http://formulajs.org]
9860            var x = exports.IMREAL(inumber);
9861            var y = exports.IMAGINARY(inumber);
9862    
9863            // Return error if either coefficient is not a number
9864            if (utils.anyIsError(x, y)) {
9865                return error.num;
9866            }
9867    
9868            // Return cosecant of complex number
9869            return exports.IMDIV('1', exports.IMSIN(inumber));
9870        };
9871    
9872        exports.IMCSCH = function (inumber) {
9873            // Return error if inumber is a logical value
9874            if (inumber === true || inumber === false) {
9875                return error.value;
9876            }
9877    
9878            // Lookup real and imaginary coefficients using Formula.js
9879            // [http://formulajs.org]
9880            var x = exports.IMREAL(inumber);
9881            var y = exports.IMAGINARY(inumber);
9882    
9883            // Return error if either coefficient is not a number
9884            if (utils.anyIsError(x, y)) {
9885                return error.num;
9886            }
9887    
9888            // Return hyperbolic cosecant of complex number
9889            return exports.IMDIV('1', exports.IMSINH(inumber));
9890        };
9891    
9892        exports.IMSUB = function(inumber1, inumber2) {
9893            // Lookup real and imaginary coefficients using Formula.js
9894            // [http://formulajs.org]
9895            var a = this.IMREAL(inumber1);
9896            var b = this.IMAGINARY(inumber1);
9897            var c = this.IMREAL(inumber2);
9898            var d = this.IMAGINARY(inumber2);
9899    
9900            if (utils.anyIsError(a, b, c, d)) {
9901                return error.value;
9902            }
9903    
9904            // Lookup imaginary unit
9905            var unit1 = inumber1.substring(inumber1.length - 1);
9906            var unit2 = inumber2.substring(inumber2.length - 1);
9907            var unit = 'i';
9908            if (unit1 === 'j') {
9909                unit = 'j';
9910            } else if (unit2 === 'j') {
9911                unit = 'j';
9912            }
9913    
9914            // Return _ of two complex numbers
9915            return this.COMPLEX(a - c, b - d, unit);
9916        };
9917    
9918        exports.IMSUM = function() {
9919            var args = utils.flatten(arguments);
9920    
9921            // Initialize result
9922            var result = args[0];
9923    
9924            // Loop on all numbers
9925            for (var i = 1; i < args.length; i++) {
9926                // Lookup coefficients of two complex numbers
9927                var a = this.IMREAL(result);
9928                var b = this.IMAGINARY(result);
9929                var c = this.IMREAL(args[i]);
9930                var d = this.IMAGINARY(args[i]);
9931    
9932                if (utils.anyIsError(a, b, c, d)) {
9933                  return error.value;
9934                }
9935    
9936                // Complute product of two complex numbers
9937                result = this.COMPLEX(a + c, b + d);
9938            }
9939    
9940            // Return sum of complex numbers
9941            return result;
9942        };
9943    
9944        exports.IMTAN = function(inumber) {
9945            // Return error if inumber is a logical value
9946            if (inumber === true || inumber === false) {
9947                return error.value;
9948            }
9949    
9950            // Lookup real and imaginary coefficients using Formula.js
9951            // [http://formulajs.org]
9952            var x = exports.IMREAL(inumber);
9953            var y = exports.IMAGINARY(inumber);
9954    
9955            if (utils.anyIsError(x, y)) {
9956                return error.value;
9957            }
9958    
9959            // Return tangent of complex number
9960            return this.IMDIV(this.IMSIN(inumber), this.IMCOS(inumber));
9961        };
9962    
9963        exports.OCT2BIN = function(number, places) {
9964            // Return error if number is not hexadecimal or contains more than ten
9965            // characters (10 digits)
9966            if (!/^[0-7]{1,10}$/.test(number)) {
9967                return error.num;
9968            }
9969    
9970            // Check if number is negative
9971            var negative = (number.length === 10 && number.substring(0, 1) === '7') ? true : false;
9972    
9973            // Convert octal number to decimal
9974            var decimal = (negative) ? parseInt(number, 8) - 1073741824 : parseInt(number, 8);
9975    
9976            // Return error if number is lower than -512 or greater than 511
9977            if (decimal < -512 || decimal > 511) {
9978                return error.num;
9979            }
9980    
9981            // Ignore places and return a 10-character binary number if number is
9982            // negative
9983            if (negative) {
9984                return '1' + REPT('0', 9 - (512 + decimal).toString(2).length) + (512 + decimal).toString(2);
9985            }
9986    
9987            // Convert decimal number to binary
9988            var result = decimal.toString(2);
9989    
9990            // Return binary number using the minimum number of characters necessary
9991            // if places is undefined
9992            if (typeof places === 'undefined') {
9993                return result;
9994            } else {
9995                // Return error if places is nonnumeric
9996                if (isNaN(places)) {
9997                  return error.value;
9998                }
9999    
10000                // Return error if places is negative
10001                if (places < 0) {
10002                  return error.num;
10003                }
10004    
10005                // Truncate places in case it is not an integer
10006                places = Math.floor(places);
10007    
10008                // Pad return value with leading 0s (zeros) if necessary (using
10009                // Underscore.string)
10010                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
10011            }
10012        };
10013    
10014        exports.OCT2DEC = function(number) {
10015            // Return error if number is not octal or contains more than ten
10016            // characters (10 digits)
10017            if (!/^[0-7]{1,10}$/.test(number)) {
10018                return error.num;
10019            }
10020    
10021            // Convert octal number to decimal
10022            var decimal = parseInt(number, 8);
10023    
10024            // Return decimal number
10025            return (decimal >= 536870912) ? decimal - 1073741824 : decimal;
10026        };
10027    
10028        exports.OCT2HEX = function(number, places) {
10029            // Return error if number is not octal or contains more than ten
10030            // characters (10 digits)
10031            if (!/^[0-7]{1,10}$/.test(number)) {
10032                return error.num;
10033            }
10034    
10035            // Convert octal number to decimal
10036            var decimal = parseInt(number, 8);
10037    
10038            // Ignore places and return a 10-character octal number if number is
10039            // negative
10040            if (decimal >= 536870912) {
10041                return 'ff' + (decimal + 3221225472).toString(16);
10042            }
10043    
10044            // Convert decimal number to hexadecimal
10045            var result = decimal.toString(16);
10046    
10047            // Return hexadecimal number using the minimum number of characters
10048            // necessary if places is undefined
10049            if (places === undefined) {
10050                return result;
10051            } else {
10052                // Return error if places is nonnumeric
10053                if (isNaN(places)) {
10054                  return error.value;
10055                }
10056    
10057                // Return error if places is negative
10058                if (places < 0) {
10059                  return error.num;
10060                }
10061    
10062                // Truncate places in case it is not an integer
10063                places = Math.floor(places);
10064    
10065                // Pad return value with leading 0s (zeros) if necessary (using
10066                // Underscore.string)
10067                return (places >= result.length) ? REPT('0', places - result.length) + result : error.num;
10068            }
10069        };
10070    
10071        return exports;
10072    })();
10073    
10074    jexcel.methods.financial = (function() {
10075        var exports = {};
10076    
10077        function validDate(d) {
10078            return d && d.getTime && !isNaN(d.getTime());
10079        }
10080    
10081        function ensureDate(d) {
10082            return (d instanceof Date)?d:new Date(d);
10083        }
10084    
10085        exports.ACCRINT = function(issue, first, settlement, rate, par, frequency, basis) {
10086            // Return error if either date is invalid
10087            issue        = ensureDate(issue);
10088            first        = ensureDate(first);
10089            settlement = ensureDate(settlement);
10090            if (!validDate(issue) || !validDate(first) || !validDate(settlement)) {
10091                return '#VALUE!';
10092            }
10093    
10094            // Return error if either rate or par are lower than or equal to zero
10095            if (rate <= 0 || par <= 0) {
10096                return '#NUM!';
10097            }
10098    
10099            // Return error if frequency is neither 1, 2, or 4
10100            if ([1, 2, 4].indexOf(frequency) === -1) {
10101                return '#NUM!';
10102            }
10103    
10104            // Return error if basis is neither 0, 1, 2, 3, or 4
10105            if ([0, 1, 2, 3, 4].indexOf(basis) === -1) {
10106                return '#NUM!';
10107            }
10108    
10109            // Return error if settlement is before or equal to issue
10110            if (settlement <= issue) {
10111                return '#NUM!';
10112            }
10113    
10114            // Set default values
10115            par   = par   || 0;
10116            basis = basis || 0;
10117    
10118            // Compute accrued interest
10119            return par * rate * YEARFRAC(issue, settlement, basis);
10120        };
10121    
10122        exports.ACCRINTM = null;
10123    
10124        exports.AMORDEGRC = null;
10125    
10126        exports.AMORLINC = null;
10127    
10128        exports.COUPDAYBS = null;
10129    
10130        exports.COUPDAYS = null;
10131    
10132        exports.COUPDAYSNC = null;
10133    
10134        exports.COUPNCD = null;
10135    
10136        exports.COUPNUM = null;
10137    
10138        exports.COUPPCD = null;
10139    
10140        exports.CUMIPMT = function(rate, periods, value, start, end, type) {
10141            // Credits: algorithm inspired by Apache OpenOffice
10142            // Credits: Hannes Stiebitzhofer for the translations of function and
10143                // variable names
10144            // Requires exports.FV() and exports.PMT() from exports.js
10145                // [http://stoic.com/exports/]
10146    
10147            rate = utils.parseNumber(rate);
10148            periods = utils.parseNumber(periods);
10149            value = utils.parseNumber(value);
10150            if (utils.anyIsError(rate, periods, value)) {
10151                return error.value;
10152            }
10153    
10154            // Return error if either rate, periods, or value are lower than or
10155                // equal to zero
10156            if (rate <= 0 || periods <= 0 || value <= 0) {
10157                return error.num;
10158            }
10159    
10160            // Return error if start < 1, end < 1, or start > end
10161            if (start < 1 || end < 1 || start > end) {
10162                return error.num;
10163            }
10164    
10165            // Return error if type is neither 0 nor 1
10166            if (type !== 0 && type !== 1) {
10167                return error.num;
10168            }
10169    
10170            // Compute cumulative interest
10171            var payment = exports.PMT(rate, periods, value, 0, type);
10172            var interest = 0;
10173    
10174            if (start === 1) {
10175                if (type === 0) {
10176                    interest = -value;
10177                    start++;
10178                }
10179            }
10180    
10181            for (var i = start; i <= end; i++) {
10182                if (type === 1) {
10183                    interest += exports.FV(rate, i - 2, payment, value, 1) - payment;
10184                } else {
10185                    interest += exports.FV(rate, i - 1, payment, value, 0);
10186                }
10187            }
10188            interest *= rate;
10189    
10190            // Return cumulative interest
10191            return interest;
10192        };
10193    
10194        exports.CUMPRINC = function(rate, periods, value, start, end, type) {
10195            // Credits: algorithm inspired by Apache OpenOffice
10196            // Credits: Hannes Stiebitzhofer for the translations of function and
10197                // variable names
10198    
10199            rate = utils.parseNumber(rate);
10200            periods = utils.parseNumber(periods);
10201            value = utils.parseNumber(value);
10202            if (utils.anyIsError(rate, periods, value)) {
10203                return error.value;
10204            }
10205    
10206            // Return error if either rate, periods, or value are lower than or
10207                // equal to zero
10208            if (rate <= 0 || periods <= 0 || value <= 0) {
10209                return error.num;
10210            }
10211    
10212            // Return error if start < 1, end < 1, or start > end
10213            if (start < 1 || end < 1 || start > end) {
10214                return error.num;
10215            }
10216    
10217            // Return error if type is neither 0 nor 1
10218            if (type !== 0 && type !== 1) {
10219                return error.num;
10220            }
10221    
10222            // Compute cumulative principal
10223            var payment = exports.PMT(rate, periods, value, 0, type);
10224            var principal = 0;
10225            if (start === 1) {
10226                if (type === 0) {
10227                    principal = payment + value * rate;
10228                } else {
10229                    principal = payment;
10230                }
10231                start++;
10232            }
10233            for (var i = start; i <= end; i++) {
10234                if (type > 0) {
10235                    principal += payment - (exports.FV(rate, i - 2, payment, value, 1) - payment) * rate;
10236                } else {
10237                    principal += payment - exports.FV(rate, i - 1, payment, value, 0) * rate;
10238                }
10239            }
10240    
10241            // Return cumulative principal
10242            return principal;
10243        };
10244    
10245        exports.DB = function(cost, salvage, life, period, month) {
10246            // Initialize month
10247            month = (month === undefined) ? 12 : month;
10248    
10249            cost = utils.parseNumber(cost);
10250            salvage = utils.parseNumber(salvage);
10251            life = utils.parseNumber(life);
10252            period = utils.parseNumber(period);
10253            month = utils.parseNumber(month);
10254            if (utils.anyIsError(cost, salvage, life, period, month)) {
10255                return error.value;
10256            }
10257    
10258            // Return error if any of the parameters is negative
10259            if (cost < 0 || salvage < 0 || life < 0 || period < 0) {
10260                return error.num;
10261            }
10262    
10263            // Return error if month is not an integer between 1 and 12
10264            if ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].indexOf(month) === -1) {
10265                return error.num;
10266            }
10267    
10268            // Return error if period is greater than life
10269            if (period > life) {
10270                return error.num;
10271            }
10272    
10273            // Return 0 (zero) if salvage is greater than or equal to cost
10274            if (salvage >= cost) {
10275                return 0;
10276            }
10277    
10278            // Rate is rounded to three decimals places
10279            var rate = (1 - Math.pow(salvage / cost, 1 / life)).toFixed(3);
10280    
10281            // Compute initial depreciation
10282            var initial = cost * rate * month / 12;
10283    
10284            // Compute total depreciation
10285            var total = initial;
10286            var current = 0;
10287            var ceiling = (period === life) ? life - 1 : period;
10288            for (var i = 2; i <= ceiling; i++) {
10289                current = (cost - total) * rate;
10290                total += current;
10291            }
10292    
10293            // Depreciation for the first and last periods are special cases
10294            if (period === 1) {
10295                // First period
10296                return initial;
10297            } else if (period === life) {
10298                // Last period
10299                return (cost - total) * rate;
10300            } else {
10301                return current;
10302            }
10303        };
10304    
10305        exports.DDB = function(cost, salvage, life, period, factor) {
10306            // Initialize factor
10307            factor = (factor === undefined) ? 2 : factor;
10308    
10309            cost = utils.parseNumber(cost);
10310            salvage = utils.parseNumber(salvage);
10311            life = utils.parseNumber(life);
10312            period = utils.parseNumber(period);
10313            factor = utils.parseNumber(factor);
10314            if (utils.anyIsError(cost, salvage, life, period, factor)) {
10315                return error.value;
10316            }
10317    
10318            // Return error if any of the parameters is negative or if factor is
10319                // null
10320            if (cost < 0 || salvage < 0 || life < 0 || period < 0 || factor <= 0) {
10321                return error.num;
10322            }
10323    
10324            // Return error if period is greater than life
10325            if (period > life) {
10326                return error.num;
10327            }
10328    
10329            // Return 0 (zero) if salvage is greater than or equal to cost
10330            if (salvage >= cost) {
10331                return 0;
10332            }
10333    
10334            // Compute depreciation
10335            var total = 0;
10336            var current = 0;
10337            for (var i = 1; i <= period; i++) {
10338                current = Math.min((cost - total) * (factor / life), (cost - salvage - total));
10339                total += current;
10340            }
10341    
10342            // Return depreciation
10343            return current;
10344        };
10345    
10346        exports.DISC = null;
10347    
10348        exports.DOLLARDE = function(dollar, fraction) {
10349            // Credits: algorithm inspired by Apache OpenOffice
10350    
10351            dollar = utils.parseNumber(dollar);
10352            fraction = utils.parseNumber(fraction);
10353            if (utils.anyIsError(dollar, fraction)) {
10354                return error.value;
10355            }
10356    
10357            // Return error if fraction is negative
10358            if (fraction < 0) {
10359                return error.num;
10360            }
10361    
10362            // Return error if fraction is greater than or equal to 0 and less than
10363                // 1
10364            if (fraction >= 0 && fraction < 1) {
10365                return error.div0;
10366            }
10367    
10368            // Truncate fraction if it is not an integer
10369            fraction = parseInt(fraction, 10);
10370    
10371            // Compute integer part
10372            var result = parseInt(dollar, 10);
10373    
10374            // Add decimal part
10375            result += (dollar % 1) * Math.pow(10, Math.ceil(Math.log(fraction) / Math.LN10)) / fraction;
10376    
10377            // Round result
10378            var power = Math.pow(10, Math.ceil(Math.log(fraction) / Math.LN2) + 1);
10379            result = Math.round(result * power) / power;
10380    
10381            // Return converted dollar price
10382            return result;
10383        };
10384    
10385        exports.DOLLARFR = function(dollar, fraction) {
10386            // Credits: algorithm inspired by Apache OpenOffice
10387    
10388            dollar = utils.parseNumber(dollar);
10389            fraction = utils.parseNumber(fraction);
10390            if (utils.anyIsError(dollar, fraction)) {
10391                return error.value;
10392            }
10393    
10394            // Return error if fraction is negative
10395            if (fraction < 0) {
10396                return error.num;
10397            }
10398    
10399            // Return error if fraction is greater than or equal to 0 and less than
10400                // 1
10401            if (fraction >= 0 && fraction < 1) {
10402                return error.div0;
10403            }
10404    
10405            // Truncate fraction if it is not an integer
10406            fraction = parseInt(fraction, 10);
10407    
10408            // Compute integer part
10409            var result = parseInt(dollar, 10);
10410    
10411            // Add decimal part
10412            result += (dollar % 1) * Math.pow(10, -Math.ceil(Math.log(fraction) / Math.LN10)) * fraction;
10413    
10414            // Return converted dollar price
10415            return result;
10416        };
10417    
10418        exports.DURATION = null;
10419    
10420        exports.EFFECT = function(rate, periods) {
10421            rate = utils.parseNumber(rate);
10422            periods = utils.parseNumber(periods);
10423            if (utils.anyIsError(rate, periods)) {
10424                return error.value;
10425            }
10426    
10427            // Return error if rate <=0 or periods < 1
10428            if (rate <= 0 || periods < 1) {
10429                return error.num;
10430            }
10431    
10432            // Truncate periods if it is not an integer
10433            periods = parseInt(periods, 10);
10434    
10435            // Return effective annual interest rate
10436            return Math.pow(1 + rate / periods, periods) - 1;
10437        };
10438    
10439        exports.FV = function(rate, periods, payment, value, type) {
10440            // Credits: algorithm inspired by Apache OpenOffice
10441    
10442            value = value || 0;
10443            type = type || 0;
10444    
10445            rate = utils.parseNumber(rate);
10446            periods = utils.parseNumber(periods);
10447            payment = utils.parseNumber(payment);
10448            value = utils.parseNumber(value);
10449            type = utils.parseNumber(type);
10450            if (utils.anyIsError(rate, periods, payment, value, type)) {
10451                return error.value;
10452            }
10453    
10454            // Return future value
10455            var result;
10456            if (rate === 0) {
10457                result = value + payment * periods;
10458            } else {
10459                var term = Math.pow(1 + rate, periods);
10460                if (type === 1) {
10461                    result = value * term + payment * (1 + rate) * (term - 1) / rate;
10462                } else {
10463                    result = value * term + payment * (term - 1) / rate;
10464                }
10465            }
10466            return -result;
10467        };
10468    
10469        exports.FVSCHEDULE = function(principal, schedule) {
10470            principal = utils.parseNumber(principal);
10471            schedule = utils.parseNumberArray(utils.flatten(schedule));
10472            if (utils.anyIsError(principal, schedule)) {
10473                return error.value;
10474            }
10475    
10476            var n = schedule.length;
10477            var future = principal;
10478    
10479            // Apply all interests in schedule
10480            for (var i = 0; i < n; i++) {
10481                // Apply scheduled interest
10482                future *= 1 + schedule[i];
10483            }
10484    
10485            // Return future value
10486            return future;
10487        };
10488    
10489        exports.INTRATE = null;
10490    
10491        exports.IPMT = function(rate, period, periods, present, future, type) {
10492            // Credits: algorithm inspired by Apache OpenOffice
10493    
10494            future = future || 0;
10495            type = type || 0;
10496    
10497            rate = utils.parseNumber(rate);
10498            period = utils.parseNumber(period);
10499            periods = utils.parseNumber(periods);
10500            present = utils.parseNumber(present);
10501            future = utils.parseNumber(future);
10502            type = utils.parseNumber(type);
10503            if (utils.anyIsError(rate, period, periods, present, future, type)) {
10504                return error.value;
10505            }
10506    
10507            // Compute payment
10508            var payment = exports.PMT(rate, periods, present, future, type);
10509    
10510            // Compute interest
10511            var interest;
10512            if (period === 1) {
10513                if (type === 1) {
10514                    interest = 0;
10515                } else {
10516                    interest = -present;
10517                }
10518            } else {
10519                if (type === 1) {
10520                    interest = exports.FV(rate, period - 2, payment, present, 1) - payment;
10521                } else {
10522                    interest = exports.FV(rate, period - 1, payment, present, 0);
10523                }
10524            }
10525    
10526            // Return interest
10527            return interest * rate;
10528        };
10529    
10530        exports.IRR = function(values, guess) {
10531            // Credits: algorithm inspired by Apache OpenOffice
10532    
10533            guess = guess || 0;
10534    
10535            values = utils.parseNumberArray(utils.flatten(values));
10536            guess = utils.parseNumber(guess);
10537            if (utils.anyIsError(values, guess)) {
10538                return error.value;
10539            }
10540    
10541            // Calculates the resulting amount
10542            var irrResult = function(values, dates, rate) {
10543                var r = rate + 1;
10544                var result = values[0];
10545                for (var i = 1; i < values.length; i++) {
10546                    result += values[i] / Math.pow(r, (dates[i] - dates[0]) / 365);
10547                }
10548                return result;
10549            };
10550    
10551            // Calculates the first derivation
10552            var irrResultDeriv = function(values, dates, rate) {
10553                var r = rate + 1;
10554                var result = 0;
10555                for (var i = 1; i < values.length; i++) {
10556                    var frac = (dates[i] - dates[0]) / 365;
10557                    result -= frac * values[i] / Math.pow(r, frac + 1);
10558                }
10559                return result;
10560            };
10561    
10562            // Initialize dates and check that values contains at least one positive
10563                // value and one negative value
10564            var dates = [];
10565            var positive = false;
10566            var negative = false;
10567            for (var i = 0; i < values.length; i++) {
10568                dates[i] = (i === 0) ? 0 : dates[i - 1] + 365;
10569                if (values[i] > 0) {
10570                    positive = true;
10571                }
10572                if (values[i] < 0) {
10573                    negative = true;
10574                }
10575            }
10576    
10577            // Return error if values does not contain at least one positive value
10578                // and one negative value
10579            if (!positive || !negative) {
10580                return error.num;
10581            }
10582    
10583            // Initialize guess and resultRate
10584            guess = (guess === undefined) ? 0.1 : guess;
10585            var resultRate = guess;
10586    
10587            // Set maximum epsilon for end of iteration
10588            var epsMax = 1e-10;
10589    
10590            // Implement Newton's method
10591            var newRate, epsRate, resultValue;
10592            var contLoop = true;
10593            do {
10594                resultValue = irrResult(values, dates, resultRate);
10595                newRate = resultRate - resultValue / irrResultDeriv(values, dates, resultRate);
10596                epsRate = Math.abs(newRate - resultRate);
10597                resultRate = newRate;
10598                contLoop = (epsRate > epsMax) && (Math.abs(resultValue) > epsMax);
10599            } while (contLoop);
10600    
10601            // Return internal rate of return
10602            return resultRate;
10603        };
10604    
10605        exports.ISPMT = function(rate, period, periods, value) {
10606            rate = utils.parseNumber(rate);
10607            period = utils.parseNumber(period);
10608            periods = utils.parseNumber(periods);
10609            value = utils.parseNumber(value);
10610            if (utils.anyIsError(rate, period, periods, value)) {
10611                return error.value;
10612            }
10613    
10614            // Return interest
10615            return value * rate * (period / periods - 1);
10616        };
10617    
10618        exports.MDURATION = null;
10619    
10620        exports.MIRR = function(values, finance_rate, reinvest_rate) {
10621            values = utils.parseNumberArray(utils.flatten(values));
10622            finance_rate = utils.parseNumber(finance_rate);
10623            reinvest_rate = utils.parseNumber(reinvest_rate);
10624            if (utils.anyIsError(values, finance_rate, reinvest_rate)) {
10625                return error.value;
10626            }
10627    
10628            // Initialize number of values
10629            var n = values.length;
10630    
10631            // Lookup payments (negative values) and incomes (positive values)
10632            var payments = [];
10633            var incomes = [];
10634            for (var i = 0; i < n; i++) {
10635                if (values[i] < 0) {
10636                    payments.push(values[i]);
10637                } else {
10638                    incomes.push(values[i]);
10639                }
10640            }
10641    
10642            // Return modified internal rate of return
10643            var num = -exports.NPV(reinvest_rate, incomes) * Math.pow(1 + reinvest_rate, n - 1);
10644            var den = exports.NPV(finance_rate, payments) * (1 + finance_rate);
10645            return Math.pow(num / den, 1 / (n - 1)) - 1;
10646        };
10647    
10648        exports.NOMINAL = function(rate, periods) {
10649            rate = utils.parseNumber(rate);
10650            periods = utils.parseNumber(periods);
10651            if (utils.anyIsError(rate, periods)) {
10652                return error.value;
10653            }
10654    
10655            // Return error if rate <=0 or periods < 1
10656            if (rate <= 0 || periods < 1) {
10657                return error.num;
10658            }
10659    
10660            // Truncate periods if it is not an integer
10661            periods = parseInt(periods, 10);
10662    
10663            // Return nominal annual interest rate
10664            return (Math.pow(rate + 1, 1 / periods) - 1) * periods;
10665        };
10666    
10667        exports.NPER = function(rate, payment, present, future, type) {
10668            type = (type === undefined) ? 0 : type;
10669            future = (future === undefined) ? 0 : future;
10670    
10671            rate = utils.parseNumber(rate);
10672            payment = utils.parseNumber(payment);
10673            present = utils.parseNumber(present);
10674            future = utils.parseNumber(future);
10675            type = utils.parseNumber(type);
10676            if (utils.anyIsError(rate, payment, present, future, type)) {
10677                return error.value;
10678            }
10679    
10680            // Return number of periods
10681            var num = payment * (1 + rate * type) - future * rate;
10682            var den = (present * rate + payment * (1 + rate * type));
10683            return Math.log(num / den) / Math.log(1 + rate);
10684        };
10685    
10686        exports.NPV = function() {
10687            var args = utils.parseNumberArray(utils.flatten(arguments));
10688            if (args instanceof Error) {
10689                return args;
10690            }
10691    
10692            // Lookup rate
10693            var rate = args[0];
10694    
10695            // Initialize net present value
10696            var value = 0;
10697    
10698            // Loop on all values
10699            for (var j = 1; j < args.length; j++) {
10700                value += args[j] / Math.pow(1 + rate, j);
10701            }
10702    
10703            // Return net present value
10704            return value;
10705        };
10706    
10707        exports.ODDFPRICE = null;
10708    
10709        exports.ODDFYIELD = null;
10710    
10711        exports.ODDLPRICE = null;
10712    
10713        exports.ODDLYIELD = null;
10714    
10715        exports.PDURATION = function(rate, present, future) {
10716            rate = utils.parseNumber(rate);
10717            present = utils.parseNumber(present);
10718            future = utils.parseNumber(future);
10719            if (utils.anyIsError(rate, present, future)) {
10720                return error.value;
10721            }
10722    
10723            // Return error if rate <=0
10724            if (rate <= 0) {
10725                return error.num;
10726            }
10727    
10728            // Return number of periods
10729            return (Math.log(future) - Math.log(present)) / Math.log(1 + rate);
10730        };
10731    
10732        exports.PMT = function(rate, periods, present, future, type) {
10733            // Credits: algorithm inspired by Apache OpenOffice
10734    
10735            future = future || 0;
10736            type = type || 0;
10737    
10738            rate = utils.parseNumber(rate);
10739            periods = utils.parseNumber(periods);
10740            present = utils.parseNumber(present);
10741            future = utils.parseNumber(future);
10742            type = utils.parseNumber(type);
10743            if (utils.anyIsError(rate, periods, present, future, type)) {
10744                return error.value;
10745            }
10746    
10747            // Return payment
10748            var result;
10749            if (rate === 0) {
10750                result = (present + future) / periods;
10751            } else {
10752                var term = Math.pow(1 + rate, periods);
10753                if (type === 1) {
10754                    result = (future * rate / (term - 1) + present * rate / (1 - 1 / term)) / (1 + rate);
10755                } else {
10756                    result = future * rate / (term - 1) + present * rate / (1 - 1 / term);
10757                }
10758            }
10759            return -result;
10760        };
10761    
10762        exports.PPMT = function(rate, period, periods, present, future, type) {
10763            future = future || 0;
10764            type = type || 0;
10765    
10766            rate = utils.parseNumber(rate);
10767            periods = utils.parseNumber(periods);
10768            present = utils.parseNumber(present);
10769            future = utils.parseNumber(future);
10770            type = utils.parseNumber(type);
10771            if (utils.anyIsError(rate, periods, present, future, type)) {
10772                return error.value;
10773            }
10774    
10775            return exports.PMT(rate, periods, present, future, type) - exports.IPMT(rate, period, periods, present, future, type);
10776        };
10777    
10778        exports.PRICE = null;
10779    
10780        exports.PRICEDISC = null;
10781    
10782        exports.PRICEMAT = null;
10783    
10784        exports.PV = function(rate, periods, payment, future, type) {
10785            future = future || 0;
10786            type = type || 0;
10787    
10788            rate = utils.parseNumber(rate);
10789            periods = utils.parseNumber(periods);
10790            payment = utils.parseNumber(payment);
10791            future = utils.parseNumber(future);
10792            type = utils.parseNumber(type);
10793            if (utils.anyIsError(rate, periods, payment, future, type)) {
10794                return error.value;
10795            }
10796    
10797            // Return present value
10798            if (rate === 0) {
10799                return -payment * periods - future;
10800            } else {
10801                return (((1 - Math.pow(1 + rate, periods)) / rate) * payment * (1 + rate * type) - future) / Math.pow(1 + rate, periods);
10802            }
10803        };
10804    
10805        exports.RATE = function(periods, payment, present, future, type, guess) {
10806            // Credits: rabugento
10807    
10808            guess = (guess === undefined) ? 0.01 : guess;
10809            future = (future === undefined) ? 0 : future;
10810            type = (type === undefined) ? 0 : type;
10811    
10812            periods = utils.parseNumber(periods);
10813            payment = utils.parseNumber(payment);
10814            present = utils.parseNumber(present);
10815            future = utils.parseNumber(future);
10816            type = utils.parseNumber(type);
10817            guess = utils.parseNumber(guess);
10818            if (utils.anyIsError(periods, payment, present, future, type, guess)) {
10819                return error.value;
10820            }
10821    
10822            // Set maximum epsilon for end of iteration
10823            var epsMax = 1e-6;
10824    
10825            // Set maximum number of iterations
10826            var iterMax = 100;
10827            var iter = 0;
10828            var close = false;
10829            var rate = guess;
10830    
10831            while (iter < iterMax && !close) {
10832                var t1 = Math.pow(rate + 1, periods);
10833                var t2 = Math.pow(rate + 1, periods - 1);
10834    
10835                var f1 = future + t1 * present + payment * (t1 - 1) * (rate * type + 1) / rate;
10836                var f2 = periods * t2 * present - payment * (t1 - 1) *(rate * type + 1) / Math.pow(rate,2);
10837                var f3 = periods * payment * t2 * (rate * type + 1) / rate + payment * (t1 - 1) * type / rate;
10838    
10839                var newRate = rate - f1 / (f2 + f3);
10840    
10841                if (Math.abs(newRate - rate) < epsMax) close = true;
10842                iter++
10843                rate = newRate;
10844            }
10845    
10846            if (!close) return Number.NaN + rate;
10847            return rate;
10848        };
10849    
10850        // TODO
10851        exports.RECEIVED = null;
10852    
10853        exports.RRI = function(periods, present, future) {
10854            periods = utils.parseNumber(periods);
10855            present = utils.parseNumber(present);
10856            future = utils.parseNumber(future);
10857            if (utils.anyIsError(periods, present, future)) {
10858                return error.value;
10859            }
10860    
10861            // Return error if periods or present is equal to 0 (zero)
10862            if (periods === 0 || present === 0) {
10863                return error.num;
10864            }
10865    
10866            // Return equivalent interest rate
10867            return Math.pow(future / present, 1 / periods) - 1;
10868        };
10869    
10870        exports.SLN = function(cost, salvage, life) {
10871            cost = utils.parseNumber(cost);
10872            salvage = utils.parseNumber(salvage);
10873            life = utils.parseNumber(life);
10874            if (utils.anyIsError(cost, salvage, life)) {
10875                return error.value;
10876            }
10877    
10878            // Return error if life equal to 0 (zero)
10879            if (life === 0) {
10880                return error.num;
10881            }
10882    
10883            // Return straight-line depreciation
10884            return (cost - salvage) / life;
10885        };
10886    
10887        exports.SYD = function(cost, salvage, life, period) {
10888            // Return error if any of the parameters is not a number
10889            cost = utils.parseNumber(cost);
10890            salvage = utils.parseNumber(salvage);
10891            life = utils.parseNumber(life);
10892            period = utils.parseNumber(period);
10893            if (utils.anyIsError(cost, salvage, life, period)) {
10894                return error.value;
10895            }
10896    
10897            // Return error if life equal to 0 (zero)
10898            if (life === 0) {
10899                return error.num;
10900            }
10901    
10902            // Return error if period is lower than 1 or greater than life
10903            if (period < 1 || period > life) {
10904                return error.num;
10905            }
10906    
10907            // Truncate period if it is not an integer
10908            period = parseInt(period, 10);
10909    
10910            // Return straight-line depreciation
10911            return ((cost - salvage) * (life - period + 1) * 2) / (life * (life + 1));
10912        };
10913    
10914        exports.TBILLEQ = function(settlement, maturity, discount) {
10915            settlement = utils.parseDate(settlement);
10916            maturity = utils.parseDate(maturity);
10917            discount = utils.parseNumber(discount);
10918            if (utils.anyIsError(settlement, maturity, discount)) {
10919                return error.value;
10920            }
10921    
10922            // Return error if discount is lower than or equal to zero
10923            if (discount <= 0) {
10924                return error.num;
10925            }
10926    
10927            // Return error if settlement is greater than maturity
10928            if (settlement > maturity) {
10929                return error.num;
10930            }
10931    
10932            // Return error if maturity is more than one year after settlement
10933            if (maturity - settlement > 365 * 24 * 60 * 60 * 1000) {
10934                return error.num;
10935            }
10936    
10937            // Return bond-equivalent yield
10938            return (365 * discount) / (360 - discount * DAYS360(settlement, maturity, false));
10939        };
10940    
10941        exports.TBILLPRICE = function(settlement, maturity, discount) {
10942            settlement = utils.parseDate(settlement);
10943            maturity = utils.parseDate(maturity);
10944            discount = utils.parseNumber(discount);
10945            if (utils.anyIsError(settlement, maturity, discount)) {
10946                return error.value;
10947            }
10948    
10949            // Return error if discount is lower than or equal to zero
10950            if (discount <= 0) {
10951                return error.num;
10952            }
10953    
10954            // Return error if settlement is greater than maturity
10955            if (settlement > maturity) {
10956                return error.num;
10957            }
10958    
10959            // Return error if maturity is more than one year after settlement
10960            if (maturity - settlement > 365 * 24 * 60 * 60 * 1000) {
10961                return error.num;
10962            }
10963    
10964            // Return bond-equivalent yield
10965            return 100 * (1 - discount * DAYS360(settlement, maturity, false) / 360);
10966        };
10967    
10968        exports.TBILLYIELD = function(settlement, maturity, price) {
10969            settlement = utils.parseDate(settlement);
10970            maturity = utils.parseDate(maturity);
10971            price = utils.parseNumber(price);
10972            if (utils.anyIsError(settlement, maturity, price)) {
10973                return error.value;
10974            }
10975    
10976            // Return error if price is lower than or equal to zero
10977            if (price <= 0) {
10978                return error.num;
10979            }
10980    
10981            // Return error if settlement is greater than maturity
10982            if (settlement > maturity) {
10983                return error.num;
10984            }
10985    
10986            // Return error if maturity is more than one year after settlement
10987            if (maturity - settlement > 365 * 24 * 60 * 60 * 1000) {
10988                return error.num;
10989            }
10990    
10991            // Return bond-equivalent yield
10992            return (100 - price) * 360 / (price * DAYS360(settlement, maturity, false));
10993        };
10994    
10995        exports.VDB = null;
10996    
10997        exports.XIRR = function(values, dates, guess) {
10998            // Credits: algorithm inspired by Apache OpenOffice
10999    
11000            values = utils.parseNumberArray(utils.flatten(values));
11001            dates = utils.parseDateArray(utils.flatten(dates));
11002            guess = utils.parseNumber(guess);
11003            if (utils.anyIsError(values, dates, guess)) {
11004                return error.value;
11005            }
11006    
11007            // Calculates the resulting amount
11008            var irrResult = function(values, dates, rate) {
11009                var r = rate + 1;
11010                var result = values[0];
11011                for (var i = 1; i < values.length; i++) {
11012                    result += values[i] / Math.pow(r, DAYS(dates[i], dates[0]) / 365);
11013                }
11014                return result;
11015            };
11016    
11017            // Calculates the first derivation
11018            var irrResultDeriv = function(values, dates, rate) {
11019                var r = rate + 1;
11020                var result = 0;
11021                for (var i = 1; i < values.length; i++) {
11022                    var frac = DAYS(dates[i], dates[0]) / 365;
11023                    result -= frac * values[i] / Math.pow(r, frac + 1);
11024                }
11025                return result;
11026            };
11027    
11028            // Check that values contains at least one positive value and one
11029                // negative value
11030            var positive = false;
11031            var negative = false;
11032            for (var i = 0; i < values.length; i++) {
11033                if (values[i] > 0) {
11034                    positive = true;
11035                }
11036                if (values[i] < 0) {
11037                    negative = true;
11038                }
11039            }
11040    
11041            // Return error if values does not contain at least one positive value
11042                // and one negative value
11043            if (!positive || !negative) {
11044                return error.num;
11045            }
11046    
11047            // Initialize guess and resultRate
11048            guess = guess || 0.1;
11049            var resultRate = guess;
11050    
11051            // Set maximum epsilon for end of iteration
11052            var epsMax = 1e-10;
11053    
11054            // Implement Newton's method
11055            var newRate, epsRate, resultValue;
11056            var contLoop = true;
11057            do {
11058                resultValue = irrResult(values, dates, resultRate);
11059                newRate = resultRate - resultValue / irrResultDeriv(values, dates, resultRate);
11060                epsRate = Math.abs(newRate - resultRate);
11061                resultRate = newRate;
11062                contLoop = (epsRate > epsMax) && (Math.abs(resultValue) > epsMax);
11063            } while (contLoop);
11064    
11065            // Return internal rate of return
11066            return resultRate;
11067        };
11068    
11069        exports.XNPV = function(rate, values, dates) {
11070            rate = utils.parseNumber(rate);
11071            values = utils.parseNumberArray(utils.flatten(values));
11072            dates = utils.parseDateArray(utils.flatten(dates));
11073            if (utils.anyIsError(rate, values, dates)) {
11074                return error.value;
11075            }
11076    
11077            var result = 0;
11078            for (var i = 0; i < values.length; i++) {
11079                result += values[i] / Math.pow(1 + rate, DAYS(dates[i], dates[0]) / 365);
11080            }
11081            return result;
11082        };
11083    
11084        exports.YIELD = null;
11085    
11086        exports.YIELDDISC = null;
11087    
11088        exports.YIELDMAT = null;
11089    
11090        return exports;
11091    })();
11092    
11093    jexcel.methods.information = (function() {
11094        var exports = {};
11095        exports.CELL = null;
11096    
11097        exports.ERROR = {};
11098        exports.ERROR.TYPE = function(error_val) {
11099            switch (error_val) {
11100                case error.nil: return 1;
11101                case error.div0: return 2;
11102                case error.value: return 3;
11103                case error.ref: return 4;
11104                case error.name: return 5;
11105                case error.num: return 6;
11106                case error.na: return 7;
11107                case error.data: return 8;
11108            }
11109            return error.na;
11110        };
11111    
11112        exports.INFO = null;
11113    
11114        exports.ISBLANK = function(value) {
11115            return value === null;
11116        };
11117    
11118        exports.ISBINARY = function (number) {
11119            return (/^[01]{1,10}$/).test(number);
11120        };
11121    
11122        exports.ISERR = function(value) {
11123            return ([error.value, error.ref, error.div0, error.num, error.name, error.nil]).indexOf(value) >= 0 ||
11124                (typeof value === 'number' && (isNaN(value) || !isFinite(value)));
11125        };
11126    
11127        exports.ISERROR = function(value) {
11128            return exports.ISERR(value) || value === error.na;
11129        };
11130    
11131        exports.ISEVEN = function(number) {
11132            return (Math.floor(Math.abs(number)) & 1) ? false : true;
11133        };
11134    
11135        // TODO
11136        exports.ISFORMULA = null;
11137    
11138        exports.ISLOGICAL = function(value) {
11139            return value === true || value === false;
11140        };
11141    
11142        exports.ISNA = function(value) {
11143            return value === error.na;
11144        };
11145    
11146        exports.ISNONTEXT = function(value) {
11147            return typeof(value) !== 'string';
11148        };
11149    
11150        exports.ISNUMBER = function(value) {
11151            return typeof(value) === 'number' && !isNaN(value) && isFinite(value);
11152        };
11153    
11154        exports.ISODD = function(number) {
11155            return (Math.floor(Math.abs(number)) & 1) ? true : false;
11156        };
11157    
11158        exports.ISREF = null;
11159    
11160        exports.ISTEXT = function(value) {
11161            return typeof(value) === 'string';
11162        };
11163    
11164        exports.N = function(value) {
11165            if (this.ISNUMBER(value)) {
11166                return value;
11167            }
11168            if (value instanceof Date) {
11169                return value.getTime();
11170            }
11171            if (value === true) {
11172                return 1;
11173            }
11174            if (value === false) {
11175                return 0;
11176            }
11177            if (this.ISERROR(value)) {
11178                return value;
11179            }
11180            return 0;
11181        };
11182    
11183        exports.NA = function() {
11184            return error.na;
11185        };
11186    
11187        exports.SHEET = null;
11188    
11189        exports.SHEETS = null;
11190    
11191        exports.TYPE = function(value) {
11192            if (this.ISNUMBER(value)) {
11193                return 1;
11194            }
11195            if (this.ISTEXT(value)) {
11196                return 2;
11197            }
11198            if (this.ISLOGICAL(value)) {
11199                return 4;
11200            }
11201            if (this.ISERROR(value)) {
11202                return 16;
11203            }
11204            if (Array.isArray(value)) {
11205                return 64;
11206            }
11207        };
11208    
11209        return exports;
11210    })();
11211    
11212    jexcel.methods.logical = (function() {
11213        var exports = {};
11214    
11215        exports.AND = function() {
11216            var args = utils.flatten(arguments);
11217            var result = true;
11218            for (var i = 0; i < args.length; i++) {
11219                if (!args[i]) {
11220                    result = false;
11221                }
11222            }
11223            return result;
11224        };
11225    
11226        exports.CHOOSE = function() {
11227            if (arguments.length < 2) {
11228                return error.na;
11229            }
11230    
11231            var index = arguments[0];
11232            if (index < 1 || index > 254) {
11233                return error.value;
11234            }
11235    
11236            if (arguments.length < index + 1) {
11237                return error.value;
11238            }
11239    
11240            return arguments[index];
11241        };
11242    
11243        exports.FALSE = function() {
11244            return false;
11245        };
11246    
11247        exports.IF = function(test, then_value, otherwise_value) {
11248            return test ? then_value : otherwise_value;
11249        };
11250    
11251        exports.IFERROR = function(value, valueIfError) {
11252            if (ISERROR(value)) {
11253                return valueIfError;
11254            }
11255            return value;
11256        };
11257    
11258        exports.IFNA = function(value, value_if_na) {
11259            return value === error.na ? value_if_na : value;
11260        };
11261    
11262        exports.NOT = function(logical) {
11263            return !logical;
11264        };
11265    
11266        exports.OR = function() {
11267            var args = utils.flatten(arguments);
11268            var result = false;
11269            for (var i = 0; i < args.length; i++) {
11270                if (args[i]) {
11271                    result = true;
11272                }
11273            }
11274            return result;
11275        };
11276    
11277        exports.TRUE = function() {
11278            return true;
11279        };
11280    
11281        exports.XOR = function() {
11282            var args = utils.flatten(arguments);
11283            var result = 0;
11284            for (var i = 0; i < args.length; i++) {
11285                if (args[i]) {
11286                    result++;
11287                }
11288            }
11289            return (Math.floor(Math.abs(result)) & 1) ? true : false;
11290        };
11291    
11292        exports.SWITCH = function() {
11293            var result;
11294            if (arguments.length > 0)  {
11295                var targetValue = arguments[0];
11296                var argc = arguments.length - 1;
11297                var switchCount = Math.floor(argc / 2);
11298                var switchSatisfied = false;
11299                var defaultClause = argc % 2 === 0 ? null : arguments[arguments.length - 1];
11300    
11301                if (switchCount) {
11302                    for (var index = 0; index < switchCount; index++) {
11303                        if (targetValue === arguments[index * 2 + 1]) {
11304                          result = arguments[index * 2 + 2];
11305                          switchSatisfied = true;
11306                          break;
11307                        }
11308                    }
11309                }
11310    
11311                if (!switchSatisfied && defaultClause) {
11312                    result = defaultClause;
11313                }
11314            }
11315    
11316            return result;
11317        };
11318    
11319        return exports;
11320    })();
11321    
11322    jexcel.methods.math = (function() {
11323        var exports = {};
11324    
11325        exports.ABS = function(number) {
11326            number = utils.parseNumber(number);
11327            if (number instanceof Error) {
11328                return number;
11329            }
11330            return Math.abs(utils.parseNumber(number));
11331        };
11332    
11333        exports.ACOS = function(number) {
11334            number = utils.parseNumber(number);
11335            if (number instanceof Error) {
11336                return number;
11337            }
11338            return Math.acos(number);
11339        };
11340    
11341        exports.ACOSH = function(number) {
11342            number = utils.parseNumber(number);
11343            if (number instanceof Error) {
11344                return number;
11345            }
11346            return Math.log(number + Math.sqrt(number * number - 1));
11347        };
11348    
11349        exports.ACOT = function(number) {
11350            number = utils.parseNumber(number);
11351            if (number instanceof Error) {
11352                return number;
11353            }
11354            return Math.atan(1 / number);
11355        };
11356    
11357        exports.ACOTH = function(number) {
11358            number = utils.parseNumber(number);
11359            if (number instanceof Error) {
11360                return number;
11361            }
11362            return 0.5 * Math.log((number + 1) / (number - 1));
11363        };
11364    
11365        exports.AGGREGATE = null
11366    
11367        exports.ARABIC = function(text) {
11368            // Credits: Rafa? Kukawski
11369            if (!/^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/.test(text)) {
11370                return error.value;
11371            }
11372            var r = 0;
11373            text.replace(/[MDLV]|C[MD]?|X[CL]?|I[XV]?/g, function(i) {
11374                r += {
11375                    M: 1000,
11376                    CM: 900,
11377                    D: 500,
11378                    CD: 400,
11379                    C: 100,
11380                    XC: 90,
11381                    L: 50,
11382                    XL: 40,
11383                    X: 10,
11384                    IX: 9,
11385                    V: 5,
11386                    IV: 4,
11387                    I: 1
11388                }[i];
11389            });
11390            return r;
11391        };
11392    
11393        exports.ASIN = function(number) {
11394            number = utils.parseNumber(number);
11395            if (number instanceof Error) {
11396                return number;
11397            }
11398            return Math.asin(number);
11399        };
11400    
11401        exports.ASINH = function(number) {
11402            number = utils.parseNumber(number);
11403            if (number instanceof Error) {
11404                return number;
11405            }
11406            return Math.log(number + Math.sqrt(number * number + 1));
11407        };
11408    
11409        exports.ATAN = function(number) {
11410            number = utils.parseNumber(number);
11411            if (number instanceof Error) {
11412                return number;
11413            }
11414            return Math.atan(number);
11415        };
11416    
11417        exports.ATAN2 = function(number_x, number_y) {
11418            number_x = utils.parseNumber(number_x);
11419            number_y = utils.parseNumber(number_y);
11420            if (utils.anyIsError(number_x, number_y)) {
11421                return error.value;
11422            }
11423            return Math.atan2(number_x, number_y);
11424        };
11425    
11426        exports.ATANH = function(number) {
11427            number = utils.parseNumber(number);
11428            if (number instanceof Error) {
11429                return number;
11430            }
11431            return Math.log((1 + number) / (1 - number)) / 2;
11432        };
11433    
11434        exports.BASE = function(number, radix, min_length) {
11435            min_length = min_length || 0;
11436    
11437            number = utils.parseNumber(number);
11438            radix = utils.parseNumber(radix);
11439            min_length = utils.parseNumber(min_length);
11440            if (utils.anyIsError(number, radix, min_length)) {
11441                return error.value;
11442            }
11443            min_length = (min_length === undefined) ? 0 : min_length;
11444            var result = number.toString(radix);
11445            return new Array(Math.max(min_length + 1 - result.length, 0)).join('0') + result;
11446        };
11447    
11448        exports.CEILING = function(number, significance, mode) {
11449            significance = (significance === undefined) ? 1 : significance;
11450            mode = (mode === undefined) ? 0 : mode;
11451    
11452            number = utils.parseNumber(number);
11453            significance = utils.parseNumber(significance);
11454            mode = utils.parseNumber(mode);
11455            if (utils.anyIsError(number, significance, mode)) {
11456                return error.value;
11457            }
11458            if (significance === 0) {
11459                return 0;
11460            }
11461    
11462            significance = Math.abs(significance);
11463            if (number >= 0) {
11464                return Math.ceil(number / significance) * significance;
11465            } else {
11466                if (mode === 0) {
11467                    return -1 * Math.floor(Math.abs(number) / significance) * significance;
11468                } else {
11469                    return -1 * Math.ceil(Math.abs(number) / significance) * significance;
11470                }
11471            }
11472        };
11473    
11474        exports.CEILING.MATH = exports.CEILING;
11475    
11476        exports.CEILING.PRECISE = exports.CEILING;
11477    
11478        exports.COMBIN = function(number, number_chosen) {
11479            number = utils.parseNumber(number);
11480            number_chosen = utils.parseNumber(number_chosen);
11481            if (utils.anyIsError(number, number_chosen)) {
11482                return error.value;
11483            }
11484            return exports.FACT(number) / (exports.FACT(number_chosen) * exports.FACT(number - number_chosen));
11485        };
11486    
11487        exports.COMBINA = function(number, number_chosen) {
11488            number = utils.parseNumber(number);
11489            number_chosen = utils.parseNumber(number_chosen);
11490            if (utils.anyIsError(number, number_chosen)) {
11491                return error.value;
11492            }
11493            return (number === 0 && number_chosen === 0) ? 1 : exports.COMBIN(number + number_chosen - 1, number - 1);
11494        };
11495    
11496        exports.COS = function(number) {
11497            number = utils.parseNumber(number);
11498            if (number instanceof Error) {
11499                return number;
11500            }
11501            return Math.cos(number);
11502        };
11503    
11504        exports.COSH = function(number) {
11505            number = utils.parseNumber(number);
11506            if (number instanceof Error) {
11507                return number;
11508            }
11509            return (Math.exp(number) + Math.exp(-number)) / 2;
11510        };
11511    
11512        exports.COT = function(number) {
11513            number = utils.parseNumber(number);
11514            if (number instanceof Error) {
11515                return number;
11516            }
11517            return 1 / Math.tan(number);
11518        };
11519    
11520        exports.COTH = function(number) {
11521            number = utils.parseNumber(number);
11522            if (number instanceof Error) {
11523                return number;
11524            }
11525            var e2 = Math.exp(2 * number);
11526            return (e2 + 1) / (e2 - 1);
11527        };
11528    
11529        exports.CSC = function(number) {
11530            number = utils.parseNumber(number);
11531            if (number instanceof Error) {
11532                return number;
11533            }
11534            return 1 / Math.sin(number);
11535        };
11536    
11537        exports.CSCH = function(number) {
11538            number = utils.parseNumber(number);
11539            if (number instanceof Error) {
11540                return number;
11541            }
11542            return 2 / (Math.exp(number) - Math.exp(-number));
11543        };
11544    
11545        exports.DECIMAL = function(number, radix) {
11546            if (arguments.length < 1) {
11547                return error.value;
11548            }
11549    
11550    
11551            return parseInt(number, radix);
11552        };
11553    
11554        exports.DEGREES = function(number) {
11555            number = utils.parseNumber(number);
11556            if (number instanceof Error) {
11557                return number;
11558            }
11559            return number * 180 / Math.PI;
11560        };
11561    
11562        exports.EVEN = function(number) {
11563            number = utils.parseNumber(number);
11564            if (number instanceof Error) {
11565                return number;
11566            }
11567            return exports.CEILING(number, -2, -1);
11568        };
11569    
11570        exports.EXP = Math.exp;
11571    
11572        var MEMOIZED_FACT = [];
11573        exports.FACT = function(number) {
11574            number = utils.parseNumber(number);
11575            if (number instanceof Error) {
11576                return number;
11577            }
11578            var n = Math.floor(number);
11579            if (n === 0 || n === 1) {
11580                return 1;
11581            } else if (MEMOIZED_FACT[n] > 0) {
11582                return MEMOIZED_FACT[n];
11583            } else {
11584                MEMOIZED_FACT[n] = exports.FACT(n - 1) * n;
11585                return MEMOIZED_FACT[n];
11586            }
11587        };
11588    
11589        exports.FACTDOUBLE = function(number) {
11590            number = utils.parseNumber(number);
11591            if (number instanceof Error) {
11592                return number;
11593            }
11594            var n = Math.floor(number);
11595            if (n <= 0) {
11596                return 1;
11597            } else {
11598                return n * exports.FACTDOUBLE(n - 2);
11599            }
11600        };
11601    
11602        exports.FLOOR = function(number, significance, mode) {
11603            significance = (significance === undefined) ? 1 : significance;
11604            mode = (mode === undefined) ? 0 : mode;
11605    
11606            number = utils.parseNumber(number);
11607            significance = utils.parseNumber(significance);
11608            mode = utils.parseNumber(mode);
11609            if (utils.anyIsError(number, significance, mode)) {
11610                return error.value;
11611            }
11612            if (significance === 0) {
11613                return 0;
11614            }
11615    
11616            significance = Math.abs(significance);
11617            if (number >= 0) {
11618                return Math.floor(number / significance) * significance;
11619            } else {
11620                if (mode === 0) {
11621                    return -1 * Math.ceil(Math.abs(number) / significance) * significance;
11622                } else {
11623                    return -1 * Math.floor(Math.abs(number) / significance) * significance;
11624                }
11625            }
11626        };
11627    
11628        exports.FLOOR.MATH = exports.FLOOR;
11629    
11630        exports.GCD = null;
11631    
11632        exports.INT = function(number) {
11633            number = utils.parseNumber(number);
11634            if (number instanceof Error) {
11635                return number;
11636            }
11637            return Math.floor(number);
11638        };
11639    
11640        exports.LCM = function() {
11641            // Credits: Jonas Raoni Soares Silva
11642            var o = utils.parseNumberArray(utils.flatten(arguments));
11643            if (o instanceof Error) {
11644                return o;
11645            }
11646            for (var i, j, n, d, r = 1;
11647                (n = o.pop()) !== undefined;) {
11648                while (n > 1) {
11649                    if (n % 2) {
11650                        for (i = 3, j = Math.floor(Math.sqrt(n)); i <= j && n % i; i += 2) {
11651                          //empty
11652                        }
11653                        d = (i <= j) ? i : n;
11654                    } else {
11655                        d = 2;
11656                    }
11657                    for (n /= d, r *= d, i = o.length; i;
11658                        (o[--i] % d) === 0 && (o[i] /= d) === 1 && o.splice(i, 1)) {
11659                        //empty
11660                    }
11661                }
11662            }
11663            return r;
11664        };
11665    
11666        exports.LN = function(number) {
11667            number = utils.parseNumber(number);
11668            if (number instanceof Error) {
11669                return number;
11670            }
11671            return Math.log(number);
11672        };
11673    
11674        exports.LOG = function(number, base) {
11675            number = utils.parseNumber(number);
11676            base = (base === undefined) ? 10 : utils.parseNumber(base);
11677    
11678            if (utils.anyIsError(number, base)) {
11679                return error.value;
11680            }
11681    
11682            return Math.log(number) / Math.log(base);
11683        };
11684    
11685        exports.LOG10 = function(number) {
11686            number = utils.parseNumber(number);
11687            if (number instanceof Error) {
11688                return number;
11689            }
11690            return Math.log(number) / Math.log(10);
11691        };
11692    
11693        exports.MDETERM = null;
11694    
11695        exports.MINVERSE = null;
11696    
11697        exports.MMULT = null;
11698    
11699        exports.MOD = function(dividend, divisor) {
11700            dividend = utils.parseNumber(dividend);
11701            divisor = utils.parseNumber(divisor);
11702            if (utils.anyIsError(dividend, divisor)) {
11703                return error.value;
11704            }
11705            if (divisor === 0) {
11706                return error.div0;
11707            }
11708            var modulus = Math.abs(dividend % divisor);
11709            return (divisor > 0) ? modulus : -modulus;
11710        };
11711    
11712        exports.MROUND = function(number, multiple) {
11713            number = utils.parseNumber(number);
11714            multiple = utils.parseNumber(multiple);
11715            if (utils.anyIsError(number, multiple)) {
11716                return error.value;
11717            }
11718            if (number * multiple < 0) {
11719                return error.num;
11720            }
11721    
11722            return Math.round(number / multiple) * multiple;
11723        };
11724    
11725        exports.MULTINOMIAL = function() {
11726            var args = utils.parseNumberArray(utils.flatten(arguments));
11727            if (args instanceof Error) {
11728                return args;
11729            }
11730            var sum = 0;
11731            var divisor = 1;
11732            for (var i = 0; i < args.length; i++) {
11733                sum += args[i];
11734                divisor *= exports.FACT(args[i]);
11735            }
11736            return exports.FACT(sum) / divisor;
11737        };
11738    
11739        exports.MUNIT = null;
11740    
11741        exports.ODD = function(number) {
11742            number = utils.parseNumber(number);
11743            if (number instanceof Error) {
11744                return number;
11745            }
11746            var temp = Math.ceil(Math.abs(number));
11747            temp = (temp & 1) ? temp : temp + 1;
11748            return (number > 0) ? temp : -temp;
11749        };
11750    
11751        exports.PI = function() {
11752            return Math.PI;
11753        };
11754    
11755        exports.POWER = function(number, power) {
11756            number = utils.parseNumber(number);
11757            power = utils.parseNumber(power);
11758            if (utils.anyIsError(number, power)) {
11759                return error.value;
11760            }
11761            var result = Math.pow(number, power);
11762            if (isNaN(result)) {
11763                return error.num;
11764            }
11765    
11766            return result;
11767        };
11768    
11769        exports.PRODUCT = function() {
11770            var args = utils.parseNumberArray(utils.flatten(arguments));
11771            if (args instanceof Error) {
11772                return args;
11773            }
11774            var result = 1;
11775            for (var i = 0; i < args.length; i++) {
11776                result *= args[i];
11777            }
11778            return result;
11779        };
11780    
11781        exports.QUOTIENT = function(numerator, denominator) {
11782            numerator = utils.parseNumber(numerator);
11783            denominator = utils.parseNumber(denominator);
11784            if (utils.anyIsError(numerator, denominator)) {
11785                return error.value;
11786            }
11787            return parseInt(numerator / denominator, 10);
11788        };
11789    
11790        exports.RADIANS = function(number) {
11791            number = utils.parseNumber(number);
11792            if (number instanceof Error) {
11793                return number;
11794            }
11795            return number * Math.PI / 180;
11796        };
11797    
11798        exports.RAND = function() {
11799            return Math.random();
11800        };
11801    
11802        exports.RANDBETWEEN = function(bottom, top) {
11803            bottom = utils.parseNumber(bottom);
11804            top = utils.parseNumber(top);
11805            if (utils.anyIsError(bottom, top)) {
11806                return error.value;
11807            }
11808            // Creative Commons Attribution 3.0 License
11809            // Copyright (c) 2012 eqcode
11810            return bottom + Math.ceil((top - bottom + 1) * Math.random()) - 1;
11811        };
11812    
11813        exports.ROMAN = null;
11814    
11815        exports.ROUND = function(number, digits) {
11816            number = utils.parseNumber(number);
11817            digits = utils.parseNumber(digits);
11818            if (utils.anyIsError(number, digits)) {
11819                return error.value;
11820            }
11821            return Math.round(number * Math.pow(10, digits)) / Math.pow(10, digits);
11822        };
11823    
11824        exports.ROUNDDOWN = function(number, digits) {
11825            number = utils.parseNumber(number);
11826            digits = utils.parseNumber(digits);
11827            if (utils.anyIsError(number, digits)) {
11828                return error.value;
11829            }
11830            var sign = (number > 0) ? 1 : -1;
11831            return sign * (Math.floor(Math.abs(number) * Math.pow(10, digits))) / Math.pow(10, digits);
11832        };
11833    
11834        exports.ROUNDUP = function(number, digits) {
11835            number = utils.parseNumber(number);
11836            digits = utils.parseNumber(digits);
11837            if (utils.anyIsError(number, digits)) {
11838                return error.value;
11839            }
11840            var sign = (number > 0) ? 1 : -1;
11841            return sign * (Math.ceil(Math.abs(number) * Math.pow(10, digits))) / Math.pow(10, digits);
11842        };
11843    
11844        exports.SEC = function(number) {
11845            number = utils.parseNumber(number);
11846            if (number instanceof Error) {
11847                return number;
11848            }
11849            return 1 / Math.cos(number);
11850        };
11851    
11852        exports.SECH = function(number) {
11853            number = utils.parseNumber(number);
11854            if (number instanceof Error) {
11855                return number;
11856            }
11857            return 2 / (Math.exp(number) + Math.exp(-number));
11858        };
11859    
11860        exports.SERIESSUM = function(x, n, m, coefficients) {
11861            x = utils.parseNumber(x);
11862            n = utils.parseNumber(n);
11863            m = utils.parseNumber(m);
11864            coefficients = utils.parseNumberArray(coefficients);
11865            if (utils.anyIsError(x, n, m, coefficients)) {
11866                return error.value;
11867            }
11868            var result = coefficients[0] * Math.pow(x, n);
11869            for (var i = 1; i < coefficients.length; i++) {
11870                result += coefficients[i] * Math.pow(x, n + i * m);
11871            }
11872            return result;
11873        };
11874    
11875        exports.SIGN = function(number) {
11876            number = utils.parseNumber(number);
11877            if (number instanceof Error) {
11878                return number;
11879            }
11880            if (number < 0) {
11881                return -1;
11882            } else if (number === 0) {
11883                return 0;
11884            } else {
11885                return 1;
11886            }
11887        };
11888    
11889        exports.SIN = function(number) {
11890            number = utils.parseNumber(number);
11891            if (number instanceof Error) {
11892                return number;
11893            }
11894            return Math.sin(number);
11895        };
11896    
11897        exports.SINH = function(number) {
11898            number = utils.parseNumber(number);
11899            if (number instanceof Error) {
11900                return number;
11901            }
11902            return (Math.exp(number) - Math.exp(-number)) / 2;
11903        };
11904    
11905        exports.SQRT = function(number) {
11906            number = utils.parseNumber(number);
11907            if (number instanceof Error) {
11908                return number;
11909            }
11910            if (number < 0) {
11911                return error.num;
11912            }
11913            return Math.sqrt(number);
11914        };
11915    
11916        exports.SQRTPI = function(number) {
11917            number = utils.parseNumber(number);
11918            if (number instanceof Error) {
11919                return number;
11920            }
11921            return Math.sqrt(number * Math.PI);
11922        };
11923    
11924        exports.SUBTOTAL = null;
11925    
11926        exports.ADD = function (num1, num2) {
11927            if (arguments.length !== 2) {
11928                return error.na;
11929            }
11930    
11931            num1 = utils.parseNumber(num1);
11932            num2 = utils.parseNumber(num2);
11933            if (utils.anyIsError(num1, num2)) {
11934                return error.value;
11935            }
11936    
11937            return num1 + num2;
11938        };
11939    
11940        exports.MINUS = function (num1, num2) {
11941            if (arguments.length !== 2) {
11942                return error.na;
11943            }
11944    
11945            num1 = utils.parseNumber(num1);
11946            num2 = utils.parseNumber(num2);
11947            if (utils.anyIsError(num1, num2)) {
11948                return error.value;
11949            }
11950    
11951            return num1 - num2;
11952        };
11953    
11954        exports.DIVIDE = function (dividend, divisor) {
11955            if (arguments.length !== 2) {
11956                return error.na;
11957            }
11958    
11959            dividend = utils.parseNumber(dividend);
11960            divisor = utils.parseNumber(divisor);
11961            if (utils.anyIsError(dividend, divisor)) {
11962                return error.value;
11963            }
11964    
11965            if (divisor === 0) {
11966                return error.div0;
11967            }
11968    
11969            return dividend / divisor;
11970        };
11971    
11972        exports.MULTIPLY = function (factor1, factor2) {
11973            if (arguments.length !== 2) {
11974                return error.na;
11975            }
11976    
11977            factor1 = utils.parseNumber(factor1);
11978            factor2 = utils.parseNumber(factor2);
11979            if (utils.anyIsError(factor1, factor2)) {
11980                return error.value;
11981            }
11982    
11983            return factor1 * factor2;
11984        };
11985    
11986        exports.GTE = function (num1, num2) {
11987            if (arguments.length !== 2) {
11988                return error.na;
11989            }
11990    
11991            num1 = utils.parseNumber(num1);
11992            num2 = utils.parseNumber(num2);
11993            if (utils.anyIsError(num1, num2)) {
11994                return error.error;
11995            }
11996    
11997            return num1 >= num2;
11998        };
11999    
12000        exports.LT = function (num1, num2) {
12001            if (arguments.length !== 2) {
12002                return error.na;
12003            }
12004    
12005            num1 = utils.parseNumber(num1);
12006            num2 = utils.parseNumber(num2);
12007            if (utils.anyIsError(num1, num2)) {
12008                return error.error;
12009            }
12010    
12011            return num1 < num2;
12012        };
12013    
12014        exports.LTE = function (num1, num2) {
12015            if (arguments.length !== 2) {
12016                return error.na;
12017            }
12018    
12019            num1 = utils.parseNumber(num1);
12020            num2 = utils.parseNumber(num2);
12021            if (utils.anyIsError(num1, num2)) {
12022                return error.error;
12023            }
12024    
12025            return num1 <= num2;
12026        };
12027    
12028        exports.EQ = function (value1, value2) {
12029            if (arguments.length !== 2) {
12030                return error.na;
12031            }
12032    
12033            return value1 === value2;
12034        };
12035    
12036        exports.NE = function (value1, value2) {
12037            if (arguments.length !== 2) {
12038                return error.na;
12039            }
12040    
12041            return value1 !== value2;
12042        };
12043    
12044        exports.POW = function (base, exponent) {
12045            if (arguments.length !== 2) {
12046                return error.na;
12047            }
12048    
12049            base = utils.parseNumber(base);
12050            exponent = utils.parseNumber(exponent);
12051            if (utils.anyIsError(base, exponent)) {
12052                return error.error;
12053            }
12054    
12055            return exports.POWER(base, exponent);
12056        };
12057    
12058        exports.SUM = function() {
12059            var result = 0;
12060            var argsKeys = Object.keys(arguments);
12061            for (var i = 0; i < argsKeys.length; ++i) {
12062                var elt = arguments[argsKeys[i]];
12063                if (typeof elt === 'number') {
12064                    result += elt;
12065                } else if (typeof elt === 'string') {
12066                    var parsed = parseFloat(elt);
12067                    !isNaN(parsed) && (result += parsed);
12068                } else if (Array.isArray(elt)) {
12069                    result += exports.SUM.apply(null, elt);
12070                }
12071            }
12072            return result;
12073        };
12074    
12075        exports.SUMIF = function(range, criteria) {
12076            range = utils.parseNumberArray(utils.flatten(range));
12077            if (range instanceof Error) {
12078                return range;
12079            }
12080            var result = 0;
12081            for (var i = 0; i < range.length; i++) {
12082                result += (eval(range[i] + criteria)) ? range[i] : 0; // jshint ignore:line
12083            }
12084            return result;
12085        };
12086    
12087        exports.SUMIFS = function() {
12088            var args = utils.argsToArray(arguments);
12089            var range = utils.parseNumberArray(utils.flatten(args.shift()));
12090            if (range instanceof Error) {
12091                return range;
12092            }
12093            var criteria = args;
12094    
12095            var n_range_elements = range.length;
12096            var n_criterias = criteria.length;
12097    
12098            var result = 0;
12099            for (var i = 0; i < n_range_elements; i++) {
12100                var el = range[i];
12101                var condition = '';
12102                for (var c = 0; c < n_criterias; c++) {
12103                    condition += el + criteria[c];
12104                    if (c !== n_criterias - 1) {
12105                        condition += '&&';
12106                    }
12107                }
12108                if (eval(condition)) { // jshint ignore:line
12109                    result += el;
12110                }
12111            }
12112            return result;
12113        };
12114    
12115        exports.SUMPRODUCT = null;
12116    
12117        exports.SUMSQ = function() {
12118            var numbers = utils.parseNumberArray(utils.flatten(arguments));
12119            if (numbers instanceof Error) {
12120                return numbers;
12121            }
12122            var result = 0;
12123            var length = numbers.length;
12124            for (var i = 0; i < length; i++) {
12125                result += (ISNUMBER(numbers[i])) ? numbers[i] * numbers[i] : 0;
12126            }
12127            return result;
12128        };
12129    
12130        exports.SUMX2MY2 = function(array_x, array_y) {
12131            array_x = utils.parseNumberArray(utils.flatten(array_x));
12132            array_y = utils.parseNumberArray(utils.flatten(array_y));
12133            if (utils.anyIsError(array_x, array_y)) {
12134                return error.value;
12135            }
12136            var result = 0;
12137            for (var i = 0; i < array_x.length; i++) {
12138                result += array_x[i] * array_x[i] - array_y[i] * array_y[i];
12139            }
12140            return result;
12141        };
12142    
12143        exports.SUMX2PY2 = function(array_x, array_y) {
12144            array_x = utils.parseNumberArray(utils.flatten(array_x));
12145            array_y = utils.parseNumberArray(utils.flatten(array_y));
12146            if (utils.anyIsError(array_x, array_y)) {
12147                return error.value;
12148            }
12149            var result = 0;
12150            array_x = utils.parseNumberArray(utils.flatten(array_x));
12151            array_y = utils.parseNumberArray(utils.flatten(array_y));
12152            for (var i = 0; i < array_x.length; i++) {
12153                result += array_x[i] * array_x[i] + array_y[i] * array_y[i];
12154            }
12155            return result;
12156        };
12157    
12158        exports.SUMXMY2 = function(array_x, array_y) {
12159            array_x = utils.parseNumberArray(utils.flatten(array_x));
12160            array_y = utils.parseNumberArray(utils.flatten(array_y));
12161            if (utils.anyIsError(array_x, array_y)) {
12162                return error.value;
12163            }
12164            var result = 0;
12165            array_x = utils.flatten(array_x);
12166            array_y = utils.flatten(array_y);
12167            for (var i = 0; i < array_x.length; i++) {
12168                result += Math.pow(array_x[i] - array_y[i], 2);
12169            }
12170            return result;
12171        };
12172    
12173        exports.TAN = function(number) {
12174            number = utils.parseNumber(number);
12175            if (number instanceof Error) {
12176                return number;
12177            }
12178            return Math.tan(number);
12179        };
12180    
12181        exports.TANH = function(number) {
12182            number = utils.parseNumber(number);
12183            if (number instanceof Error) {
12184                return number;
12185            }
12186            var e2 = Math.exp(2 * number);
12187            return (e2 - 1) / (e2 + 1);
12188        };
12189    
12190        exports.TRUNC = function(number, digits) {
12191            digits = (digits === undefined) ? 0 : digits;
12192            number = utils.parseNumber(number);
12193            digits = utils.parseNumber(digits);
12194            if (utils.anyIsError(number, digits)) {
12195                return error.value;
12196            }
12197            var sign = (number > 0) ? 1 : -1;
12198            return sign * (Math.floor(Math.abs(number) * Math.pow(10, digits))) / Math.pow(10, digits);
12199        };
12200    
12201        return exports;
12202    })();
12203    
12204    jexcel.methods.misc = (function() {
12205        var exports = {};
12206    
12207        exports.UNIQUE = function () {
12208            var result = [];
12209            for (var i = 0; i < arguments.length; ++i) {
12210                var hasElement = false;
12211                var element = arguments[i];
12212    
12213                // Check if we've already seen this element.
12214                for (var j = 0; j < result.length; ++j) {
12215                    hasElement = result[j] === element;
12216                    if (hasElement) { break; }
12217                }
12218    
12219                // If we did not find it, add it to the result.
12220                if (!hasElement) {
12221                    result.push(element);
12222                }
12223            }
12224            return result;
12225        };
12226    
12227        exports.FLATTEN = utils.flatten;
12228    
12229        exports.ARGS2ARRAY = function () {
12230            return Array.prototype.slice.call(arguments, 0);
12231        };
12232    
12233        exports.REFERENCE = function (context, reference) {
12234            try {
12235                var path = reference.split('.');
12236                var result = context;
12237                for (var i = 0; i < path.length; ++i) {
12238                    var step = path[i];
12239                    if (step[step.length - 1] === ']') {
12240                        var opening = step.indexOf('[');
12241                        var index = step.substring(opening + 1, step.length - 1);
12242                        result = result[step.substring(0, opening)][index];
12243                    } else {
12244                        result = result[step];
12245                    }
12246                }
12247                return result;
12248            } catch (error) {}
12249        };
12250    
12251        exports.JOIN = function (array, separator) {
12252            return array.join(separator);
12253        };
12254    
12255        exports.NUMBERS = function () {
12256            var possibleNumbers = utils.flatten(arguments);
12257            return possibleNumbers.filter(function (el) {
12258                return typeof el === 'number';
12259            });
12260        };
12261    
12262        exports.NUMERAL = null;
12263    
12264        return exports;
12265    })();
12266    
12267    jexcel.methods.text = (function() {
12268        var exports = {};
12269    
12270        exports.ASC = null;
12271    
12272        exports.BAHTTEXT = null;
12273    
12274        exports.CHAR = function(number) {
12275            number = utils.parseNumber(number);
12276            if (number instanceof Error) {
12277                return number;
12278            }
12279            return String.fromCharCode(number);
12280        };
12281    
12282        exports.CLEAN = function(text) {
12283            text = text || '';
12284            var re = /[\0-\x1F]/g;
12285            return text.replace(re, "");
12286        };
12287    
12288        exports.CODE = function(text) {
12289            text = text || '';
12290            return text.charCodeAt(0);
12291        };
12292    
12293        exports.CONCATENATE = function() {
12294            var args = utils.flatten(arguments);
12295    
12296            var trueFound = 0;
12297            while ((trueFound = args.indexOf(true)) > -1) {
12298                args[trueFound] = 'TRUE';
12299            }
12300    
12301            var falseFound = 0;
12302            while ((falseFound = args.indexOf(false)) > -1) {
12303                args[falseFound] = 'FALSE';
12304            }
12305    
12306            return args.join('');
12307        };
12308    
12309        exports.DBCS = null;
12310    
12311        exports.DOLLAR = null;
12312    
12313        exports.EXACT = function(text1, text2) {
12314            return text1 === text2;
12315        };
12316    
12317        exports.FIND = function(find_text, within_text, position) {
12318            position = (position === undefined) ? 0 : position;
12319            return within_text ? within_text.indexOf(find_text, position - 1) + 1 : null;
12320        };
12321    
12322        exports.FIXED = null;
12323    
12324        exports.HTML2TEXT = function (value) {
12325            var result = '';
12326    
12327            if (value) {
12328                if (value instanceof Array) {
12329                    value.forEach(function (line) {
12330                        if (result !== '') {
12331                          result += '\n';
12332                        }
12333                        result += (line.replace(/<(?:.|\n)*?>/gm, ''));
12334                    });
12335                } else {
12336                    result = value.replace(/<(?:.|\n)*?>/gm, '');
12337                }
12338            }
12339    
12340            return result;
12341        };
12342    
12343        exports.LEFT = function(text, number) {
12344            number = (number === undefined) ? 1 : number;
12345            number = utils.parseNumber(number);
12346            if (number instanceof Error || typeof text !== 'string') {
12347                return error.value;
12348            }
12349            return text ? text.substring(0, number) : null;
12350        };
12351    
12352        exports.LEN = function(text) {
12353            if (arguments.length === 0) {
12354                return error.error;
12355            }
12356    
12357            if (typeof text === 'string') {
12358                return text ? text.length : 0;
12359            }
12360    
12361            if (text.length) {
12362                return text.length;
12363            }
12364    
12365            return error.value;
12366        };
12367    
12368        exports.LOWER = function(text) {
12369            if (typeof text !== 'string') {
12370                return error.value;
12371            }
12372            return text ? text.toLowerCase() : text;
12373        };
12374    
12375        exports.MID = function(text, start, number) {
12376            start = utils.parseNumber(start);
12377            number = utils.parseNumber(number);
12378            if (utils.anyIsError(start, number) || typeof text !== 'string') {
12379                return number;
12380            }
12381    
12382            var begin = start - 1;
12383            var end = begin + number;
12384    
12385            return text.substring(begin, end);
12386        };
12387    
12388        exports.NUMBERVALUE = null;
12389    
12390        exports.PRONETIC = null;
12391    
12392        exports.PROPER = function(text) {
12393            if (text === undefined || text.length === 0) {
12394                return error.value;
12395            }
12396            if (text === true) {
12397                text = 'TRUE';
12398            }
12399            if (text === false) {
12400                text = 'FALSE';
12401            }
12402            if (isNaN(text) && typeof text === 'number') {
12403                return error.value;
12404            }
12405            if (typeof text === 'number') {
12406                text = '' + text;
12407            }
12408    
12409            return text.replace(/\w\S*/g, function(txt) {
12410                return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
12411            });
12412        };
12413    
12414        exports.REGEXEXTRACT = function (text, regular_expression) {
12415            var match = text.match(new RegExp(regular_expression));
12416            return match ? (match[match.length > 1 ? match.length - 1 : 0]) : null;
12417        };
12418    
12419        exports.REGEXMATCH = function (text, regular_expression, full) {
12420            var match = text.match(new RegExp(regular_expression));
12421            return full ? match : !!match;
12422        };
12423    
12424        exports.REGEXREPLACE = function (text, regular_expression, replacement) {
12425            return text.replace(new RegExp(regular_expression), replacement);
12426        };
12427    
12428        exports.REPLACE = function(text, position, length, new_text) {
12429            position = utils.parseNumber(position);
12430            length = utils.parseNumber(length);
12431            if (utils.anyIsError(position, length) ||
12432                typeof text !== 'string' ||
12433                typeof new_text !== 'string') {
12434                return error.value;
12435            }
12436            return text.substr(0, position - 1) + new_text + text.substr(position - 1 + length);
12437        };
12438    
12439        exports.REPT = function(text, number) {
12440            number = utils.parseNumber(number);
12441            if (number instanceof Error) {
12442                return number;
12443            }
12444            return new Array(number + 1).join(text);
12445        };
12446    
12447        exports.RIGHT = function(text, number) {
12448            number = (number === undefined) ? 1 : number;
12449            number = utils.parseNumber(number);
12450            if (number instanceof Error) {
12451                return number;
12452            }
12453            return text ? text.substring(text.length - number) : null;
12454        };
12455    
12456        exports.SEARCH = function(find_text, within_text, position) {
12457            var foundAt;
12458            if (typeof find_text !== 'string' || typeof within_text !== 'string') {
12459                return error.value;
12460            }
12461            position = (position === undefined) ? 0 : position;
12462            foundAt = within_text.toLowerCase().indexOf(find_text.toLowerCase(), position - 1)+1;
12463            return (foundAt === 0)?error.value:foundAt;
12464        };
12465    
12466        exports.SPLIT = function (text, separator) {
12467            return text.split(separator);
12468        };
12469    
12470        exports.SUBSTITUTE = function(text, old_text, new_text, occurrence) {
12471            if (!text || !old_text || !new_text) {
12472                return text;
12473            } else if (occurrence === undefined) {
12474                return text.replace(new RegExp(old_text, 'g'), new_text);
12475            } else {
12476                var index = 0;
12477                var i = 0;
12478                while (text.indexOf(old_text, index) > 0) {
12479                    index = text.indexOf(old_text, index + 1);
12480                    i++;
12481                    if (i === occurrence) {
12482                        return text.substring(0, index) + new_text + text.substring(index + old_text.length);
12483                    }
12484                }
12485            }
12486        };
12487    
12488        exports.T = function(value) {
12489            return (typeof value === "string") ? value : '';
12490        };
12491    
12492        exports.TEXT = null;
12493    
12494        exports.TRIM = function(text) {
12495            if (typeof text !== 'string') {
12496                return error.value;
12497            }
12498            return text.replace(/ +/g, ' ').trim();
12499        };
12500    
12501        exports.UNICHAR = exports.CHAR;
12502    
12503        exports.UNICODE = exports.CODE;
12504    
12505        exports.UPPER = function(text) {
12506            if (typeof text !== 'string') {
12507                return error.value;
12508            }
12509            return text.toUpperCase();
12510        };
12511    
12512        exports.VALUE = null;
12513    
12514        return exports;
12515    })();
12516    
12517    jexcel.methods.stats = (function() {
12518        var exports = {};
12519    
12520        var SQRT2PI = 2.5066282746310002;
12521    
12522        exports.AVEDEV = null;
12523    
12524        exports.AVERAGE = function() {
12525            var range = utils.numbers(utils.flatten(arguments));
12526            var n = range.length;
12527            var sum = 0;
12528            var count = 0;
12529            for (var i = 0; i < n; i++) {
12530                sum += range[i];
12531                count += 1;
12532            }
12533            return sum / count;
12534        };
12535    
12536        exports.AVERAGEA = function() {
12537            var range = utils.flatten(arguments);
12538            var n = range.length;
12539            var sum = 0;
12540            var count = 0;
12541            for (var i = 0; i < n; i++) {
12542                var el = range[i];
12543                if (typeof el === 'number') {
12544                    sum += el;
12545                }
12546                if (el === true) {
12547                    sum++;
12548                }
12549                if (el !== null) {
12550                    count++;
12551                }
12552            }
12553            return sum / count;
12554        };
12555    
12556        exports.AVERAGEIF = function(range, criteria, average_range) {
12557            average_range = average_range || range;
12558            range = utils.flatten(range);
12559            average_range = utils.parseNumberArray(utils.flatten(average_range));
12560            if (average_range instanceof Error) {
12561                return average_range;
12562            }
12563            var average_count = 0;
12564            var result = 0;
12565            for (var i = 0; i < range.length; i++) {
12566                if (eval(range[i] + criteria)) { // jshint ignore:line
12567                    result += average_range[i];
12568                    average_count++;
12569                }
12570            }
12571            return result / average_count;
12572        };
12573    
12574        exports.AVERAGEIFS = null;
12575    
12576        exports.COUNT = function() {
12577            return utils.numbers(utils.flatten(arguments)).length;
12578        };
12579    
12580        exports.COUNTA = function() {
12581            var range = utils.flatten(arguments);
12582            return range.length - exports.COUNTBLANK(range);
12583        };
12584    
12585        exports.COUNTIN = function (range, value) {
12586            var result = 0;
12587            for (var i = 0; i < range.length; i++) {
12588                if (range[i] === value) {
12589                    result++;
12590                }
12591            }
12592            return result;
12593        };
12594    
12595        exports.COUNTBLANK = function() {
12596            var range = utils.flatten(arguments);
12597            var blanks = 0;
12598            var element;
12599            for (var i = 0; i < range.length; i++) {
12600                element = range[i];
12601                if (element === null || element === '') {
12602                    blanks++;
12603                }
12604            }
12605            return blanks;
12606        };
12607    
12608        exports.COUNTIF = function(range, criteria) {
12609            range = utils.flatten(range);
12610            if (!/[<>=!]/.test(criteria)) {
12611                criteria = '=="' + criteria + '"';
12612            }
12613            var matches = 0;
12614            for (var i = 0; i < range.length; i++) {
12615                if (typeof range[i] !== 'string') {
12616                    if (eval(range[i] + criteria)) { // jshint ignore:line
12617                        matches++;
12618                    }
12619                } else {
12620                    if (eval('"' + range[i] + '"' + criteria)) { // jshint ignore:line
12621                        matches++;
12622                    }
12623                }
12624            }
12625            return matches;
12626        };
12627    
12628        exports.COUNTIFS = function() {
12629            var args = utils.argsToArray(arguments);
12630            var results = new Array(utils.flatten(args[0]).length);
12631            for (var i = 0; i < results.length; i++) {
12632                results[i] = true;
12633            }
12634            for (i = 0; i < args.length; i += 2) {
12635                var range = utils.flatten(args[i]);
12636                var criteria = args[i + 1];
12637                if (!/[<>=!]/.test(criteria)) {
12638                    criteria = '=="' + criteria + '"';
12639                }
12640                for (var j = 0; j < range.length; j++) {
12641                    if (typeof range[j] !== 'string') {
12642                        results[j] = results[j] && eval(range[j] + criteria); // jshint ignore:line
12643                    } else {
12644                        results[j] = results[j] && eval('"' + range[j] + '"' + criteria); // jshint ignore:line
12645                    }
12646                }
12647            }
12648            var result = 0;
12649            for (i = 0; i < results.length; i++) {
12650                if (results[i]) {
12651                    result++;
12652                }
12653            }
12654            return result;
12655        };
12656    
12657        exports.COUNTUNIQUE = function () {
12658            return UNIQUE.apply(null, utils.flatten(arguments)).length;
12659        };
12660    
12661        exports.FISHER = function(x) {
12662            x = utils.parseNumber(x);
12663            if (x instanceof Error) {
12664                return x;
12665            }
12666            return Math.log((1 + x) / (1 - x)) / 2;
12667        };
12668    
12669        exports.FISHERINV = function(y) {
12670            y = utils.parseNumber(y);
12671            if (y instanceof Error) {
12672                return y;
12673            }
12674            var e2y = Math.exp(2 * y);
12675            return (e2y - 1) / (e2y + 1);
12676        };
12677    
12678        exports.FREQUENCY = function(data, bins) {
12679            data = utils.parseNumberArray(utils.flatten(data));
12680            bins = utils.parseNumberArray(utils.flatten(bins));
12681            if (utils.anyIsError(data, bins)) {
12682                return error.value;
12683            }
12684            var n = data.length;
12685            var b = bins.length;
12686            var r = [];
12687            for (var i = 0; i <= b; i++) {
12688                r[i] = 0;
12689                for (var j = 0; j < n; j++) {
12690                    if (i === 0) {
12691                        if (data[j] <= bins[0]) {
12692                            r[0] += 1;
12693                        }
12694                    } else if (i < b) {
12695                        if (data[j] > bins[i - 1] && data[j] <= bins[i]) {
12696                            r[i] += 1;
12697                        }
12698                    } else if (i === b) {
12699                        if (data[j] > bins[b - 1]) {
12700                            r[b] += 1;
12701                        }
12702                    }
12703                }
12704            }
12705            return r;
12706        };
12707    
12708        exports.LARGE = function(range, k) {
12709            range = utils.parseNumberArray(utils.flatten(range));
12710            k = utils.parseNumber(k);
12711            if (utils.anyIsError(range, k)) {
12712                return range;
12713            }
12714            return range.sort(function(a, b) {
12715                return b - a;
12716            })[k - 1];
12717        };
12718    
12719        exports.MAX = function() {
12720            var range = utils.numbers(utils.flatten(arguments));
12721            return (range.length === 0) ? 0 : Math.max.apply(Math, range);
12722        };
12723    
12724        exports.MAXA = function() {
12725            var range = utils.arrayValuesToNumbers(utils.flatten(arguments));
12726            return (range.length === 0) ? 0 : Math.max.apply(Math, range);
12727        };
12728    
12729        exports.MIN = function() {
12730            var range = utils.numbers(utils.flatten(arguments));
12731            return (range.length === 0) ? 0 : Math.min.apply(Math, range);
12732        };
12733    
12734        exports.MINA = function() {
12735            var range = utils.arrayValuesToNumbers(utils.flatten(arguments));
12736            return (range.length === 0) ? 0 : Math.min.apply(Math, range);
12737        };
12738    
12739        exports.MODE = {};
12740    
12741        exports.MODE.MULT = function() {
12742            // Credits: Roönaän
12743            var range = utils.parseNumberArray(utils.flatten(arguments));
12744            if (range instanceof Error) {
12745                return range;
12746            }
12747            var n = range.length;
12748            var count = {};
12749            var maxItems = [];
12750            var max = 0;
12751            var currentItem;
12752    
12753            for (var i = 0; i < n; i++) {
12754                currentItem = range[i];
12755                count[currentItem] = count[currentItem] ? count[currentItem] + 1 : 1;
12756                if (count[currentItem] > max) {
12757                    max = count[currentItem];
12758                    maxItems = [];
12759                }
12760                if (count[currentItem] === max) {
12761                    maxItems[maxItems.length] = currentItem;
12762                }
12763            }
12764            return maxItems;
12765        };
12766    
12767        exports.MODE.SNGL = function() {
12768            var range = utils.parseNumberArray(utils.flatten(arguments));
12769            if (range instanceof Error) {
12770                return range;
12771            }
12772            return exports.MODE.MULT(range).sort(function(a, b) {
12773                return a - b;
12774            })[0];
12775        };
12776    
12777        exports.PERCENTILE = {};
12778    
12779        exports.PERCENTILE.EXC = function(array, k) {
12780            array = utils.parseNumberArray(utils.flatten(array));
12781            k = utils.parseNumber(k);
12782            if (utils.anyIsError(array, k)) {
12783                return error.value;
12784            }
12785            array = array.sort(function(a, b) {
12786                {
12787                    return a - b;
12788                }
12789            });
12790            var n = array.length;
12791            if (k < 1 / (n + 1) || k > 1 - 1 / (n + 1)) {
12792                return error.num;
12793            }
12794            var l = k * (n + 1) - 1;
12795            var fl = Math.floor(l);
12796            return utils.cleanFloat((l === fl) ? array[l] : array[fl] + (l - fl) * (array[fl + 1] - array[fl]));
12797        };
12798    
12799        exports.PERCENTILE.INC = function(array, k) {
12800            array = utils.parseNumberArray(utils.flatten(array));
12801            k = utils.parseNumber(k);
12802            if (utils.anyIsError(array, k)) {
12803                return error.value;
12804            }
12805            array = array.sort(function(a, b) {
12806                return a - b;
12807            });
12808            var n = array.length;
12809            var l = k * (n - 1);
12810            var fl = Math.floor(l);
12811            return utils.cleanFloat((l === fl) ? array[l] : array[fl] + (l - fl) * (array[fl + 1] - array[fl]));
12812        };
12813    
12814        exports.PERCENTRANK = {};
12815    
12816        exports.PERCENTRANK.EXC = function(array, x, significance) {
12817            significance = (significance === undefined) ? 3 : significance;
12818            array = utils.parseNumberArray(utils.flatten(array));
12819            x = utils.parseNumber(x);
12820            significance = utils.parseNumber(significance);
12821            if (utils.anyIsError(array, x, significance)) {
12822                return error.value;
12823            }
12824            array = array.sort(function(a, b) {
12825                return a - b;
12826            });
12827            var uniques = UNIQUE.apply(null, array);
12828            var n = array.length;
12829            var m = uniques.length;
12830            var power = Math.pow(10, significance);
12831            var result = 0;
12832            var match = false;
12833            var i = 0;
12834            while (!match && i < m) {
12835                if (x === uniques[i]) {
12836                    result = (array.indexOf(uniques[i]) + 1) / (n + 1);
12837                    match = true;
12838                } else if (x >= uniques[i] && (x < uniques[i + 1] || i === m - 1)) {
12839                    result = (array.indexOf(uniques[i]) + 1 + (x - uniques[i]) / (uniques[i + 1] - uniques[i])) / (n + 1);
12840                    match = true;
12841                }
12842                i++;
12843            }
12844            return Math.floor(result * power) / power;
12845        };
12846    
12847        exports.PERCENTRANK.INC = function(array, x, significance) {
12848            significance = (significance === undefined) ? 3 : significance;
12849            array = utils.parseNumberArray(utils.flatten(array));
12850            x = utils.parseNumber(x);
12851            significance = utils.parseNumber(significance);
12852            if (utils.anyIsError(array, x, significance)) {
12853                return error.value;
12854            }
12855            array = array.sort(function(a, b) {
12856                return a - b;
12857            });
12858            var uniques = UNIQUE.apply(null, array);
12859            var n = array.length;
12860            var m = uniques.length;
12861            var power = Math.pow(10, significance);
12862            var result = 0;
12863            var match = false;
12864            var i = 0;
12865            while (!match && i < m) {
12866                if (x === uniques[i]) {
12867                    result = array.indexOf(uniques[i]) / (n - 1);
12868                    match = true;
12869                } else if (x >= uniques[i] && (x < uniques[i + 1] || i === m - 1)) {
12870                    result = (array.indexOf(uniques[i]) + (x - uniques[i]) / (uniques[i + 1] - uniques[i])) / (n - 1);
12871                    match = true;
12872                }
12873                i++;
12874            }
12875            return Math.floor(result * power) / power;
12876        };
12877    
12878        exports.PERMUT = function(number, number_chosen) {
12879            number = utils.parseNumber(number);
12880            number_chosen = utils.parseNumber(number_chosen);
12881            if (utils.anyIsError(number, number_chosen)) {
12882                return error.value;
12883            }
12884            return FACT(number) / FACT(number - number_chosen);
12885        };
12886    
12887        exports.PERMUTATIONA = function(number, number_chosen) {
12888            number = utils.parseNumber(number);
12889            number_chosen = utils.parseNumber(number_chosen);
12890            if (utils.anyIsError(number, number_chosen)) {
12891                return error.value;
12892            }
12893            return Math.pow(number, number_chosen);
12894        };
12895    
12896        exports.PHI = function(x) {
12897            x = utils.parseNumber(x);
12898            if (x instanceof Error) {
12899                return error.value;
12900            }
12901            return Math.exp(-0.5 * x * x) / SQRT2PI;
12902        };
12903    
12904        exports.PROB = function(range, probability, lower, upper) {
12905            if (lower === undefined) {
12906                return 0;
12907            }
12908            upper = (upper === undefined) ? lower : upper;
12909    
12910            range = utils.parseNumberArray(utils.flatten(range));
12911            probability = utils.parseNumberArray(utils.flatten(probability));
12912            lower = utils.parseNumber(lower);
12913            upper = utils.parseNumber(upper);
12914            if (utils.anyIsError(range, probability, lower, upper)) {
12915                return error.value;
12916            }
12917    
12918            if (lower === upper) {
12919                return (range.indexOf(lower) >= 0) ? probability[range.indexOf(lower)] : 0;
12920            }
12921    
12922            var sorted = range.sort(function(a, b) {
12923                return a - b;
12924            });
12925            var n = sorted.length;
12926            var result = 0;
12927            for (var i = 0; i < n; i++) {
12928                if (sorted[i] >= lower && sorted[i] <= upper) {
12929                    result += probability[range.indexOf(sorted[i])];
12930                }
12931            }
12932            return result;
12933        };
12934    
12935        exports.QUARTILE = {};
12936    
12937        exports.QUARTILE.EXC = function(range, quart) {
12938            range = utils.parseNumberArray(utils.flatten(range));
12939            quart = utils.parseNumber(quart);
12940            if (utils.anyIsError(range, quart)) {
12941                return error.value;
12942            }
12943            switch (quart) {
12944                case 1:
12945                    return exports.PERCENTILE.EXC(range, 0.25);
12946                case 2:
12947                    return exports.PERCENTILE.EXC(range, 0.5);
12948                case 3:
12949                    return exports.PERCENTILE.EXC(range, 0.75);
12950                default:
12951                    return error.num;
12952            }
12953        };
12954    
12955        exports.QUARTILE.INC = function(range, quart) {
12956            range = utils.parseNumberArray(utils.flatten(range));
12957            quart = utils.parseNumber(quart);
12958            if (utils.anyIsError(range, quart)) {
12959                return error.value;
12960            }
12961            switch (quart) {
12962                case 1:
12963                    return exports.PERCENTILE.INC(range, 0.25);
12964                case 2:
12965                    return exports.PERCENTILE.INC(range, 0.5);
12966                case 3:
12967                    return exports.PERCENTILE.INC(range, 0.75);
12968                default:
12969                    return error.num;
12970            }
12971        };
12972    
12973        exports.RANK = {};
12974    
12975        exports.RANK.AVG = function(number, range, order) {
12976            number = utils.parseNumber(number);
12977            range = utils.parseNumberArray(utils.flatten(range));
12978            if (utils.anyIsError(number, range)) {
12979                return error.value;
12980            }
12981            range = utils.flatten(range);
12982            order = order || false;
12983            var sort = (order) ? function(a, b) {
12984                return a - b;
12985            } : function(a, b) {
12986                return b - a;
12987            };
12988            range = range.sort(sort);
12989    
12990            var length = range.length;
12991            var count = 0;
12992            for (var i = 0; i < length; i++) {
12993                if (range[i] === number) {
12994                    count++;
12995                }
12996            }
12997    
12998            return (count > 1) ? (2 * range.indexOf(number) + count + 1) / 2 : range.indexOf(number) + 1;
12999        };
13000    
13001        exports.RANK.EQ = function(number, range, order) {
13002            number = utils.parseNumber(number);
13003            range = utils.parseNumberArray(utils.flatten(range));
13004            if (utils.anyIsError(number, range)) {
13005                return error.value;
13006            }
13007            order = order || false;
13008            var sort = (order) ? function(a, b) {
13009                return a - b;
13010            } : function(a, b) {
13011                return b - a;
13012            };
13013            range = range.sort(sort);
13014            return range.indexOf(number) + 1;
13015        };
13016    
13017        exports.RSQ = function(data_x, data_y) { // no need to flatten here, PEARSON will take care of that
13018            data_x = utils.parseNumberArray(utils.flatten(data_x));
13019            data_y = utils.parseNumberArray(utils.flatten(data_y));
13020            if (utils.anyIsError(data_x, data_y)) {
13021                return error.value;
13022            }
13023            return Math.pow(exports.PEARSON(data_x, data_y), 2);
13024        };
13025    
13026        exports.SMALL = function(range, k) {
13027            range = utils.parseNumberArray(utils.flatten(range));
13028            k = utils.parseNumber(k);
13029            if (utils.anyIsError(range, k)) {
13030                return range;
13031            }
13032            return range.sort(function(a, b) {
13033                return a - b;
13034            })[k - 1];
13035        };
13036    
13037        exports.STANDARDIZE = function(x, mean, sd) {
13038            x = utils.parseNumber(x);
13039            mean = utils.parseNumber(mean);
13040            sd = utils.parseNumber(sd);
13041            if (utils.anyIsError(x, mean, sd)) {
13042                return error.value;
13043            }
13044            return (x - mean) / sd;
13045        };
13046    
13047        exports.STDEV = {};
13048    
13049        exports.STDEV.P = function() {
13050            var v = exports.VAR.P.apply(this, arguments);
13051            return Math.sqrt(v);
13052        };
13053    
13054        exports.STDEV.S = function() {
13055            var v = exports.VAR.S.apply(this, arguments);
13056            return Math.sqrt(v);
13057        };
13058    
13059        exports.STDEVA = function() {
13060            var v = exports.VARA.apply(this, arguments);
13061            return Math.sqrt(v);
13062        };
13063    
13064        exports.STDEVPA = function() {
13065            var v = exports.VARPA.apply(this, arguments);
13066            return Math.sqrt(v);
13067        };
13068    
13069        exports.VAR = {};
13070    
13071        exports.VAR.P = function() {
13072            var range = utils.numbers(utils.flatten(arguments));
13073            var n = range.length;
13074            var sigma = 0;
13075            var mean = exports.AVERAGE(range);
13076            for (var i = 0; i < n; i++) {
13077                sigma += Math.pow(range[i] - mean, 2);
13078            }
13079            return sigma / n;
13080        };
13081    
13082        exports.VAR.S = function() {
13083            var range = utils.numbers(utils.flatten(arguments));
13084            var n = range.length;
13085            var sigma = 0;
13086            var mean = exports.AVERAGE(range);
13087            for (var i = 0; i < n; i++) {
13088                sigma += Math.pow(range[i] - mean, 2);
13089            }
13090            return sigma / (n - 1);
13091        };
13092    
13093        exports.VARA = function() {
13094            var range = utils.flatten(arguments);
13095            var n = range.length;
13096            var sigma = 0;
13097            var count = 0;
13098            var mean = exports.AVERAGEA(range);
13099            for (var i = 0; i < n; i++) {
13100                var el = range[i];
13101                if (typeof el === 'number') {
13102                    sigma += Math.pow(el - mean, 2);
13103                } else if (el === true) {
13104                    sigma += Math.pow(1 - mean, 2);
13105                } else {
13106                    sigma += Math.pow(0 - mean, 2);
13107                }
13108    
13109                if (el !== null) {
13110                    count++;
13111                }
13112            }
13113            return sigma / (count - 1);
13114        };
13115    
13116        exports.VARPA = function() {
13117            var range = utils.flatten(arguments);
13118            var n = range.length;
13119            var sigma = 0;
13120            var count = 0;
13121            var mean = exports.AVERAGEA(range);
13122            for (var i = 0; i < n; i++) {
13123                var el = range[i];
13124                if (typeof el === 'number') {
13125                    sigma += Math.pow(el - mean, 2);
13126                } else if (el === true) {
13127                    sigma += Math.pow(1 - mean, 2);
13128                } else {
13129                    sigma += Math.pow(0 - mean, 2);
13130                }
13131    
13132                if (el !== null) {
13133                    count++;
13134                }
13135            }
13136            return sigma / count;
13137        };
13138    
13139        exports.WEIBULL = {};
13140    
13141        exports.WEIBULL.DIST = function(x, alpha, beta, cumulative) {
13142            x = utils.parseNumber(x);
13143            alpha = utils.parseNumber(alpha);
13144            beta = utils.parseNumber(beta);
13145            if (utils.anyIsError(x, alpha, beta)) {
13146                return error.value;
13147            }
13148            return (cumulative) ? 1 - Math.exp(-Math.pow(x / beta, alpha)) : Math.pow(x, alpha - 1) * Math.exp(-Math.pow(x / beta, alpha)) * alpha / Math.pow(beta, alpha);
13149        };
13150    
13151        exports.Z = {};
13152    
13153        exports.Z.TEST = function(range, x, sd) {
13154            range = utils.parseNumberArray(utils.flatten(range));
13155            x = utils.parseNumber(x);
13156            if (utils.anyIsError(range, x)) {
13157                return error.value;
13158            }
13159    
13160            sd = sd || exports.STDEV.S(range);
13161            var n = range.length;
13162            return 1 - exports.NORM.S.DIST((exports.AVERAGE(range) - x) / (sd / Math.sqrt(n)), true);
13163        };
13164    
13165        return exports;
13166    })();
13167    
13168    for (var i = 0; i < Object.keys(jexcel.methods).length; i++) {
13169        var methods = jexcel.methods[Object.keys(jexcel.methods)[i]];
13170        for (var j = 0; j < Object.keys(methods).length; j++) {
13171            if (typeof(methods[Object.keys(methods)[j]]) == 'function') {
13172                window[Object.keys(methods)[j]] = methods[Object.keys(methods)[j]];
13173            } else {
13174                window[Object.keys(methods)[j]] = function() {
13175                    return Object.keys(methods)[j] + 'Not implemented';
13176                }
13177            }
13178        }
13179    }
13180
13181    return jexcel;
13182})));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.