diff --git a/lib/xlsx/xform/sheet/cell-xform.js b/lib/xlsx/xform/sheet/cell-xform.js index e5a6c1eab..2fb075333 100644 --- a/lib/xlsx/xform/sheet/cell-xform.js +++ b/lib/xlsx/xform/sheet/cell-xform.js @@ -12,6 +12,7 @@ var BaseXform = require('../base-xform'); var Enums = require('../../../doc/enums'); var Range = require('../../../doc/range'); +var RichTextXform = require('../strings/rich-text-xform'); function getValueType(v) { if ((v === null) || (v === undefined)) { @@ -45,6 +46,7 @@ function getEffectiveCellType(cell) { var CellXform = module.exports = function() { + this.richTextXForm = new RichTextXform(); }; utils.inherits(CellXform, BaseXform, { @@ -191,8 +193,18 @@ utils.inherits(CellXform, BaseXform, { xmlStream.addAttribute('t', 's'); xmlStream.leafNode('v', null, model.ssId); } else { - xmlStream.addAttribute('t', 'str'); - xmlStream.leafNode('v', null, model.value); + if (model.value && model.value.richText) { + xmlStream.addAttribute('t', 'inlineStr'); + xmlStream.openNode('is'); + var self = this; + model.value.richText.forEach(function(text) { + self.richTextXForm.render(xmlStream, text); + }); + xmlStream.closeNode('is'); + } else { + xmlStream.addAttribute('t', 'str'); + xmlStream.leafNode('v', null, model.value); + } } break; @@ -224,8 +236,12 @@ utils.inherits(CellXform, BaseXform, { xmlStream.closeNode(); // }, - + parseOpen: function(node) { + if (this.parser) { + this.parser.parseOpen(node); + return true; + } switch (node.name) { case 'c': // var address = colCache.decodeAddress(node.attributes.r); @@ -251,17 +267,35 @@ utils.inherits(CellXform, BaseXform, { this.currentNode = 'v'; return true; + case 't': + this.currentNode = 't'; + return true; + + case 'r': + this.parser = this.richTextXForm; + this.parser.parseOpen(node); + return true; + default: return false; } }, parseText: function(text) { + if (this.parser) { + this.parser.parseText(text); + return; + } switch (this.currentNode) { case 'f': this.model.formula = this.model.formula ? this.model.formula + text : text; break; case 'v': - this.model.value = this.model.value ? this.model.value + text : text; + case 't': + if (this.model.value && this.model.value.richText) { + this.model.value.richText.text = this.model.value.richText.text ? this.model.value.richText.text + text : text; + } else { + this.model.value = this.model.value ? this.model.value + text : text; + } break; default: break; @@ -297,6 +331,9 @@ utils.inherits(CellXform, BaseXform, { model.type = Enums.ValueType.String; model.value = utils.xmlDecode(model.value); break; + case 'inlineStr': + model.type = Enums.ValueType.String; + break; case 'b': model.type = Enums.ValueType.Boolean; model.value = parseInt(model.value, 10) !== 0; @@ -318,9 +355,29 @@ utils.inherits(CellXform, BaseXform, { return false; case 'f': case 'v': + case 'is': + this.currentNode = undefined; + return true; + case 't': + if (this.parser) { + this.parser.parseClose(name); + return true; + } else { + this.currentNode = undefined; + return true; + } + case 'r': + this.model.value = this.model.value || {}; + this.model.value.richText = this.model.value.richText || []; + this.model.value.richText.push(this.parser.model); + this.parser = undefined; this.currentNode = undefined; return true; default: + if (this.parser) { + this.parser.parseClose(name); + return true; + } return false; } }, @@ -366,7 +423,7 @@ utils.inherits(CellXform, BaseXform, { default: break; } - + // look for hyperlink var hyperlink = options.hyperlinkMap[model.address]; if (hyperlink) { diff --git a/spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js b/spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js index a9c18fda8..842858ecc 100644 --- a/spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js +++ b/spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js @@ -166,6 +166,49 @@ describe('WorkbookWriter', function() { }); }); + it('rich text', function() { + var options = { + filename: TEST_XLSX_FILE_NAME, + useStyles: true + }; + var wb = new Excel.stream.xlsx.WorkbookWriter(options); + var ws = wb.addWorksheet('Hello'); + + ws.getCell('A1').value = { + richText: [ + { + font: {color: {argb: 'FF0000'}}, text: 'red ' + }, + { + font: {color: {argb: '00FF00'}, bold: true}, text: ' bold green' + } + ] + }; + + ws.getCell('B1').value = 'plain text' + + ws.commit(); + return wb.commit() + .then(function() { + var wb2 = new Excel.Workbook(); + return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME); + }) + .then(function(wb2) { + var ws2 = wb2.getWorksheet('Hello'); + expect(ws2.getCell('A1').value).to.deep.equal({ + richText: [ + { + font: {color: {argb: 'FF0000'}}, text: 'red ' + }, + { + font: {color: {argb: '00FF00'}, bold: true}, text: ' bold green' + } + ] + }); + expect(ws2.getCell('B1').value).to.equal('plain text'); + }); + }); + it('A lot of sheets', function() { this.timeout(5000); diff --git a/spec/unit/xlsx/xform/sheet/cell-xform.spec.js b/spec/unit/xlsx/xform/sheet/cell-xform.spec.js index f73ae6684..03001f971 100644 --- a/spec/unit/xlsx/xform/sheet/cell-xform.spec.js +++ b/spec/unit/xlsx/xform/sheet/cell-xform.spec.js @@ -61,7 +61,7 @@ var expectations = [ tests: ['render', 'renderIn', 'parse'] }, { - title: 'Inline String', + title: 'String', create: function() { return new CellXform(); }, initialModel: {address: 'A1', type: Enums.ValueType.String, value: 'Foo'}, preparedModel: {address: 'A1', type: Enums.ValueType.String, value: 'Foo'}, @@ -71,6 +71,26 @@ var expectations = [ tests: ['prepare', 'render', 'renderIn', 'parse', 'reconcile'], options: { hyperlinkMap: fakeHyperlinkMap, styles: fakeStyles } }, + { + title: 'Inline String with plain text', + create: function() { return new CellXform(); }, + xml: 'Foo', + parsedModel: {address: 'A1', type: Enums.ValueType.String, value: 'Foo'}, + reconciledModel: {address: 'A1', type: Enums.ValueType.String, value: 'Foo'}, + tests: ['parse', 'reconcile'], + options: { hyperlinkMap: fakeHyperlinkMap, styles: fakeStyles } + }, + { + title: 'Inline String with RichText', + create: function() { return new CellXform(); }, + initialModel: {address: 'A1', type: Enums.ValueType.String, value: { richText: [ {font: {color: {argb: 'FF0000'}}, text: 'red'}, {font: {color: {argb: '00FF00'}}, text: 'green'} ] }}, + preparedModel: {address: 'A1', type: Enums.ValueType.String, value: { richText: [ {font: {color: {argb: 'FF0000'}}, text: 'red'}, {font: {color: {argb: '00FF00'}}, text: 'green'} ] }}, + xml: 'redgreen', + parsedModel: {address: 'A1', type: Enums.ValueType.String, value: { richText: [ {font: {color: {argb: 'FF0000'}}, text: 'red'}, {font: {color: {argb: '00FF00'}}, text: 'green'} ] }}, + reconciledModel: {address: 'A1', type: Enums.ValueType.RichText, value: { richText: [ {font: {color: {argb: 'FF0000'}}, text: 'red'}, {font: {color: {argb: '00FF00'}}, text: 'green'} ] }}, + tests: ['prepare', 'render', 'renderIn', 'parse', 'reconcile'], + options: { hyperlinkMap: fakeHyperlinkMap, styles: fakeStyles } + }, { title: 'Shared String', create: function() { return new CellXform(); },