1/*!
2 * HTML5 export buttons for Buttons and DataTables.
3 * © SpryMedia Ltd - datatables.net/license
4 *
5 * FileSaver.js (1.3.3) - MIT license
6 * Copyright © 2016 Eli Grey - http://eligrey.com
7 */
8
9(function( factory ){
10	if ( typeof define === 'function' && define.amd ) {
11		// AMD
12		define( ['jquery', 'datatables.net', 'datatables.net-buttons'], function ( $ ) {
13			return factory( $, window, document );
14		} );
15	}
16	else if ( typeof exports === 'object' ) {
17		// CommonJS
18		var jq = require('jquery');
19		var cjsRequires = function (root, $) {
20			if ( ! $.fn.dataTable ) {
21				require('datatables.net')(root, $);
22			}
23
24			if ( ! $.fn.dataTable.Buttons ) {
25				require('datatables.net-buttons')(root, $);
26			}
27		};
28
29		if (typeof window === 'undefined') {
30			module.exports = function (root, $, jszip, pdfmake) {
31				if ( ! root ) {
32					// CommonJS environments without a window global must pass a
33					// root. This will give an error otherwise
34					root = window;
35				}
36
37				if ( ! $ ) {
38					$ = jq( root );
39				}
40
41				cjsRequires( root, $ );
42				return factory( $, root, root.document, jszip, pdfmake );
43			};
44		}
45		else {
46			cjsRequires( window, jq );
47			module.exports = factory( jq, window, window.document );
48		}
49	}
50	else {
51		// Browser
52		factory( jQuery, window, document );
53	}
54}(function( $, window, document, jszip, pdfmake, undefined ) {
55'use strict';
56var DataTable = $.fn.dataTable;
57
58
59
60// Allow the constructor to pass in JSZip and PDFMake from external requires.
61// Otherwise, use globally defined variables, if they are available.
62var useJszip;
63var usePdfmake;
64
65function _jsZip() {
66	return useJszip || window.JSZip;
67}
68function _pdfMake() {
69	return usePdfmake || window.pdfMake;
70}
71
72DataTable.Buttons.pdfMake = function (_) {
73	if (!_) {
74		return _pdfMake();
75	}
76	usePdfmake = _;
77};
78
79DataTable.Buttons.jszip = function (_) {
80	if (!_) {
81		return _jsZip();
82	}
83	useJszip = _;
84};
85
86/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
87 * FileSaver.js dependency
88 */
89
90/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
91
92var _saveAs = (function (view) {
93	'use strict';
94	// IE <10 is explicitly unsupported
95	if (
96		typeof view === 'undefined' ||
97		(typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent))
98	) {
99		return;
100	}
101	var doc = view.document,
102		// only get URL when necessary in case Blob.js hasn't overridden it yet
103		get_URL = function () {
104			return view.URL || view.webkitURL || view;
105		},
106		save_link = doc.createElementNS('http://www.w3.org/1999/xhtml', 'a'),
107		can_use_save_link = 'download' in save_link,
108		click = function (node) {
109			var event = new MouseEvent('click');
110			node.dispatchEvent(event);
111		},
112		is_safari = /constructor/i.test(view.HTMLElement) || view.safari,
113		is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent),
114		throw_outside = function (ex) {
115			(view.setImmediate || view.setTimeout)(function () {
116				throw ex;
117			}, 0);
118		},
119		force_saveable_type = 'application/octet-stream',
120		// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
121		arbitrary_revoke_timeout = 1000 * 40, // in ms
122		revoke = function (file) {
123			var revoker = function () {
124				if (typeof file === 'string') {
125					// file is an object URL
126					get_URL().revokeObjectURL(file);
127				}
128				else {
129					// file is a File
130					file.remove();
131				}
132			};
133			setTimeout(revoker, arbitrary_revoke_timeout);
134		},
135		dispatch = function (filesaver, event_types, event) {
136			event_types = [].concat(event_types);
137			var i = event_types.length;
138			while (i--) {
139				var listener = filesaver['on' + event_types[i]];
140				if (typeof listener === 'function') {
141					try {
142						listener.call(filesaver, event || filesaver);
143					} catch (ex) {
144						throw_outside(ex);
145					}
146				}
147			}
148		},
149		auto_bom = function (blob) {
150			// prepend BOM for UTF-8 XML and text/* types (including HTML)
151			// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
152			if (
153				/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(
154					blob.type
155				)
156			) {
157				return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type });
158			}
159			return blob;
160		},
161		FileSaver = function (blob, name, no_auto_bom) {
162			if (!no_auto_bom) {
163				blob = auto_bom(blob);
164			}
165			// First try a.download, then web filesystem, then object URLs
166			var filesaver = this,
167				type = blob.type,
168				force = type === force_saveable_type,
169				object_url,
170				dispatch_all = function () {
171					dispatch(filesaver, 'writestart progress write writeend'.split(' '));
172				},
173				// on any filesys errors revert to saving with object URLs
174				fs_error = function () {
175					if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
176						// Safari doesn't allow downloading of blob urls
177						var reader = new FileReader();
178						reader.onloadend = function () {
179							var url = is_chrome_ios
180								? reader.result
181								: reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
182							var popup = view.open(url, '_blank');
183							if (!popup) view.location.href = url;
184							url = undefined; // release reference before dispatching
185							filesaver.readyState = filesaver.DONE;
186							dispatch_all();
187						};
188						reader.readAsDataURL(blob);
189						filesaver.readyState = filesaver.INIT;
190						return;
191					}
192					// don't create more object URLs than needed
193					if (!object_url) {
194						object_url = get_URL().createObjectURL(blob);
195					}
196					if (force) {
197						view.location.href = object_url;
198					}
199					else {
200						var opened = view.open(object_url, '_blank');
201						if (!opened) {
202							// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
203							view.location.href = object_url;
204						}
205					}
206					filesaver.readyState = filesaver.DONE;
207					dispatch_all();
208					revoke(object_url);
209				};
210			filesaver.readyState = filesaver.INIT;
211
212			if (can_use_save_link) {
213				object_url = get_URL().createObjectURL(blob);
214				setTimeout(function () {
215					save_link.href = object_url;
216					save_link.download = name;
217					click(save_link);
218					dispatch_all();
219					revoke(object_url);
220					filesaver.readyState = filesaver.DONE;
221				});
222				return;
223			}
224
225			fs_error();
226		},
227		FS_proto = FileSaver.prototype,
228		saveAs = function (blob, name, no_auto_bom) {
229			return new FileSaver(blob, name || blob.name || 'download', no_auto_bom);
230		};
231	// IE 10+ (native saveAs)
232	if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) {
233		return function (blob, name, no_auto_bom) {
234			name = name || blob.name || 'download';
235
236			if (!no_auto_bom) {
237				blob = auto_bom(blob);
238			}
239			return navigator.msSaveOrOpenBlob(blob, name);
240		};
241	}
242
243	FS_proto.abort = function () {};
244	FS_proto.readyState = FS_proto.INIT = 0;
245	FS_proto.WRITING = 1;
246	FS_proto.DONE = 2;
247
248	FS_proto.error =
249		FS_proto.onwritestart =
250		FS_proto.onprogress =
251		FS_proto.onwrite =
252		FS_proto.onabort =
253		FS_proto.onerror =
254		FS_proto.onwriteend =
255			null;
256
257	return saveAs;
258})(
259	(typeof self !== 'undefined' && self) ||
260		(typeof window !== 'undefined' && window) ||
261		this.content
262);
263
264// Expose file saver on the DataTables API. Can't attach to `DataTables.Buttons`
265// since this file can be loaded before Button's core!
266DataTable.fileSave = _saveAs;
267
268/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
269 * Local (private) functions
270 */
271
272/**
273 * Get the sheet name for Excel exports.
274 *
275 * @param {object}	config Button configuration
276 */
277var _sheetname = function (config) {
278	var sheetName = 'Sheet1';
279
280	if (config.sheetName) {
281		sheetName = config.sheetName.replace(/[\[\]\*\/\\\?\:]/g, '');
282	}
283
284	return sheetName;
285};
286
287/**
288 * Get the newline character(s)
289 *
290 * @param {object}	config Button configuration
291 * @return {string}				Newline character
292 */
293var _newLine = function (config) {
294	return config.newline ? config.newline : navigator.userAgent.match(/Windows/) ? '\r\n' : '\n';
295};
296
297/**
298 * Combine the data from the `buttons.exportData` method into a string that
299 * will be used in the export file.
300 *
301 * @param	{DataTable.Api} dt		 DataTables API instance
302 * @param	{object}				config Button configuration
303 * @return {object}							 The data to export
304 */
305var _exportData = function (dt, config) {
306	var newLine = _newLine(config);
307	var data = dt.buttons.exportData(config.exportOptions);
308	var boundary = config.fieldBoundary;
309	var separator = config.fieldSeparator;
310	var reBoundary = new RegExp(boundary, 'g');
311	var escapeChar = config.escapeChar !== undefined ? config.escapeChar : '\\';
312	var join = function (a) {
313		var s = '';
314
315		// If there is a field boundary, then we might need to escape it in
316		// the source data
317		for (var i = 0, ien = a.length; i < ien; i++) {
318			if (i > 0) {
319				s += separator;
320			}
321
322			s += boundary
323				? boundary + ('' + a[i]).replace(reBoundary, escapeChar + boundary) + boundary
324				: a[i];
325		}
326
327		return s;
328	};
329
330	var header = config.header ? join(data.header) + newLine : '';
331	var footer = config.footer && data.footer ? newLine + join(data.footer) : '';
332	var body = [];
333
334	for (var i = 0, ien = data.body.length; i < ien; i++) {
335		body.push(join(data.body[i]));
336	}
337
338	return {
339		str: header + body.join(newLine) + footer,
340		rows: body.length
341	};
342};
343
344/**
345 * Older versions of Safari (prior to tech preview 18) don't support the
346 * download option required.
347 *
348 * @return {Boolean} `true` if old Safari
349 */
350var _isDuffSafari = function () {
351	var safari =
352		navigator.userAgent.indexOf('Safari') !== -1 &&
353		navigator.userAgent.indexOf('Chrome') === -1 &&
354		navigator.userAgent.indexOf('Opera') === -1;
355
356	if (!safari) {
357		return false;
358	}
359
360	var version = navigator.userAgent.match(/AppleWebKit\/(\d+\.\d+)/);
361	if (version && version.length > 1 && version[1] * 1 < 603.1) {
362		return true;
363	}
364
365	return false;
366};
367
368/**
369 * Convert from numeric position to letter for column names in Excel
370 * @param  {int} n Column number
371 * @return {string} Column letter(s) name
372 */
373function createCellPos(n) {
374	var ordA = 'A'.charCodeAt(0);
375	var ordZ = 'Z'.charCodeAt(0);
376	var len = ordZ - ordA + 1;
377	var s = '';
378
379	while (n >= 0) {
380		s = String.fromCharCode((n % len) + ordA) + s;
381		n = Math.floor(n / len) - 1;
382	}
383
384	return s;
385}
386
387try {
388	var _serialiser = new XMLSerializer();
389	var _ieExcel;
390} catch (t) {}
391
392/**
393 * Recursively add XML files from an object's structure to a ZIP file. This
394 * allows the XSLX file to be easily defined with an object's structure matching
395 * the files structure.
396 *
397 * @param {JSZip} zip ZIP package
398 * @param {object} obj Object to add (recursive)
399 */
400function _addToZip(zip, obj) {
401	if (_ieExcel === undefined) {
402		// Detect if we are dealing with IE's _awful_ serialiser by seeing if it
403		// drop attributes
404		_ieExcel =
405			_serialiser
406				.serializeToString(
407					new window.DOMParser().parseFromString(
408						excelStrings['xl/worksheets/sheet1.xml'],
409						'text/xml'
410					)
411				)
412				.indexOf('xmlns:r') === -1;
413	}
414
415	$.each(obj, function (name, val) {
416		if ($.isPlainObject(val)) {
417			var newDir = zip.folder(name);
418			_addToZip(newDir, val);
419		}
420		else {
421			if (_ieExcel) {
422				// IE's XML serialiser will drop some name space attributes from
423				// from the root node, so we need to save them. Do this by
424				// replacing the namespace nodes with a regular attribute that
425				// we convert back when serialised. Edge does not have this
426				// issue
427				var worksheet = val.childNodes[0];
428				var i, ien;
429				var attrs = [];
430
431				for (i = worksheet.attributes.length - 1; i >= 0; i--) {
432					var attrName = worksheet.attributes[i].nodeName;
433					var attrValue = worksheet.attributes[i].nodeValue;
434
435					if (attrName.indexOf(':') !== -1) {
436						attrs.push({ name: attrName, value: attrValue });
437
438						worksheet.removeAttribute(attrName);
439					}
440				}
441
442				for (i = 0, ien = attrs.length; i < ien; i++) {
443					var attr = val.createAttribute(
444						attrs[i].name.replace(':', '_dt_b_namespace_token_')
445					);
446					attr.value = attrs[i].value;
447					worksheet.setAttributeNode(attr);
448				}
449			}
450
451			var str = _serialiser.serializeToString(val);
452
453			// Fix IE's XML
454			if (_ieExcel) {
455				// IE doesn't include the XML declaration
456				if (str.indexOf('<?xml') === -1) {
457					str = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + str;
458				}
459
460				// Return namespace attributes to being as such
461				str = str.replace(/_dt_b_namespace_token_/g, ':');
462
463				// Remove testing name space that IE puts into the space preserve attr
464				str = str.replace(/xmlns:NS[\d]+="" NS[\d]+:/g, '');
465			}
466
467			// Safari, IE and Edge will put empty name space attributes onto
468			// various elements making them useless. This strips them out
469			str = str.replace(/<([^<>]*?) xmlns=""([^<>]*?)>/g, '<$1 $2>');
470
471			zip.file(name, str);
472		}
473	});
474}
475
476/**
477 * Create an XML node and add any children, attributes, etc without needing to
478 * be verbose in the DOM.
479 *
480 * @param  {object} doc      XML document
481 * @param  {string} nodeName Node name
482 * @param  {object} opts     Options - can be `attr` (attributes), `children`
483 *   (child nodes) and `text` (text content)
484 * @return {node}            Created node
485 */
486function _createNode(doc, nodeName, opts) {
487	var tempNode = doc.createElement(nodeName);
488
489	if (opts) {
490		if (opts.attr) {
491			$(tempNode).attr(opts.attr);
492		}
493
494		if (opts.children) {
495			$.each(opts.children, function (key, value) {
496				tempNode.appendChild(value);
497			});
498		}
499
500		if (opts.text !== null && opts.text !== undefined) {
501			tempNode.appendChild(doc.createTextNode(opts.text));
502		}
503	}
504
505	return tempNode;
506}
507
508/**
509 * Get the width for an Excel column based on the contents of that column
510 * @param  {object} data Data for export
511 * @param  {int}    col  Column index
512 * @return {int}         Column width
513 */
514function _excelColWidth(data, col) {
515	var max = data.header[col].length;
516	var len, lineSplit, str;
517
518	if (data.footer && data.footer[col].length > max) {
519		max = data.footer[col].length;
520	}
521
522	for (var i = 0, ien = data.body.length; i < ien; i++) {
523		var point = data.body[i][col];
524		str = point !== null && point !== undefined ? point.toString() : '';
525
526		// If there is a newline character, workout the width of the column
527		// based on the longest line in the string
528		if (str.indexOf('\n') !== -1) {
529			lineSplit = str.split('\n');
530			lineSplit.sort(function (a, b) {
531				return b.length - a.length;
532			});
533
534			len = lineSplit[0].length;
535		}
536		else {
537			len = str.length;
538		}
539
540		if (len > max) {
541			max = len;
542		}
543
544		// Max width rather than having potentially massive column widths
545		if (max > 40) {
546			return 54; // 40 * 1.35
547		}
548	}
549
550	max *= 1.35;
551
552	// And a min width
553	return max > 6 ? max : 6;
554}
555
556// Excel - Pre-defined strings to build a basic XLSX file
557var excelStrings = {
558	'_rels/.rels':
559		'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
560		'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
561		'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
562		'</Relationships>',
563
564	'xl/_rels/workbook.xml.rels':
565		'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
566		'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
567		'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' +
568		'<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' +
569		'</Relationships>',
570
571	'[Content_Types].xml':
572		'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
573		'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
574		'<Default Extension="xml" ContentType="application/xml" />' +
575		'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />' +
576		'<Default Extension="jpeg" ContentType="image/jpeg" />' +
577		'<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" />' +
578		'<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" />' +
579		'<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" />' +
580		'</Types>',
581
582	'xl/workbook.xml':
583		'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
584		'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' +
585		'<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="24816"/>' +
586		'<workbookPr showInkAnnotation="0" autoCompressPictures="0"/>' +
587		'<bookViews>' +
588		'<workbookView xWindow="0" yWindow="0" windowWidth="25600" windowHeight="19020" tabRatio="500"/>' +
589		'</bookViews>' +
590		'<sheets>' +
591		'<sheet name="Sheet1" sheetId="1" r:id="rId1"/>' +
592		'</sheets>' +
593		'<definedNames/>' +
594		'</workbook>',
595
596	'xl/worksheets/sheet1.xml':
597		'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
598		'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">' +
599		'<sheetData/>' +
600		'<mergeCells count="0"/>' +
601		'</worksheet>',
602
603	'xl/styles.xml':
604		'<?xml version="1.0" encoding="UTF-8"?>' +
605		'<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">' +
606		'<numFmts count="6">' +
607		'<numFmt numFmtId="164" formatCode="#,##0.00_- [$$-45C]"/>' +
608		'<numFmt numFmtId="165" formatCode="&quot;£&quot;#,##0.00"/>' +
609		'<numFmt numFmtId="166" formatCode="[$€-2] #,##0.00"/>' +
610		'<numFmt numFmtId="167" formatCode="0.0%"/>' +
611		'<numFmt numFmtId="168" formatCode="#,##0;(#,##0)"/>' +
612		'<numFmt numFmtId="169" formatCode="#,##0.00;(#,##0.00)"/>' +
613		'</numFmts>' +
614		'<fonts count="5" x14ac:knownFonts="1">' +
615		'<font>' +
616		'<sz val="11" />' +
617		'<name val="Calibri" />' +
618		'</font>' +
619		'<font>' +
620		'<sz val="11" />' +
621		'<name val="Calibri" />' +
622		'<color rgb="FFFFFFFF" />' +
623		'</font>' +
624		'<font>' +
625		'<sz val="11" />' +
626		'<name val="Calibri" />' +
627		'<b />' +
628		'</font>' +
629		'<font>' +
630		'<sz val="11" />' +
631		'<name val="Calibri" />' +
632		'<i />' +
633		'</font>' +
634		'<font>' +
635		'<sz val="11" />' +
636		'<name val="Calibri" />' +
637		'<u />' +
638		'</font>' +
639		'</fonts>' +
640		'<fills count="6">' +
641		'<fill>' +
642		'<patternFill patternType="none" />' +
643		'</fill>' +
644		'<fill>' + // Excel appears to use this as a dotted background regardless of values but
645		'<patternFill patternType="none" />' + // to be valid to the schema, use a patternFill
646		'</fill>' +
647		'<fill>' +
648		'<patternFill patternType="solid">' +
649		'<fgColor rgb="FFD9D9D9" />' +
650		'<bgColor indexed="64" />' +
651		'</patternFill>' +
652		'</fill>' +
653		'<fill>' +
654		'<patternFill patternType="solid">' +
655		'<fgColor rgb="FFD99795" />' +
656		'<bgColor indexed="64" />' +
657		'</patternFill>' +
658		'</fill>' +
659		'<fill>' +
660		'<patternFill patternType="solid">' +
661		'<fgColor rgb="ffc6efce" />' +
662		'<bgColor indexed="64" />' +
663		'</patternFill>' +
664		'</fill>' +
665		'<fill>' +
666		'<patternFill patternType="solid">' +
667		'<fgColor rgb="ffc6cfef" />' +
668		'<bgColor indexed="64" />' +
669		'</patternFill>' +
670		'</fill>' +
671		'</fills>' +
672		'<borders count="2">' +
673		'<border>' +
674		'<left />' +
675		'<right />' +
676		'<top />' +
677		'<bottom />' +
678		'<diagonal />' +
679		'</border>' +
680		'<border diagonalUp="false" diagonalDown="false">' +
681		'<left style="thin">' +
682		'<color auto="1" />' +
683		'</left>' +
684		'<right style="thin">' +
685		'<color auto="1" />' +
686		'</right>' +
687		'<top style="thin">' +
688		'<color auto="1" />' +
689		'</top>' +
690		'<bottom style="thin">' +
691		'<color auto="1" />' +
692		'</bottom>' +
693		'<diagonal />' +
694		'</border>' +
695		'</borders>' +
696		'<cellStyleXfs count="1">' +
697		'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />' +
698		'</cellStyleXfs>' +
699		'<cellXfs count="68">' +
700		'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
701		'<xf numFmtId="0" fontId="1" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
702		'<xf numFmtId="0" fontId="2" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
703		'<xf numFmtId="0" fontId="3" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
704		'<xf numFmtId="0" fontId="4" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
705		'<xf numFmtId="0" fontId="0" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
706		'<xf numFmtId="0" fontId="1" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
707		'<xf numFmtId="0" fontId="2" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
708		'<xf numFmtId="0" fontId="3" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
709		'<xf numFmtId="0" fontId="4" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
710		'<xf numFmtId="0" fontId="0" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
711		'<xf numFmtId="0" fontId="1" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
712		'<xf numFmtId="0" fontId="2" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
713		'<xf numFmtId="0" fontId="3" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
714		'<xf numFmtId="0" fontId="4" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
715		'<xf numFmtId="0" fontId="0" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
716		'<xf numFmtId="0" fontId="1" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
717		'<xf numFmtId="0" fontId="2" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
718		'<xf numFmtId="0" fontId="3" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
719		'<xf numFmtId="0" fontId="4" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
720		'<xf numFmtId="0" fontId="0" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
721		'<xf numFmtId="0" fontId="1" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
722		'<xf numFmtId="0" fontId="2" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
723		'<xf numFmtId="0" fontId="3" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
724		'<xf numFmtId="0" fontId="4" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' +
725		'<xf numFmtId="0" fontId="0" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
726		'<xf numFmtId="0" fontId="1" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
727		'<xf numFmtId="0" fontId="2" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
728		'<xf numFmtId="0" fontId="3" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
729		'<xf numFmtId="0" fontId="4" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
730		'<xf numFmtId="0" fontId="0" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
731		'<xf numFmtId="0" fontId="1" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
732		'<xf numFmtId="0" fontId="2" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
733		'<xf numFmtId="0" fontId="3" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
734		'<xf numFmtId="0" fontId="4" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
735		'<xf numFmtId="0" fontId="0" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
736		'<xf numFmtId="0" fontId="1" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
737		'<xf numFmtId="0" fontId="2" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
738		'<xf numFmtId="0" fontId="3" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
739		'<xf numFmtId="0" fontId="4" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
740		'<xf numFmtId="0" fontId="0" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
741		'<xf numFmtId="0" fontId="1" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
742		'<xf numFmtId="0" fontId="2" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
743		'<xf numFmtId="0" fontId="3" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
744		'<xf numFmtId="0" fontId="4" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
745		'<xf numFmtId="0" fontId="0" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
746		'<xf numFmtId="0" fontId="1" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
747		'<xf numFmtId="0" fontId="2" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
748		'<xf numFmtId="0" fontId="3" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
749		'<xf numFmtId="0" fontId="4" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' +
750		'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' +
751		'<alignment horizontal="left"/>' +
752		'</xf>' +
753		'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' +
754		'<alignment horizontal="center"/>' +
755		'</xf>' +
756		'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' +
757		'<alignment horizontal="right"/>' +
758		'</xf>' +
759		'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' +
760		'<alignment horizontal="fill"/>' +
761		'</xf>' +
762		'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' +
763		'<alignment textRotation="90"/>' +
764		'</xf>' +
765		'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' +
766		'<alignment wrapText="1"/>' +
767		'</xf>' +
768		'<xf numFmtId="9"   fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
769		'<xf numFmtId="164" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
770		'<xf numFmtId="165" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
771		'<xf numFmtId="166" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
772		'<xf numFmtId="167" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
773		'<xf numFmtId="168" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
774		'<xf numFmtId="169" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
775		'<xf numFmtId="3" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
776		'<xf numFmtId="4" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
777		'<xf numFmtId="1" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
778		'<xf numFmtId="2" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
779		'<xf numFmtId="14" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' +
780		'</cellXfs>' +
781		'<cellStyles count="1">' +
782		'<cellStyle name="Normal" xfId="0" builtinId="0" />' +
783		'</cellStyles>' +
784		'<dxfs count="0" />' +
785		'<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleMedium4" />' +
786		'</styleSheet>'
787};
788// Note we could use 3 `for` loops for the styles, but when gzipped there is
789// virtually no difference in size, since the above can be easily compressed
790
791// Pattern matching for special number formats. Perhaps this should be exposed
792// via an API in future?
793// Ref: section 3.8.30 - built in formatters in open spreadsheet
794//   https://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf
795var _excelSpecials = [
796	{
797		match: /^\-?\d+\.\d%$/,
798		style: 60,
799		fmt: function (d) {
800			return d / 100;
801		}
802	}, // Percent with d.p.
803	{
804		match: /^\-?\d+\.?\d*%$/,
805		style: 56,
806		fmt: function (d) {
807			return d / 100;
808		}
809	}, // Percent
810	{ match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars
811	{ match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds
812	{ match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros
813	{ match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators
814	{ match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators
815	{
816		match: /^\([\d,]+\)$/,
817		style: 61,
818		fmt: function (d) {
819			return -1 * d.replace(/[\(\)]/g, '');
820		}
821	}, // Negative numbers indicated by brackets
822	{
823		match: /^\([\d,]+\.\d{2}\)$/,
824		style: 62,
825		fmt: function (d) {
826			return -1 * d.replace(/[\(\)]/g, '');
827		}
828	}, // Negative numbers indicated by brackets - 2d.p.
829	{ match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators
830	{ match: /^\-?[\d,]+\.\d{2}$/, style: 64 },
831	{
832		match: /^[\d]{4}\-[01][\d]\-[0123][\d]$/,
833		style: 67,
834		fmt: function (d) {
835			return Math.round(25569 + Date.parse(d) / (86400 * 1000));
836		}
837	} //Date yyyy-mm-dd
838];
839
840/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
841 * Buttons
842 */
843
844//
845// Copy to clipboard
846//
847DataTable.ext.buttons.copyHtml5 = {
848	className: 'buttons-copy buttons-html5',
849
850	text: function (dt) {
851		return dt.i18n('buttons.copy', 'Copy');
852	},
853
854	action: function (e, dt, button, config) {
855		this.processing(true);
856
857		var that = this;
858		var exportData = _exportData(dt, config);
859		var info = dt.buttons.exportInfo(config);
860		var newline = _newLine(config);
861		var output = exportData.str;
862		var hiddenDiv = $('<div/>').css({
863			height: 1,
864			width: 1,
865			overflow: 'hidden',
866			position: 'fixed',
867			top: 0,
868			left: 0
869		});
870
871		if (info.title) {
872			output = info.title + newline + newline + output;
873		}
874
875		if (info.messageTop) {
876			output = info.messageTop + newline + newline + output;
877		}
878
879		if (info.messageBottom) {
880			output = output + newline + newline + info.messageBottom;
881		}
882
883		if (config.customize) {
884			output = config.customize(output, config, dt);
885		}
886
887		var textarea = $('<textarea readonly/>').val(output).appendTo(hiddenDiv);
888
889		// For browsers that support the copy execCommand, try to use it
890		if (document.queryCommandSupported('copy')) {
891			hiddenDiv.appendTo(dt.table().container());
892			textarea[0].focus();
893			textarea[0].select();
894
895			try {
896				var successful = document.execCommand('copy');
897				hiddenDiv.remove();
898
899				if (successful) {
900					dt.buttons.info(
901						dt.i18n('buttons.copyTitle', 'Copy to clipboard'),
902						dt.i18n(
903							'buttons.copySuccess',
904							{
905								1: 'Copied one row to clipboard',
906								_: 'Copied %d rows to clipboard'
907							},
908							exportData.rows
909						),
910						2000
911					);
912
913					this.processing(false);
914					return;
915				}
916			} catch (t) {}
917		}
918
919		// Otherwise we show the text box and instruct the user to use it
920		var message = $(
921			'<span>' +
922				dt.i18n(
923					'buttons.copyKeys',
924					'Press <i>ctrl</i> or <i>\u2318</i> + <i>C</i> to copy the table data<br>to your system clipboard.<br><br>' +
925						'To cancel, click this message or press escape.'
926				) +
927				'</span>'
928		).append(hiddenDiv);
929
930		dt.buttons.info(dt.i18n('buttons.copyTitle', 'Copy to clipboard'), message, 0);
931
932		// Select the text so when the user activates their system clipboard
933		// it will copy that text
934		textarea[0].focus();
935		textarea[0].select();
936
937		// Event to hide the message when the user is done
938		var container = $(message).closest('.dt-button-info');
939		var close = function () {
940			container.off('click.buttons-copy');
941			$(document).off('.buttons-copy');
942			dt.buttons.info(false);
943		};
944
945		container.on('click.buttons-copy', close);
946		$(document)
947			.on('keydown.buttons-copy', function (e) {
948				if (e.keyCode === 27) {
949					// esc
950					close();
951					that.processing(false);
952				}
953			})
954			.on('copy.buttons-copy cut.buttons-copy', function () {
955				close();
956				that.processing(false);
957			});
958	},
959
960	exportOptions: {},
961
962	fieldSeparator: '\t',
963
964	fieldBoundary: '',
965
966	header: true,
967
968	footer: false,
969
970	title: '*',
971
972	messageTop: '*',
973
974	messageBottom: '*'
975};
976
977//
978// CSV export
979//
980DataTable.ext.buttons.csvHtml5 = {
981	bom: false,
982
983	className: 'buttons-csv buttons-html5',
984
985	available: function () {
986		return window.FileReader !== undefined && window.Blob;
987	},
988
989	text: function (dt) {
990		return dt.i18n('buttons.csv', 'CSV');
991	},
992
993	action: function (e, dt, button, config) {
994		this.processing(true);
995
996		// Set the text
997		var output = _exportData(dt, config).str;
998		var info = dt.buttons.exportInfo(config);
999		var charset = config.charset;
1000
1001		if (config.customize) {
1002			output = config.customize(output, config, dt);
1003		}
1004
1005		if (charset !== false) {
1006			if (!charset) {
1007				charset = document.characterSet || document.charset;
1008			}
1009
1010			if (charset) {
1011				charset = ';charset=' + charset;
1012			}
1013		}
1014		else {
1015			charset = '';
1016		}
1017
1018		if (config.bom) {
1019			output = String.fromCharCode(0xfeff) + output;
1020		}
1021
1022		_saveAs(new Blob([output], { type: 'text/csv' + charset }), info.filename, true);
1023
1024		this.processing(false);
1025	},
1026
1027	filename: '*',
1028
1029	extension: '.csv',
1030
1031	exportOptions: {},
1032
1033	fieldSeparator: ',',
1034
1035	fieldBoundary: '"',
1036
1037	escapeChar: '"',
1038
1039	charset: null,
1040
1041	header: true,
1042
1043	footer: false
1044};
1045
1046//
1047// Excel (xlsx) export
1048//
1049DataTable.ext.buttons.excelHtml5 = {
1050	className: 'buttons-excel buttons-html5',
1051
1052	available: function () {
1053		return (
1054			window.FileReader !== undefined &&
1055			_jsZip() !== undefined &&
1056			!_isDuffSafari() &&
1057			_serialiser
1058		);
1059	},
1060
1061	text: function (dt) {
1062		return dt.i18n('buttons.excel', 'Excel');
1063	},
1064
1065	action: function (e, dt, button, config) {
1066		this.processing(true);
1067
1068		var that = this;
1069		var rowPos = 0;
1070		var dataStartRow, dataEndRow;
1071		var getXml = function (type) {
1072			var str = excelStrings[type];
1073
1074			//str = str.replace( /xmlns:/g, 'xmlns_' ).replace( /mc:/g, 'mc_' );
1075
1076			return $.parseXML(str);
1077		};
1078		var rels = getXml('xl/worksheets/sheet1.xml');
1079		var relsGet = rels.getElementsByTagName('sheetData')[0];
1080
1081		var xlsx = {
1082			_rels: {
1083				'.rels': getXml('_rels/.rels')
1084			},
1085			xl: {
1086				_rels: {
1087					'workbook.xml.rels': getXml('xl/_rels/workbook.xml.rels')
1088				},
1089				'workbook.xml': getXml('xl/workbook.xml'),
1090				'styles.xml': getXml('xl/styles.xml'),
1091				worksheets: {
1092					'sheet1.xml': rels
1093				}
1094			},
1095			'[Content_Types].xml': getXml('[Content_Types].xml')
1096		};
1097
1098		var data = dt.buttons.exportData(config.exportOptions);
1099		var currentRow, rowNode;
1100		var addRow = function (row) {
1101			currentRow = rowPos + 1;
1102			rowNode = _createNode(rels, 'row', { attr: { r: currentRow } });
1103
1104			for (var i = 0, ien = row.length; i < ien; i++) {
1105				// Concat both the Cell Columns as a letter and the Row of the cell.
1106				var cellId = createCellPos(i) + '' + currentRow;
1107				var cell = null;
1108
1109				// For null, undefined of blank cell, continue so it doesn't create the _createNode
1110				if (row[i] === null || row[i] === undefined || row[i] === '') {
1111					if (config.createEmptyCells === true) {
1112						row[i] = '';
1113					}
1114					else {
1115						continue;
1116					}
1117				}
1118
1119				var originalContent = row[i];
1120				row[i] = typeof row[i].trim === 'function' ? row[i].trim() : row[i];
1121
1122				// Special number formatting options
1123				for (var j = 0, jen = _excelSpecials.length; j < jen; j++) {
1124					var special = _excelSpecials[j];
1125
1126					// TODO Need to provide the ability for the specials to say
1127					// if they are returning a string, since at the moment it is
1128					// assumed to be a number
1129					if (row[i].match && !row[i].match(/^0\d+/) && row[i].match(special.match)) {
1130						var val = row[i].replace(/[^\d\.\-]/g, '');
1131
1132						if (special.fmt) {
1133							val = special.fmt(val);
1134						}
1135
1136						cell = _createNode(rels, 'c', {
1137							attr: {
1138								r: cellId,
1139								s: special.style
1140							},
1141							children: [_createNode(rels, 'v', { text: val })]
1142						});
1143
1144						break;
1145					}
1146				}
1147
1148				if (!cell) {
1149					if (
1150						typeof row[i] === 'number' ||
1151						(row[i].match &&
1152							row[i].match(/^-?\d+(\.\d+)?([eE]\-?\d+)?$/) && // Includes exponential format
1153							!row[i].match(/^0\d+/))
1154					) {
1155						// Detect numbers - don't match numbers with leading zeros
1156						// or a negative anywhere but the start
1157						cell = _createNode(rels, 'c', {
1158							attr: {
1159								t: 'n',
1160								r: cellId
1161							},
1162							children: [_createNode(rels, 'v', { text: row[i] })]
1163						});
1164					}
1165					else {
1166						// String output - replace non standard characters for text output
1167						var text = !originalContent.replace
1168							? originalContent
1169							: originalContent.replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, '');
1170
1171						cell = _createNode(rels, 'c', {
1172							attr: {
1173								t: 'inlineStr',
1174								r: cellId
1175							},
1176							children: {
1177								row: _createNode(rels, 'is', {
1178									children: {
1179										row: _createNode(rels, 't', {
1180											text: text,
1181											attr: {
1182												'xml:space': 'preserve'
1183											}
1184										})
1185									}
1186								})
1187							}
1188						});
1189					}
1190				}
1191
1192				rowNode.appendChild(cell);
1193			}
1194
1195			relsGet.appendChild(rowNode);
1196			rowPos++;
1197		};
1198
1199		if (config.customizeData) {
1200			config.customizeData(data);
1201		}
1202
1203		var mergeCells = function (row, colspan) {
1204			var mergeCells = $('mergeCells', rels);
1205
1206			mergeCells[0].appendChild(
1207				_createNode(rels, 'mergeCell', {
1208					attr: {
1209						ref: 'A' + row + ':' + createCellPos(colspan) + row
1210					}
1211				})
1212			);
1213			mergeCells.attr('count', parseFloat(mergeCells.attr('count')) + 1);
1214			$('row:eq(' + (row - 1) + ') c', rels).attr('s', '51'); // centre
1215		};
1216
1217		// Title and top messages
1218		var exportInfo = dt.buttons.exportInfo(config);
1219		if (exportInfo.title) {
1220			addRow([exportInfo.title], rowPos);
1221			mergeCells(rowPos, data.header.length - 1);
1222		}
1223
1224		if (exportInfo.messageTop) {
1225			addRow([exportInfo.messageTop], rowPos);
1226			mergeCells(rowPos, data.header.length - 1);
1227		}
1228
1229		// Table itself
1230		if (config.header) {
1231			addRow(data.header, rowPos);
1232			$('row:last c', rels).attr('s', '2'); // bold
1233		}
1234
1235		dataStartRow = rowPos;
1236
1237		for (var n = 0, ie = data.body.length; n < ie; n++) {
1238			addRow(data.body[n], rowPos);
1239		}
1240
1241		dataEndRow = rowPos;
1242
1243		if (config.footer && data.footer) {
1244			addRow(data.footer, rowPos);
1245			$('row:last c', rels).attr('s', '2'); // bold
1246		}
1247
1248		// Below the table
1249		if (exportInfo.messageBottom) {
1250			addRow([exportInfo.messageBottom], rowPos);
1251			mergeCells(rowPos, data.header.length - 1);
1252		}
1253
1254		// Set column widths
1255		var cols = _createNode(rels, 'cols');
1256		$('worksheet', rels).prepend(cols);
1257
1258		for (var i = 0, ien = data.header.length; i < ien; i++) {
1259			cols.appendChild(
1260				_createNode(rels, 'col', {
1261					attr: {
1262						min: i + 1,
1263						max: i + 1,
1264						width: _excelColWidth(data, i),
1265						customWidth: 1
1266					}
1267				})
1268			);
1269		}
1270
1271		// Workbook modifications
1272		var workbook = xlsx.xl['workbook.xml'];
1273
1274		$('sheets sheet', workbook).attr('name', _sheetname(config));
1275
1276		// Auto filter for columns
1277		if (config.autoFilter) {
1278			$('mergeCells', rels).before(
1279				_createNode(rels, 'autoFilter', {
1280					attr: {
1281						ref:
1282							'A' +
1283							dataStartRow +
1284							':' +
1285							createCellPos(data.header.length - 1) +
1286							dataEndRow
1287					}
1288				})
1289			);
1290
1291			$('definedNames', workbook).append(
1292				_createNode(workbook, 'definedName', {
1293					attr: {
1294						name: '_xlnm._FilterDatabase',
1295						localSheetId: '0',
1296						hidden: 1
1297					},
1298					text:
1299						_sheetname(config) +
1300						'!$A$' +
1301						dataStartRow +
1302						':' +
1303						createCellPos(data.header.length - 1) +
1304						dataEndRow
1305				})
1306			);
1307		}
1308
1309		// Let the developer customise the document if they want to
1310		if (config.customize) {
1311			config.customize(xlsx, config, dt);
1312		}
1313
1314		// Excel doesn't like an empty mergeCells tag
1315		if ($('mergeCells', rels).children().length === 0) {
1316			$('mergeCells', rels).remove();
1317		}
1318
1319		var jszip = _jsZip();
1320		var zip = new jszip();
1321		var zipConfig = {
1322			compression: 'DEFLATE',
1323			type: 'blob',
1324			mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
1325		};
1326
1327		_addToZip(zip, xlsx);
1328
1329		// Modern Excel has a 218 character limit on the file name + path of the file (why!?)
1330		// https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3
1331		// So we truncate to allow for this.
1332		var filename = exportInfo.filename;
1333
1334		if (filename > 175) {
1335			filename = filename.substr(0, 175);
1336		}
1337
1338		if (zip.generateAsync) {
1339			// JSZip 3+
1340			zip.generateAsync(zipConfig).then(function (blob) {
1341				_saveAs(blob, filename);
1342				that.processing(false);
1343			});
1344		}
1345		else {
1346			// JSZip 2.5
1347			_saveAs(zip.generate(zipConfig), filename);
1348			this.processing(false);
1349		}
1350	},
1351
1352	filename: '*',
1353
1354	extension: '.xlsx',
1355
1356	exportOptions: {},
1357
1358	header: true,
1359
1360	footer: false,
1361
1362	title: '*',
1363
1364	messageTop: '*',
1365
1366	messageBottom: '*',
1367
1368	createEmptyCells: false,
1369
1370	autoFilter: false,
1371
1372	sheetName: ''
1373};
1374
1375//
1376// PDF export - using pdfMake - http://pdfmake.org
1377//
1378DataTable.ext.buttons.pdfHtml5 = {
1379	className: 'buttons-pdf buttons-html5',
1380
1381	available: function () {
1382		return window.FileReader !== undefined && _pdfMake();
1383	},
1384
1385	text: function (dt) {
1386		return dt.i18n('buttons.pdf', 'PDF');
1387	},
1388
1389	action: function (e, dt, button, config) {
1390		this.processing(true);
1391
1392		var that = this;
1393		var data = dt.buttons.exportData(config.exportOptions);
1394		var info = dt.buttons.exportInfo(config);
1395		var rows = [];
1396
1397		if (config.header) {
1398			rows.push(
1399				$.map(data.header, function (d) {
1400					return {
1401						text: typeof d === 'string' ? d : d + '',
1402						style: 'tableHeader'
1403					};
1404				})
1405			);
1406		}
1407
1408		for (var i = 0, ien = data.body.length; i < ien; i++) {
1409			rows.push(
1410				$.map(data.body[i], function (d) {
1411					if (d === null || d === undefined) {
1412						d = '';
1413					}
1414					return {
1415						text: typeof d === 'string' ? d : d + '',
1416						style: i % 2 ? 'tableBodyEven' : 'tableBodyOdd'
1417					};
1418				})
1419			);
1420		}
1421
1422		if (config.footer && data.footer) {
1423			rows.push(
1424				$.map(data.footer, function (d) {
1425					return {
1426						text: typeof d === 'string' ? d : d + '',
1427						style: 'tableFooter'
1428					};
1429				})
1430			);
1431		}
1432
1433		var doc = {
1434			pageSize: config.pageSize,
1435			pageOrientation: config.orientation,
1436			content: [
1437				{
1438					table: {
1439						headerRows: 1,
1440						body: rows
1441					},
1442					layout: 'noBorders'
1443				}
1444			],
1445			styles: {
1446				tableHeader: {
1447					bold: true,
1448					fontSize: 11,
1449					color: 'white',
1450					fillColor: '#2d4154',
1451					alignment: 'center'
1452				},
1453				tableBodyEven: {},
1454				tableBodyOdd: {
1455					fillColor: '#f3f3f3'
1456				},
1457				tableFooter: {
1458					bold: true,
1459					fontSize: 11,
1460					color: 'white',
1461					fillColor: '#2d4154'
1462				},
1463				title: {
1464					alignment: 'center',
1465					fontSize: 15
1466				},
1467				message: {}
1468			},
1469			defaultStyle: {
1470				fontSize: 10
1471			}
1472		};
1473
1474		if (info.messageTop) {
1475			doc.content.unshift({
1476				text: info.messageTop,
1477				style: 'message',
1478				margin: [0, 0, 0, 12]
1479			});
1480		}
1481
1482		if (info.messageBottom) {
1483			doc.content.push({
1484				text: info.messageBottom,
1485				style: 'message',
1486				margin: [0, 0, 0, 12]
1487			});
1488		}
1489
1490		if (info.title) {
1491			doc.content.unshift({
1492				text: info.title,
1493				style: 'title',
1494				margin: [0, 0, 0, 12]
1495			});
1496		}
1497
1498		if (config.customize) {
1499			config.customize(doc, config, dt);
1500		}
1501
1502		var pdf = _pdfMake().createPdf(doc);
1503
1504		if (config.download === 'open' && !_isDuffSafari()) {
1505			pdf.open();
1506		}
1507		else {
1508			pdf.download(info.filename);
1509		}
1510
1511		this.processing(false);
1512	},
1513
1514	title: '*',
1515
1516	filename: '*',
1517
1518	extension: '.pdf',
1519
1520	exportOptions: {},
1521
1522	orientation: 'portrait',
1523
1524	pageSize: 'A4',
1525
1526	header: true,
1527
1528	footer: false,
1529
1530	messageTop: '*',
1531
1532	messageBottom: '*',
1533
1534	customize: null,
1535
1536	download: 'download'
1537};
1538
1539
1540return DataTable;
1541}));
1542