workbook.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. 'use strict';
  2. const Worksheet = require('./worksheet');
  3. const DefinedNames = require('./defined-names');
  4. const XLSX = require('../xlsx/xlsx');
  5. const CSV = require('../csv/csv');
  6. // Workbook requirements
  7. // Load and Save from file and stream
  8. // Access/Add/Delete individual worksheets
  9. // Manage String table, Hyperlink table, etc.
  10. // Manage scaffolding for contained objects to write to/read from
  11. class Workbook {
  12. constructor() {
  13. this.category = '';
  14. this.company = '';
  15. this.created = new Date();
  16. this.description = '';
  17. this.keywords = '';
  18. this.manager = '';
  19. this.modified = this.created;
  20. this.properties = {};
  21. this.calcProperties = {};
  22. this._worksheets = [];
  23. this.subject = '';
  24. this.title = '';
  25. this.views = [];
  26. this.media = [];
  27. this._definedNames = new DefinedNames();
  28. }
  29. get xlsx() {
  30. if (!this._xlsx) this._xlsx = new XLSX(this);
  31. return this._xlsx;
  32. }
  33. get csv() {
  34. if (!this._csv) this._csv = new CSV(this);
  35. return this._csv;
  36. }
  37. get nextId() {
  38. // find the next unique spot to add worksheet
  39. for (let i = 1; i < this._worksheets.length; i++) {
  40. if (!this._worksheets[i]) {
  41. return i;
  42. }
  43. }
  44. return this._worksheets.length || 1;
  45. }
  46. addWorksheet(name, options) {
  47. const id = this.nextId;
  48. if (name && name.length > 31) {
  49. // eslint-disable-next-line no-console
  50. console.warn(`Worksheet name ${name} exceeds 31 chars. This will be truncated`);
  51. }
  52. // Illegal character in worksheet name: asterisk (*), question mark (?),
  53. // colon (:), forward slash (/ \), or bracket ([])
  54. if (/[*?:/\\[\]]/.test(name)) {
  55. throw new Error(
  56. `Worksheet name ${name} cannot include any of the following characters: * ? : \\ / [ ]`
  57. );
  58. }
  59. if (/(^')|('$)/.test(name)) {
  60. throw new Error(
  61. `The first or last character of worksheet name cannot be a single quotation mark: ${name}`
  62. );
  63. }
  64. name = (name || `sheet${id}`).substring(0, 31);
  65. if (this._worksheets.find(ws => ws && ws.name.toLowerCase() === name.toLowerCase())) {
  66. throw new Error(`Worksheet name already exists: ${name}`);
  67. }
  68. // if options is a color, call it tabColor (and signal deprecated message)
  69. if (options) {
  70. if (typeof options === 'string') {
  71. // eslint-disable-next-line no-console
  72. console.trace(
  73. 'tabColor argument is now deprecated. Please use workbook.addWorksheet(name, {properties: { tabColor: { argb: "rbg value" } }'
  74. );
  75. options = {
  76. properties: {
  77. tabColor: {argb: options},
  78. },
  79. };
  80. } else if (options.argb || options.theme || options.indexed) {
  81. // eslint-disable-next-line no-console
  82. console.trace(
  83. 'tabColor argument is now deprecated. Please use workbook.addWorksheet(name, {properties: { tabColor: { ... } }'
  84. );
  85. options = {
  86. properties: {
  87. tabColor: options,
  88. },
  89. };
  90. }
  91. }
  92. const lastOrderNo = this._worksheets.reduce(
  93. (acc, ws) => ((ws && ws.orderNo) > acc ? ws.orderNo : acc),
  94. 0
  95. );
  96. const worksheetOptions = Object.assign({}, options, {
  97. id,
  98. name,
  99. orderNo: lastOrderNo + 1,
  100. workbook: this,
  101. });
  102. const worksheet = new Worksheet(worksheetOptions);
  103. this._worksheets[id] = worksheet;
  104. return worksheet;
  105. }
  106. removeWorksheetEx(worksheet) {
  107. delete this._worksheets[worksheet.id];
  108. }
  109. removeWorksheet(id) {
  110. const worksheet = this.getWorksheet(id);
  111. if (worksheet) {
  112. worksheet.destroy();
  113. }
  114. }
  115. getWorksheet(id) {
  116. if (id === undefined) {
  117. return this._worksheets.find(Boolean);
  118. }
  119. if (typeof id === 'number') {
  120. return this._worksheets[id];
  121. }
  122. if (typeof id === 'string') {
  123. return this._worksheets.find(worksheet => worksheet && worksheet.name === id);
  124. }
  125. return undefined;
  126. }
  127. get worksheets() {
  128. // return a clone of _worksheets
  129. return this._worksheets
  130. .slice(1)
  131. .sort((a, b) => a.orderNo - b.orderNo)
  132. .filter(Boolean);
  133. }
  134. eachSheet(iteratee) {
  135. this.worksheets.forEach(sheet => {
  136. iteratee(sheet, sheet.id);
  137. });
  138. }
  139. get definedNames() {
  140. return this._definedNames;
  141. }
  142. clearThemes() {
  143. // Note: themes are not an exposed feature, meddle at your peril!
  144. this._themes = undefined;
  145. }
  146. addImage(image) {
  147. // TODO: validation?
  148. const id = this.media.length;
  149. this.media.push(Object.assign({}, image, {type: 'image'}));
  150. return id;
  151. }
  152. getImage(id) {
  153. return this.media[id];
  154. }
  155. get model() {
  156. return {
  157. creator: this.creator || 'Unknown',
  158. lastModifiedBy: this.lastModifiedBy || 'Unknown',
  159. lastPrinted: this.lastPrinted,
  160. created: this.created,
  161. modified: this.modified,
  162. properties: this.properties,
  163. worksheets: this.worksheets.map(worksheet => worksheet.model),
  164. sheets: this.worksheets.map(ws => ws.model).filter(Boolean),
  165. definedNames: this._definedNames.model,
  166. views: this.views,
  167. company: this.company,
  168. manager: this.manager,
  169. title: this.title,
  170. subject: this.subject,
  171. keywords: this.keywords,
  172. category: this.category,
  173. description: this.description,
  174. language: this.language,
  175. revision: this.revision,
  176. contentStatus: this.contentStatus,
  177. themes: this._themes,
  178. media: this.media,
  179. calcProperties: this.calcProperties,
  180. };
  181. }
  182. set model(value) {
  183. this.creator = value.creator;
  184. this.lastModifiedBy = value.lastModifiedBy;
  185. this.lastPrinted = value.lastPrinted;
  186. this.created = value.created;
  187. this.modified = value.modified;
  188. this.company = value.company;
  189. this.manager = value.manager;
  190. this.title = value.title;
  191. this.subject = value.subject;
  192. this.keywords = value.keywords;
  193. this.category = value.category;
  194. this.description = value.description;
  195. this.language = value.language;
  196. this.revision = value.revision;
  197. this.contentStatus = value.contentStatus;
  198. this.properties = value.properties;
  199. this.calcProperties = value.calcProperties;
  200. this._worksheets = [];
  201. value.worksheets.forEach(worksheetModel => {
  202. const {id, name, state} = worksheetModel;
  203. const orderNo = value.sheets && value.sheets.findIndex(ws => ws.id === id);
  204. const worksheet = (this._worksheets[id] = new Worksheet({
  205. id,
  206. name,
  207. orderNo,
  208. state,
  209. workbook: this,
  210. }));
  211. worksheet.model = worksheetModel;
  212. });
  213. this._definedNames.model = value.definedNames;
  214. this.views = value.views;
  215. this._themes = value.themes;
  216. this.media = value.media || [];
  217. }
  218. }
  219. module.exports = Workbook;