Skip to content

Commit fd6189c

Browse files
authored
Merge pull request #823 from ilimei/comment
add comment support #529
2 parents 54e49be + 7f3e457 commit fd6189c

File tree

17 files changed

+291
-0
lines changed

17 files changed

+291
-0
lines changed

index.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ export type CellValue =
371371
| CellRichTextValue | CellHyperlinkValue
372372
| CellFormulaValue | CellSharedFormulaValue;
373373

374+
export interface Comment {
375+
texts: RichText[];
376+
}
377+
374378
export interface CellModel {
375379
address: Address;
376380
style: Style;
@@ -382,6 +386,7 @@ export interface CellModel {
382386
formula?: string;
383387
sharedFormula?: string;
384388
result?: string | number | any;
389+
comment: Comment;
385390
}
386391

387392
export interface Cell extends Style, Address {
@@ -422,6 +427,11 @@ export interface Cell extends Style, Address {
422427
*/
423428
value: CellValue;
424429

430+
/**
431+
* comment of the cell
432+
*/
433+
comment: Comment;
434+
425435
/**
426436
* convenience getter to access the formula
427437
*/

lib/doc/cell.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ class Cell {
209209
this._value = Value.create(Value.getType(v), this, v);
210210
}
211211

212+
get comment() {
213+
return this._comment;
214+
}
215+
216+
set comment(comment) {
217+
this._comment = comment;
218+
}
219+
212220
get text() {
213221
return this._value.toString();
214222
}
@@ -309,13 +317,17 @@ class Cell {
309317
get model() {
310318
const {model} = this._value;
311319
model.style = this.style;
320+
if(this._comment){
321+
model.comment = this._comment;
322+
}
312323
return model;
313324
}
314325

315326
set model(value) {
316327
this._value.release();
317328
this._value = Value.create(value.type, this);
318329
this._value.model = value;
330+
this._comment = value.comment;
319331
if (value.style) {
320332
this.style = value.style;
321333
} else {

lib/xlsx/rel-type.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ module.exports = {
1111
Image: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
1212
CoreProperties: 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties',
1313
ExtenderProperties: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties',
14+
Comments: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
1415
};
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
const RichTextXform = require('../strings/rich-text-xform');
2+
const utils = require('../../../utils/utils');
3+
const BaseXform = require('../base-xform');
4+
5+
/**
6+
<comment ref="B1" authorId="0">
7+
<text>
8+
<r>
9+
<rPr>
10+
<b/>
11+
<sz val="9"/>
12+
<rFont val="宋体"/>
13+
<charset val="134"/>
14+
</rPr>
15+
<t>51422:</t>
16+
</r>
17+
<r>
18+
<rPr>
19+
<sz val="9"/>
20+
<rFont val="宋体"/>
21+
<charset val="134"/>
22+
</rPr>
23+
<t xml:space="preserve">&#10;test</t>
24+
</r>
25+
</text>
26+
</comment>
27+
*/
28+
29+
const CommentXform = (module.exports = function(model) {
30+
this.model = model;
31+
});
32+
33+
utils.inherits(CommentXform, BaseXform, {
34+
get tag() {
35+
return 'r';
36+
},
37+
38+
get richTextXform() {
39+
if (!this._richTextXform) {
40+
this._richTextXform = new RichTextXform();
41+
}
42+
return this._richTextXform;
43+
},
44+
45+
render(xmlStream, model) {
46+
model = model || this.model;
47+
48+
xmlStream.openNode('comment', {
49+
ref: model.ref,
50+
});
51+
xmlStream.openNode('text');
52+
if (model.texts) {
53+
model.texts.forEach(text => {
54+
this.richTextXform.render(xmlStream, text);
55+
});
56+
}
57+
xmlStream.closeNode();
58+
xmlStream.closeNode();
59+
},
60+
61+
parseOpen(node) {
62+
if (this.parser) {
63+
this.parser.parseOpen(node);
64+
return true;
65+
}
66+
switch (node.name) {
67+
case 'comment':
68+
this.model = {
69+
texts: [],
70+
...node.attributes,
71+
};
72+
return true;
73+
case 'r':
74+
this.parser = this.richTextXform;
75+
this.parser.parseOpen(node);
76+
return true;
77+
default:
78+
return false;
79+
}
80+
},
81+
parseText(text) {
82+
if (this.parser) {
83+
this.parser.parseText(text);
84+
}
85+
},
86+
parseClose(name) {
87+
switch (name) {
88+
case 'comment':
89+
return false;
90+
case 'r':
91+
this.model.texts.push(this.parser.model);
92+
this.parser = undefined;
93+
return true;
94+
default:
95+
if (this.parser) {
96+
this.parser.parseClose(name);
97+
}
98+
return true;
99+
}
100+
},
101+
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
const XmlStream = require('../../../utils/xml-stream');
2+
const utils = require('../../../utils/utils');
3+
const BaseXform = require('../base-xform');
4+
5+
const CommentXform = require('./comment-xform');
6+
7+
const CommentsXform = (module.exports = function() {
8+
this.map = {
9+
comment: new CommentXform(),
10+
};
11+
});
12+
13+
utils.inherits(CommentsXform, BaseXform, {
14+
COMMENTS_ATTRIBUTES: {
15+
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
16+
},
17+
}, {
18+
render(xmlStream, model) {
19+
model = model || this.model;
20+
xmlStream.openXml(XmlStream.StdDocAttributes);
21+
xmlStream.openNode('comments', CommentsXform.COMMENTS_ATTRIBUTES);
22+
xmlStream.openNode('commentList');
23+
if (model.comments) {
24+
model.comments.forEach(comment => {
25+
this.map.comment.render(xmlStream, comment);
26+
});
27+
}
28+
xmlStream.closeNode();
29+
xmlStream.closeNode();
30+
},
31+
32+
parseOpen(node) {
33+
if (this.parser) {
34+
this.parser.parseOpen(node);
35+
return true;
36+
}
37+
switch (node.name) {
38+
case 'commentList':
39+
this.model = {
40+
comments: [],
41+
};
42+
return true;
43+
case 'comment':
44+
this.parser = this.map.comment;
45+
this.parser.parseOpen(node);
46+
return true;
47+
default:
48+
return false;
49+
}
50+
},
51+
parseText(text) {
52+
if (this.parser) {
53+
this.parser.parseText(text);
54+
}
55+
},
56+
parseClose(name) {
57+
switch (name) {
58+
case 'commentList':
59+
return false;
60+
case 'comment':
61+
this.model.comments.push(this.parser.model);
62+
this.parser = undefined;
63+
return true;
64+
default:
65+
if (this.parser) {
66+
this.parser.parseClose(name);
67+
}
68+
return true;
69+
}
70+
},
71+
}
72+
);

lib/xlsx/xform/sheet/cell-xform.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ utils.inherits(CellXform, BaseXform, {
6060
model.styleId = styleId;
6161
}
6262

63+
if (model.comment) {
64+
options.comments.push({ ...model.comment, ref: model.address });
65+
}
66+
6367
switch (model.type) {
6468
case Enums.ValueType.String:
6569
if (options.sharedStrings) {
@@ -441,5 +445,10 @@ utils.inherits(CellXform, BaseXform, {
441445
model.type = Enums.ValueType.Hyperlink;
442446
model.hyperlink = hyperlink;
443447
}
448+
449+
const comment = options.commentsMap && options.commentsMap[model.address];
450+
if (comment) {
451+
model.comment = comment;
452+
}
444453
},
445454
});

lib/xlsx/xform/sheet/worksheet-xform.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ utils.inherits(
6767
prepare(model, options) {
6868
options.merges = new Merges();
6969
model.hyperlinks = options.hyperlinks = [];
70+
model.comments = options.comments = [];
7071

7172
options.formulae = {};
7273
options.siFormulae = 0;
@@ -92,6 +93,16 @@ utils.inherits(
9293
});
9394
});
9495

96+
// prepare comment relationships
97+
if (model.comments.length > 0) {
98+
rId = nextRid(rels);
99+
rels.push({
100+
Id: rId,
101+
Type: RelType.Comments,
102+
Target: `../comments${model.id}.xml`,
103+
});
104+
}
105+
95106
const drawingRelsHash = [];
96107
let bookImage;
97108
model.media.forEach(medium => {
@@ -269,6 +280,15 @@ utils.inherits(
269280
// options.merges.reconcile(model.mergeCells, model.rows);
270281
const rels = (model.relationships || []).reduce((h, rel) => {
271282
h[rel.Id] = rel;
283+
if (rel.Type === RelType.Comments) {
284+
model.comments = options.comments[rel.Target].comments;
285+
}
286+
return h;
287+
}, {});
288+
options.commentsMap = (model.comments || []).reduce((h, comment) => {
289+
if (comment.ref) {
290+
h[comment.ref] = comment;
291+
}
272292
return h;
273293
}, {});
274294
options.hyperlinkMap = (model.hyperlinks || []).reduce((h, hyperlink) => {
@@ -322,6 +342,7 @@ utils.inherits(
322342

323343
delete model.relationships;
324344
delete model.hyperlinks;
345+
delete model.comments;
325346
},
326347
}
327348
);

lib/xlsx/xlsx.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const AppXform = require('./xform/core/app-xform');
1818
const WorkbookXform = require('./xform/book/workbook-xform');
1919
const WorksheetXform = require('./xform/sheet/worksheet-xform');
2020
const DrawingXform = require('./xform/drawing/drawing-xform');
21+
const CommentXform = require('./xform/comment/comments-xform');
2122

2223
const theme1Xml = require('./xml/theme1.js');
2324

@@ -108,6 +109,7 @@ XLSX.prototype = {
108109
mediaIndex: model.mediaIndex,
109110
date1904: model.properties && model.properties.date1904,
110111
drawings: model.drawings,
112+
comments: model.comments,
111113
};
112114
model.worksheets.forEach(worksheet => {
113115
worksheet.relationships = model.worksheetRels[worksheet.sheetNo];
@@ -139,6 +141,17 @@ XLSX.prototype = {
139141
}
140142
return undefined;
141143
},
144+
processCommentEntry(entry, model) {
145+
const match = entry.path.match(/xl\/(comments\d+)[.]xml/);
146+
if (match) {
147+
const name = match[1];
148+
const xform = new CommentXform();
149+
return xform.parseStream(entry).then(comments => {
150+
model.comments[`../${name}.xml`] = comments;
151+
});
152+
}
153+
return undefined;
154+
},
142155
processWorksheetRelsEntry(entry, model) {
143156
const match = entry.path.match(/xl\/worksheets\/_rels\/sheet(\d+)[.]xml.rels/);
144157
if (match) {
@@ -237,6 +250,7 @@ XLSX.prototype = {
237250
mediaIndex: {},
238251
drawings: {},
239252
drawingRels: {},
253+
comments: {},
240254
};
241255

242256
// we have to be prepared to read the zip entries in whatever order they arrive
@@ -309,6 +323,7 @@ XLSX.prototype = {
309323
self.processThemeEntry(entry, model) ||
310324
self.processMediaEntry(entry, model) ||
311325
self.processDrawingEntry(entry, model) ||
326+
self.processCommentEntry(entry, model) ||
312327
self.processDrawingRelsEntry(entry, model) ||
313328
self.processIgnoreEntry(entry);
314329
break;
@@ -543,6 +558,7 @@ XLSX.prototype = {
543558
// preparation phase
544559
const worksheetXform = new WorksheetXform();
545560
const relationshipsXform = new RelationshipsXform();
561+
const commentXform = new CommentXform();
546562

547563
// write sheets
548564
model.worksheets.forEach(worksheet => {
@@ -555,6 +571,12 @@ XLSX.prototype = {
555571
relationshipsXform.render(xmlStream, worksheet.rels);
556572
zip.append(xmlStream.xml, { name: `xl/worksheets/_rels/sheet${worksheet.id}.xml.rels` });
557573
}
574+
575+
if (worksheet.comments.length > 0) {
576+
xmlStream = new XmlStream();
577+
commentXform.render(xmlStream, worksheet);
578+
zip.append(xmlStream.xml, { name: `xl/comments${worksheet.id}.xml` });
579+
}
558580
});
559581

560582
resolve();

0 commit comments

Comments
 (0)