worksheet-reader.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. "use strict";
  2. const {
  3. EventEmitter
  4. } = require('events');
  5. const parseSax = require('../../utils/parse-sax');
  6. const _ = require('../../utils/under-dash');
  7. const utils = require('../../utils/utils');
  8. const colCache = require('../../utils/col-cache');
  9. const Dimensions = require('../../doc/range');
  10. const Row = require('../../doc/row');
  11. const Column = require('../../doc/column');
  12. class WorksheetReader extends EventEmitter {
  13. constructor(_ref) {
  14. let {
  15. workbook,
  16. id,
  17. iterator,
  18. options
  19. } = _ref;
  20. super();
  21. this.workbook = workbook;
  22. this.id = id;
  23. this.iterator = iterator;
  24. this.options = options || {};
  25. // and a name
  26. this.name = `Sheet${this.id}`;
  27. // column definitions
  28. this._columns = null;
  29. this._keys = {};
  30. // keep a record of dimensions
  31. this._dimensions = new Dimensions();
  32. }
  33. // destroy - not a valid operation for a streaming writer
  34. // even though some streamers might be able to, it's a bad idea.
  35. destroy() {
  36. throw new Error('Invalid Operation: destroy');
  37. }
  38. // return the current dimensions of the writer
  39. get dimensions() {
  40. return this._dimensions;
  41. }
  42. // =========================================================================
  43. // Columns
  44. // get the current columns array.
  45. get columns() {
  46. return this._columns;
  47. }
  48. // get a single column by col number. If it doesn't exist, it and any gaps before it
  49. // are created.
  50. getColumn(c) {
  51. if (typeof c === 'string') {
  52. // if it matches a key'd column, return that
  53. const col = this._keys[c];
  54. if (col) {
  55. return col;
  56. }
  57. // otherise, assume letter
  58. c = colCache.l2n(c);
  59. }
  60. if (!this._columns) {
  61. this._columns = [];
  62. }
  63. if (c > this._columns.length) {
  64. let n = this._columns.length + 1;
  65. while (n <= c) {
  66. this._columns.push(new Column(this, n++));
  67. }
  68. }
  69. return this._columns[c - 1];
  70. }
  71. getColumnKey(key) {
  72. return this._keys[key];
  73. }
  74. setColumnKey(key, value) {
  75. this._keys[key] = value;
  76. }
  77. deleteColumnKey(key) {
  78. delete this._keys[key];
  79. }
  80. eachColumnKey(f) {
  81. _.each(this._keys, f);
  82. }
  83. async read() {
  84. try {
  85. for await (const events of this.parse()) {
  86. for (const {
  87. eventType,
  88. value
  89. } of events) {
  90. this.emit(eventType, value);
  91. }
  92. }
  93. this.emit('finished');
  94. } catch (error) {
  95. this.emit('error', error);
  96. }
  97. }
  98. async *[Symbol.asyncIterator]() {
  99. for await (const events of this.parse()) {
  100. for (const {
  101. eventType,
  102. value
  103. } of events) {
  104. if (eventType === 'row') {
  105. yield value;
  106. }
  107. }
  108. }
  109. }
  110. async *parse() {
  111. const {
  112. iterator,
  113. options
  114. } = this;
  115. let emitSheet = false;
  116. let emitHyperlinks = false;
  117. let hyperlinks = null;
  118. switch (options.worksheets) {
  119. case 'emit':
  120. emitSheet = true;
  121. break;
  122. case 'prep':
  123. break;
  124. default:
  125. break;
  126. }
  127. switch (options.hyperlinks) {
  128. case 'emit':
  129. emitHyperlinks = true;
  130. break;
  131. case 'cache':
  132. this.hyperlinks = hyperlinks = {};
  133. break;
  134. default:
  135. break;
  136. }
  137. if (!emitSheet && !emitHyperlinks && !hyperlinks) {
  138. return;
  139. }
  140. // references
  141. const {
  142. sharedStrings,
  143. styles,
  144. properties
  145. } = this.workbook;
  146. // xml position
  147. let inCols = false;
  148. let inRows = false;
  149. let inHyperlinks = false;
  150. // parse state
  151. let cols = null;
  152. let row = null;
  153. let c = null;
  154. let current = null;
  155. for await (const events of parseSax(iterator)) {
  156. const worksheetEvents = [];
  157. for (const {
  158. eventType,
  159. value
  160. } of events) {
  161. if (eventType === 'opentag') {
  162. const node = value;
  163. if (emitSheet) {
  164. switch (node.name) {
  165. case 'cols':
  166. inCols = true;
  167. cols = [];
  168. break;
  169. case 'sheetData':
  170. inRows = true;
  171. break;
  172. case 'col':
  173. if (inCols) {
  174. cols.push({
  175. min: parseInt(node.attributes.min, 10),
  176. max: parseInt(node.attributes.max, 10),
  177. width: parseFloat(node.attributes.width),
  178. styleId: parseInt(node.attributes.style || '0', 10)
  179. });
  180. }
  181. break;
  182. case 'row':
  183. if (inRows) {
  184. const r = parseInt(node.attributes.r, 10);
  185. row = new Row(this, r);
  186. if (node.attributes.ht) {
  187. row.height = parseFloat(node.attributes.ht);
  188. }
  189. if (node.attributes.s) {
  190. const styleId = parseInt(node.attributes.s, 10);
  191. const style = styles.getStyleModel(styleId);
  192. if (style) {
  193. row.style = style;
  194. }
  195. }
  196. }
  197. break;
  198. case 'c':
  199. if (row) {
  200. c = {
  201. ref: node.attributes.r,
  202. s: parseInt(node.attributes.s, 10),
  203. t: node.attributes.t
  204. };
  205. }
  206. break;
  207. case 'f':
  208. if (c) {
  209. current = c.f = {
  210. text: ''
  211. };
  212. }
  213. break;
  214. case 'v':
  215. if (c) {
  216. current = c.v = {
  217. text: ''
  218. };
  219. }
  220. break;
  221. case 'is':
  222. case 't':
  223. if (c) {
  224. current = c.v = {
  225. text: ''
  226. };
  227. }
  228. break;
  229. case 'mergeCell':
  230. break;
  231. default:
  232. break;
  233. }
  234. }
  235. // =================================================================
  236. //
  237. if (emitHyperlinks || hyperlinks) {
  238. switch (node.name) {
  239. case 'hyperlinks':
  240. inHyperlinks = true;
  241. break;
  242. case 'hyperlink':
  243. if (inHyperlinks) {
  244. const hyperlink = {
  245. ref: node.attributes.ref,
  246. rId: node.attributes['r:id']
  247. };
  248. if (emitHyperlinks) {
  249. worksheetEvents.push({
  250. eventType: 'hyperlink',
  251. value: hyperlink
  252. });
  253. } else {
  254. hyperlinks[hyperlink.ref] = hyperlink;
  255. }
  256. }
  257. break;
  258. default:
  259. break;
  260. }
  261. }
  262. } else if (eventType === 'text') {
  263. // only text data is for sheet values
  264. if (emitSheet) {
  265. if (current) {
  266. current.text += value;
  267. }
  268. }
  269. } else if (eventType === 'closetag') {
  270. const node = value;
  271. if (emitSheet) {
  272. switch (node.name) {
  273. case 'cols':
  274. inCols = false;
  275. this._columns = Column.fromModel(cols);
  276. break;
  277. case 'sheetData':
  278. inRows = false;
  279. break;
  280. case 'row':
  281. this._dimensions.expandRow(row);
  282. worksheetEvents.push({
  283. eventType: 'row',
  284. value: row
  285. });
  286. row = null;
  287. break;
  288. case 'c':
  289. if (row && c) {
  290. const address = colCache.decodeAddress(c.ref);
  291. const cell = row.getCell(address.col);
  292. if (c.s) {
  293. const style = styles.getStyleModel(c.s);
  294. if (style) {
  295. cell.style = style;
  296. }
  297. }
  298. if (c.f) {
  299. const cellValue = {
  300. formula: c.f.text
  301. };
  302. if (c.v) {
  303. if (c.t === 'str') {
  304. cellValue.result = utils.xmlDecode(c.v.text);
  305. } else {
  306. cellValue.result = parseFloat(c.v.text);
  307. }
  308. }
  309. cell.value = cellValue;
  310. } else if (c.v) {
  311. switch (c.t) {
  312. case 's':
  313. {
  314. const index = parseInt(c.v.text, 10);
  315. if (sharedStrings) {
  316. cell.value = sharedStrings[index];
  317. } else {
  318. cell.value = {
  319. sharedString: index
  320. };
  321. }
  322. break;
  323. }
  324. case 'inlineStr':
  325. case 'str':
  326. cell.value = utils.xmlDecode(c.v.text);
  327. break;
  328. case 'e':
  329. cell.value = {
  330. error: c.v.text
  331. };
  332. break;
  333. case 'b':
  334. cell.value = parseInt(c.v.text, 10) !== 0;
  335. break;
  336. default:
  337. if (utils.isDateFmt(cell.numFmt)) {
  338. cell.value = utils.excelToDate(parseFloat(c.v.text), properties.model && properties.model.date1904);
  339. } else {
  340. cell.value = parseFloat(c.v.text);
  341. }
  342. break;
  343. }
  344. }
  345. if (hyperlinks) {
  346. const hyperlink = hyperlinks[c.ref];
  347. if (hyperlink) {
  348. cell.text = cell.value;
  349. cell.value = undefined;
  350. cell.hyperlink = hyperlink;
  351. }
  352. }
  353. c = null;
  354. }
  355. break;
  356. default:
  357. break;
  358. }
  359. }
  360. if (emitHyperlinks || hyperlinks) {
  361. switch (node.name) {
  362. case 'hyperlinks':
  363. inHyperlinks = false;
  364. break;
  365. default:
  366. break;
  367. }
  368. }
  369. }
  370. }
  371. if (worksheetEvents.length > 0) {
  372. yield worksheetEvents;
  373. }
  374. }
  375. }
  376. }
  377. module.exports = WorksheetReader;
  378. //# sourceMappingURL=worksheet-reader.js.map