cell-xform.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. const utils = require('../../../utils/utils');
  2. const BaseXform = require('../base-xform');
  3. const Range = require('../../../doc/range');
  4. const Enums = require('../../../doc/enums');
  5. const RichTextXform = require('../strings/rich-text-xform');
  6. function getValueType(v) {
  7. if (v === null || v === undefined) {
  8. return Enums.ValueType.Null;
  9. }
  10. if (v instanceof String || typeof v === 'string') {
  11. return Enums.ValueType.String;
  12. }
  13. if (typeof v === 'number') {
  14. return Enums.ValueType.Number;
  15. }
  16. if (typeof v === 'boolean') {
  17. return Enums.ValueType.Boolean;
  18. }
  19. if (v instanceof Date) {
  20. return Enums.ValueType.Date;
  21. }
  22. if (v.text && v.hyperlink) {
  23. return Enums.ValueType.Hyperlink;
  24. }
  25. if (v.formula) {
  26. return Enums.ValueType.Formula;
  27. }
  28. if (v.error) {
  29. return Enums.ValueType.Error;
  30. }
  31. throw new Error('I could not understand type of value');
  32. }
  33. function getEffectiveCellType(cell) {
  34. switch (cell.type) {
  35. case Enums.ValueType.Formula:
  36. return getValueType(cell.result);
  37. default:
  38. return cell.type;
  39. }
  40. }
  41. class CellXform extends BaseXform {
  42. constructor() {
  43. super();
  44. this.richTextXForm = new RichTextXform();
  45. }
  46. get tag() {
  47. return 'c';
  48. }
  49. prepare(model, options) {
  50. const styleId = options.styles.addStyleModel(model.style || {}, getEffectiveCellType(model));
  51. if (styleId) {
  52. model.styleId = styleId;
  53. }
  54. if (model.comment) {
  55. options.comments.push({...model.comment, ref: model.address});
  56. }
  57. switch (model.type) {
  58. case Enums.ValueType.String:
  59. case Enums.ValueType.RichText:
  60. if (options.sharedStrings) {
  61. model.ssId = options.sharedStrings.add(model.value);
  62. }
  63. break;
  64. case Enums.ValueType.Date:
  65. if (options.date1904) {
  66. model.date1904 = true;
  67. }
  68. break;
  69. case Enums.ValueType.Hyperlink:
  70. if (options.sharedStrings && model.text !== undefined && model.text !== null) {
  71. model.ssId = options.sharedStrings.add(model.text);
  72. }
  73. options.hyperlinks.push({
  74. address: model.address,
  75. target: model.hyperlink,
  76. tooltip: model.tooltip,
  77. });
  78. break;
  79. case Enums.ValueType.Merge:
  80. options.merges.add(model);
  81. break;
  82. case Enums.ValueType.Formula:
  83. if (options.date1904) {
  84. // in case valueType is date
  85. model.date1904 = true;
  86. }
  87. if (model.shareType === 'shared') {
  88. model.si = options.siFormulae++;
  89. }
  90. if (model.formula) {
  91. options.formulae[model.address] = model;
  92. } else if (model.sharedFormula) {
  93. const master = options.formulae[model.sharedFormula];
  94. if (!master) {
  95. throw new Error(
  96. `Shared Formula master must exist above and or left of clone for cell ${model.address}`
  97. );
  98. }
  99. if (master.si === undefined) {
  100. master.shareType = 'shared';
  101. master.si = options.siFormulae++;
  102. master.range = new Range(master.address, model.address);
  103. } else if (master.range) {
  104. master.range.expandToAddress(model.address);
  105. }
  106. model.si = master.si;
  107. }
  108. break;
  109. default:
  110. break;
  111. }
  112. }
  113. renderFormula(xmlStream, model) {
  114. let attrs = null;
  115. switch (model.shareType) {
  116. case 'shared':
  117. attrs = {
  118. t: 'shared',
  119. ref: model.ref || model.range.range,
  120. si: model.si,
  121. };
  122. break;
  123. case 'array':
  124. attrs = {
  125. t: 'array',
  126. ref: model.ref,
  127. };
  128. break;
  129. default:
  130. if (model.si !== undefined) {
  131. attrs = {
  132. t: 'shared',
  133. si: model.si,
  134. };
  135. }
  136. break;
  137. }
  138. switch (getValueType(model.result)) {
  139. case Enums.ValueType.Null: // ?
  140. xmlStream.leafNode('f', attrs, model.formula);
  141. break;
  142. case Enums.ValueType.String:
  143. // oddly, formula results don't ever use shared strings
  144. xmlStream.addAttribute('t', 'str');
  145. xmlStream.leafNode('f', attrs, model.formula);
  146. xmlStream.leafNode('v', null, model.result);
  147. break;
  148. case Enums.ValueType.Number:
  149. xmlStream.leafNode('f', attrs, model.formula);
  150. xmlStream.leafNode('v', null, model.result);
  151. break;
  152. case Enums.ValueType.Boolean:
  153. xmlStream.addAttribute('t', 'b');
  154. xmlStream.leafNode('f', attrs, model.formula);
  155. xmlStream.leafNode('v', null, model.result ? 1 : 0);
  156. break;
  157. case Enums.ValueType.Error:
  158. xmlStream.addAttribute('t', 'e');
  159. xmlStream.leafNode('f', attrs, model.formula);
  160. xmlStream.leafNode('v', null, model.result.error);
  161. break;
  162. case Enums.ValueType.Date:
  163. xmlStream.leafNode('f', attrs, model.formula);
  164. xmlStream.leafNode('v', null, utils.dateToExcel(model.result, model.date1904));
  165. break;
  166. // case Enums.ValueType.Hyperlink: // ??
  167. // case Enums.ValueType.Formula:
  168. default:
  169. throw new Error('I could not understand type of value');
  170. }
  171. }
  172. render(xmlStream, model) {
  173. if (model.type === Enums.ValueType.Null && !model.styleId) {
  174. // if null and no style, exit
  175. return;
  176. }
  177. xmlStream.openNode('c');
  178. xmlStream.addAttribute('r', model.address);
  179. if (model.styleId) {
  180. xmlStream.addAttribute('s', model.styleId);
  181. }
  182. switch (model.type) {
  183. case Enums.ValueType.Null:
  184. break;
  185. case Enums.ValueType.Number:
  186. xmlStream.leafNode('v', null, model.value);
  187. break;
  188. case Enums.ValueType.Boolean:
  189. xmlStream.addAttribute('t', 'b');
  190. xmlStream.leafNode('v', null, model.value ? '1' : '0');
  191. break;
  192. case Enums.ValueType.Error:
  193. xmlStream.addAttribute('t', 'e');
  194. xmlStream.leafNode('v', null, model.value.error);
  195. break;
  196. case Enums.ValueType.String:
  197. case Enums.ValueType.RichText:
  198. if (model.ssId !== undefined) {
  199. xmlStream.addAttribute('t', 's');
  200. xmlStream.leafNode('v', null, model.ssId);
  201. } else if (model.value && model.value.richText) {
  202. xmlStream.addAttribute('t', 'inlineStr');
  203. xmlStream.openNode('is');
  204. model.value.richText.forEach(text => {
  205. this.richTextXForm.render(xmlStream, text);
  206. });
  207. xmlStream.closeNode('is');
  208. } else {
  209. xmlStream.addAttribute('t', 'str');
  210. xmlStream.leafNode('v', null, model.value);
  211. }
  212. break;
  213. case Enums.ValueType.Date:
  214. xmlStream.leafNode('v', null, utils.dateToExcel(model.value, model.date1904));
  215. break;
  216. case Enums.ValueType.Hyperlink:
  217. if (model.ssId !== undefined) {
  218. xmlStream.addAttribute('t', 's');
  219. xmlStream.leafNode('v', null, model.ssId);
  220. } else {
  221. xmlStream.addAttribute('t', 'str');
  222. xmlStream.leafNode('v', null, model.text);
  223. }
  224. break;
  225. case Enums.ValueType.Formula:
  226. this.renderFormula(xmlStream, model);
  227. break;
  228. case Enums.ValueType.Merge:
  229. // nothing to add
  230. break;
  231. default:
  232. break;
  233. }
  234. xmlStream.closeNode(); // </c>
  235. }
  236. parseOpen(node) {
  237. if (this.parser) {
  238. this.parser.parseOpen(node);
  239. return true;
  240. }
  241. switch (node.name) {
  242. case 'c':
  243. // const address = colCache.decodeAddress(node.attributes.r);
  244. this.model = {
  245. address: node.attributes.r,
  246. };
  247. this.t = node.attributes.t;
  248. if (node.attributes.s) {
  249. this.model.styleId = parseInt(node.attributes.s, 10);
  250. }
  251. return true;
  252. case 'f':
  253. this.currentNode = 'f';
  254. this.model.si = node.attributes.si;
  255. this.model.shareType = node.attributes.t;
  256. this.model.ref = node.attributes.ref;
  257. return true;
  258. case 'v':
  259. this.currentNode = 'v';
  260. return true;
  261. case 't':
  262. this.currentNode = 't';
  263. return true;
  264. case 'r':
  265. this.parser = this.richTextXForm;
  266. this.parser.parseOpen(node);
  267. return true;
  268. default:
  269. return false;
  270. }
  271. }
  272. parseText(text) {
  273. if (this.parser) {
  274. this.parser.parseText(text);
  275. return;
  276. }
  277. switch (this.currentNode) {
  278. case 'f':
  279. this.model.formula = this.model.formula ? this.model.formula + text : text;
  280. break;
  281. case 'v':
  282. case 't':
  283. if (this.model.value && this.model.value.richText) {
  284. this.model.value.richText.text = this.model.value.richText.text
  285. ? this.model.value.richText.text + text
  286. : text;
  287. } else {
  288. this.model.value = this.model.value ? this.model.value + text : text;
  289. }
  290. break;
  291. default:
  292. break;
  293. }
  294. }
  295. parseClose(name) {
  296. switch (name) {
  297. case 'c': {
  298. const {model} = this;
  299. // first guess on cell type
  300. if (model.formula || model.shareType) {
  301. model.type = Enums.ValueType.Formula;
  302. if (model.value) {
  303. if (this.t === 'str') {
  304. model.result = utils.xmlDecode(model.value);
  305. } else if (this.t === 'b') {
  306. model.result = parseInt(model.value, 10) !== 0;
  307. } else if (this.t === 'e') {
  308. model.result = {error: model.value};
  309. } else {
  310. model.result = parseFloat(model.value);
  311. }
  312. model.value = undefined;
  313. }
  314. } else if (model.value !== undefined) {
  315. switch (this.t) {
  316. case 's':
  317. model.type = Enums.ValueType.String;
  318. model.value = parseInt(model.value, 10);
  319. break;
  320. case 'str':
  321. model.type = Enums.ValueType.String;
  322. model.value = utils.xmlDecode(model.value);
  323. break;
  324. case 'inlineStr':
  325. model.type = Enums.ValueType.String;
  326. break;
  327. case 'b':
  328. model.type = Enums.ValueType.Boolean;
  329. model.value = parseInt(model.value, 10) !== 0;
  330. break;
  331. case 'e':
  332. model.type = Enums.ValueType.Error;
  333. model.value = {error: model.value};
  334. break;
  335. default:
  336. model.type = Enums.ValueType.Number;
  337. model.value = parseFloat(model.value);
  338. break;
  339. }
  340. } else if (model.styleId) {
  341. model.type = Enums.ValueType.Null;
  342. } else {
  343. model.type = Enums.ValueType.Merge;
  344. }
  345. return false;
  346. }
  347. case 'f':
  348. case 'v':
  349. case 'is':
  350. this.currentNode = undefined;
  351. return true;
  352. case 't':
  353. if (this.parser) {
  354. this.parser.parseClose(name);
  355. return true;
  356. }
  357. this.currentNode = undefined;
  358. return true;
  359. case 'r':
  360. this.model.value = this.model.value || {};
  361. this.model.value.richText = this.model.value.richText || [];
  362. this.model.value.richText.push(this.parser.model);
  363. this.parser = undefined;
  364. this.currentNode = undefined;
  365. return true;
  366. default:
  367. if (this.parser) {
  368. this.parser.parseClose(name);
  369. return true;
  370. }
  371. return false;
  372. }
  373. }
  374. reconcile(model, options) {
  375. const style = model.styleId && options.styles && options.styles.getStyleModel(model.styleId);
  376. if (style) {
  377. model.style = style;
  378. }
  379. if (model.styleId !== undefined) {
  380. model.styleId = undefined;
  381. }
  382. switch (model.type) {
  383. case Enums.ValueType.String:
  384. if (typeof model.value === 'number') {
  385. if (options.sharedStrings) {
  386. model.value = options.sharedStrings.getString(model.value);
  387. }
  388. }
  389. if (model.value.richText) {
  390. model.type = Enums.ValueType.RichText;
  391. }
  392. break;
  393. case Enums.ValueType.Number:
  394. if (style && utils.isDateFmt(style.numFmt)) {
  395. model.type = Enums.ValueType.Date;
  396. model.value = utils.excelToDate(model.value, options.date1904);
  397. }
  398. break;
  399. case Enums.ValueType.Formula:
  400. if (model.result !== undefined && style && utils.isDateFmt(style.numFmt)) {
  401. model.result = utils.excelToDate(model.result, options.date1904);
  402. }
  403. if (model.shareType === 'shared') {
  404. if (model.ref) {
  405. // master
  406. options.formulae[model.si] = model.address;
  407. } else {
  408. // slave
  409. model.sharedFormula = options.formulae[model.si];
  410. delete model.shareType;
  411. }
  412. delete model.si;
  413. }
  414. break;
  415. default:
  416. break;
  417. }
  418. // look for hyperlink
  419. const hyperlink = options.hyperlinkMap[model.address];
  420. if (hyperlink) {
  421. if (model.type === Enums.ValueType.Formula) {
  422. model.text = model.result;
  423. model.result = undefined;
  424. } else {
  425. model.text = model.value;
  426. model.value = undefined;
  427. }
  428. model.type = Enums.ValueType.Hyperlink;
  429. model.hyperlink = hyperlink;
  430. }
  431. const comment = options.commentsMap && options.commentsMap[model.address];
  432. if (comment) {
  433. model.comment = comment;
  434. }
  435. }
  436. }
  437. module.exports = CellXform;