Skip to content

Commit c1e348c

Browse files
authored
Merge pull request #5 from zurmokeeper/bugfix/internal_hyperlink_error
Bugfix/internal hyperlink error
2 parents 677a1c5 + a896c80 commit c1e348c

File tree

9 files changed

+139
-34
lines changed

9 files changed

+139
-34
lines changed

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ExcelJS
1+
# @zurmokeeper/exceljs
22

33
[![Build status](https://github.com/exceljs/exceljs/workflows/ExcelJS/badge.svg)](https://github.com/exceljs/exceljs/actions?query=workflow%3AExcelJS)
44

@@ -16,6 +16,33 @@ Reverse engineered from Excel spreadsheet files as a project.
1616
npm install @zurmokeeper/exceljs
1717
```
1818

19+
# V4.4.2 New Features!
20+
21+
Change Log:
22+
23+
* 1: Fixbug: [Internal hyperlink does not work on wps office](https://github.com/zurmokeeper/excelize/issues/4). (Break change) and support new internal hyperlink methods。
24+
* 2:Add type definition for WorksheetModel.merges, Thank you <a href="https://github.com/ytjmt">ytjmt</a>, Merged <a href="https://github.com/exceljs/exceljs/pull/2281"> PR2281</a>.
25+
* 3:Add type definition for WorksheetProtection.spinCount,Thank you <a href="https://github.com/damingerdai">damingerdai</a>, Merged <a href="https://github.com/exceljs/exceljs/pull/2284"> PR2284</a>.
26+
27+
PS: Since V4.4.2 @zurmokeeper/exceljs new cell insertion internal hyperlink support `Sheet2!A1:B1` and `A1:B1` and other forms, the original only supports `Sheet2!A1`, use the following way::
28+
29+
30+
```
31+
const wb = new ExcelJS.Workbook();
32+
const ws1 = wb.addWorksheet('Sheet1');
33+
const ws2 = wb.addWorksheet('Sheet2');
34+
35+
'#' is required, @zurmokeeper/exceljs is to distinguish internal hyperlink by '#', the default will be considered non-internal hyperlink, older versions also need to manually add '#' , how not to add if
36+
// internal hyperlink
37+
ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#Sheet2!A1' };
38+
39+
// internal hyperlink
40+
ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#Sheet2!A1:B1' };
41+
42+
// internal hyperlink
43+
ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#A1:B1' };
44+
```
45+
1946
# V4.4.1 New Features!
2047

2148
Change Log:
@@ -227,7 +254,7 @@ To be clear, all contributions added to this library will be included in the lib
227254
# Importing[](#contents)<!-- Link generated with jump2header -->
228255

229256
```javascript
230-
const ExcelJS = require('exceljs');
257+
const ExcelJS = require('@zurmokeeper/exceljs');
231258
```
232259

233260
## ES5 Imports[](#contents)<!-- Link generated with jump2header -->

README_zh.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ExcelJS
1+
# @zurmokeeper/exceljs
22

33
[![Build status](https://github.com/exceljs/exceljs/workflows/ExcelJS/badge.svg)](https://github.com/exceljs/exceljs/actions?query=workflow%3AExcelJS)
44
[![Code Quality: Javascript](https://img.shields.io/lgtm/grade/javascript/g/exceljs/exceljs.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/exceljs/exceljs/context:javascript)
@@ -14,6 +14,33 @@
1414
npm install @zurmokeeper/exceljs
1515
```
1616

17+
# V4.4.2 新的功能!
18+
19+
变更日志:
20+
21+
* 1: Fixbug: [Internal hyperlink does not work on wps office](https://github.com/zurmokeeper/excelize/issues/4). (Break change) 和支持新的内部链接方式。
22+
* 2:Add type definition for WorksheetModel.merges, Thank you <a href="https://github.com/ytjmt">ytjmt</a>, Merged <a href="https://github.com/exceljs/exceljs/pull/2281"> PR2281</a>.
23+
* 3:Add type definition for WorksheetProtection.spinCount,Thank you <a href="https://github.com/damingerdai">damingerdai</a>, Merged <a href="https://github.com/exceljs/exceljs/pull/2284"> PR2284</a>.
24+
25+
PS: 自 V4.4.2 @zurmokeeper/exceljs 新增单元格插入内部链接支持 Sheet2!A1:B1 和 A1:B1等形式,原来只支持 Sheet2!A1,使用方式如下:
26+
27+
28+
```
29+
const wb = new ExcelJS.Workbook();
30+
const ws1 = wb.addWorksheet('Sheet1');
31+
const ws2 = wb.addWorksheet('Sheet2');
32+
33+
'#'是必须的,@zurmokeeper/exceljs 是通过'#'来区分内部链接的,默认会被认为是非内部链接,旧版本使用也要手动加上 '#' ,如何没加的话
34+
// internal hyperlink
35+
ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#Sheet2!A1' };
36+
37+
// internal hyperlink
38+
ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#Sheet2!A1:B1' };
39+
40+
// internal hyperlink
41+
ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#A1:B1' };
42+
```
43+
1744
# V4.4.1 新的功能!
1845

1946
变更日志:
@@ -188,7 +215,7 @@ npm install @zurmokeeper/exceljs
188215
# 导入[](#目录)<!-- Link generated with jump2header -->
189216

190217
```javascript
191-
const ExcelJS = require('exceljs');
218+
const ExcelJS = require('@zurmokeeper/exceljs');
192219
```
193220

194221
## ES5 导入[](#目录)<!-- Link generated with jump2header -->

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,7 @@ export interface WorksheetProtection {
880880
sort: boolean;
881881
autoFilter: boolean;
882882
pivotTables: boolean;
883+
spinCount: number;
883884
}
884885
export interface Image {
885886
extension: 'jpeg' | 'png' | 'gif';
@@ -989,6 +990,7 @@ export interface WorksheetModel {
989990
views: WorksheetView[];
990991
autoFilter: AutoFilter;
991992
media: Media[];
993+
merges: Range['range'][];
992994
}
993995
export type WorksheetState = 'visible' | 'hidden' | 'veryHidden';
994996

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ class HyperlinkXform extends BaseXform {
77

88
render(xmlStream, model) {
99
if (this.isInternalLink(model)) {
10+
// Remove '#' example #sheet1!A1 -> sheet1!A1
11+
model.target = model.target ? model.target.slice(1) : model.target;
1012
xmlStream.leafNode('hyperlink', {
1113
ref: model.address,
12-
'r:id': model.rId,
14+
// 'r:id': model.rId, // Internal hyperlink don't need 'r:id', it's enough to have location
1315
tooltip: model.tooltip,
1416
location: model.target,
17+
display: model.tooltip, // TODO: For the time being, this is compatible with google sheet. https://www.google.cn/sheets/about/
1518
});
1619
} else {
1720
xmlStream.leafNode('hyperlink', {
@@ -45,9 +48,23 @@ class HyperlinkXform extends BaseXform {
4548
return false;
4649
}
4750

51+
/**
52+
* @desc example Sheet2!D3 Sheet2!D3:E3 D3:E3
53+
* @returns
54+
*/
4855
isInternalLink(model) {
4956
// @example: Sheet2!D3, return true
50-
return model.target && /^[^!]+![a-zA-Z]+[\d]+$/.test(model.target);
57+
// return model.target && /^[^!]+![a-zA-Z]+[\d]+$/.test(model.target);
58+
59+
// Using regular expressions is not enough to cover all cases like the one below,
60+
// An example of the xlsx library, which is also generic
61+
// https://docs.sheetjs.com/docs/csf/features/hyperlinks#internal-links
62+
// ws["C1"].l = { Target: "#SheetJSDN", Tooltip: "Defined Name" };
63+
// wb.Workbook = {
64+
// Names: [{Name: "SheetJSDN", Ref:"Sheet2!A1:B2"}]
65+
// }
66+
// an example of the xlsx library, so instead pass '#' manually to determine if it is an internal hyperlink.
67+
return model.target && model.target.slice(0, 1) === '#';
5168
}
5269
}
5370

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,23 @@ class WorkSheetXform extends BaseXform {
152152
return `rId${r.length + 1}`;
153153
}
154154

155+
// TODO: The same as HyperlinkXform. isInternalLink
156+
function isInternalLink(m) {
157+
return m.target && m.target.slice(0, 1) === '#';
158+
}
159+
155160
model.hyperlinks.forEach(hyperlink => {
156161
const rId = nextRid(rels);
157162
hyperlink.rId = rId;
158-
rels.push({
159-
Id: rId,
160-
Type: RelType.Hyperlink,
161-
Target: hyperlink.target,
162-
TargetMode: 'External',
163-
});
163+
// Internal hyperlink do not need to generate worksheets/_rels/sheetx.xml.rels, but external hyperlink do.
164+
if (!isInternalLink(hyperlink)) {
165+
rels.push({
166+
Id: rId,
167+
Type: RelType.Hyperlink,
168+
Target: hyperlink.target,
169+
TargetMode: 'External',
170+
});
171+
}
164172
});
165173

166174
// prepare comment relationships
@@ -221,9 +229,7 @@ class WorkSheetXform extends BaseXform {
221229
});
222230
}
223231
let rIdImage =
224-
this.preImageId === medium.imageId
225-
? drawingRelsHash[medium.imageId]
226-
: drawingRelsHash[drawing.rels.length];
232+
this.preImageId === medium.imageId ? drawingRelsHash[medium.imageId] : drawingRelsHash[drawing.rels.length];
227233
if (!rIdImage) {
228234
rIdImage = nextRid(drawing.rels);
229235
drawingRelsHash[drawing.rels.length] = rIdImage;
@@ -405,11 +411,7 @@ class WorkSheetXform extends BaseXform {
405411
false,
406412
margins: this.map.pageMargins.model,
407413
};
408-
const pageSetup = Object.assign(
409-
sheetProperties,
410-
this.map.pageSetup.model,
411-
this.map.printOptions.model
412-
);
414+
const pageSetup = Object.assign(sheetProperties, this.map.pageSetup.model, this.map.printOptions.model);
413415
const conditionalFormattings = mergeConditionalFormattings(
414416
this.map.conditionalFormatting.model,
415417
this.map.extLst.model && this.map.extLst.model['x14:conditionalFormattings']

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
{
22
"name": "@zurmokeeper/exceljs",
3-
"version": "4.4.1",
3+
"version": "4.4.2",
44
"description": "Excel Workbook Manager - Read and Write xlsx and csv Files.",
55
"private": false,
66
"license": "MIT",
7+
"publishConfig": {
8+
"access": "public"
9+
},
710
"author": {
811
"name": "zurmokeeper",
912
"email": "[email protected]"

spec/integration/issues/issue-2247-cryptor.spec.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('pr related issues', () => {
1515
});
1616
const sheetName = workbook.getWorksheet(1).name;
1717
expect(sheetName).to.equal('Sheet1');
18-
});
18+
}).timeout(10000);
1919

2020
it('workbook.xlsx.readFile, ecma376_agile encryption method decrypted successfully', async () => {
2121
const workbook = new ExcelJS.Workbook();
@@ -24,7 +24,7 @@ describe('pr related issues', () => {
2424
});
2525
const sheetName = workbook.getWorksheet(1).name;
2626
expect(sheetName).to.equal('Sheet1');
27-
});
27+
}).timeout(10000);
2828

2929
it('workbook.xlsx.load, ecma376_standard encryption method decrypted successfully ', async () => {
3030
const workbook = new ExcelJS.Workbook();
@@ -34,7 +34,7 @@ describe('pr related issues', () => {
3434
});
3535
const sheetName = workbook.getWorksheet(1).name;
3636
expect(sheetName).to.equal('Sheet1');
37-
});
37+
}).timeout(10000);
3838

3939
it('workbook.xlsx.load, ecma376_agile encryption method decrypted successfully ', async () => {
4040
const workbook = new ExcelJS.Workbook();
@@ -47,7 +47,7 @@ describe('pr related issues', () => {
4747
);
4848
const sheetName = workbook.getWorksheet(1).name;
4949
expect(sheetName).to.equal('Sheet1');
50-
});
50+
}).timeout(10000);
5151

5252
it('workbook.xlsx.load, options.base64 = true, ecma376_standard encryption method decrypted successfully ', async () => {
5353
const workbook = new ExcelJS.Workbook();
@@ -60,7 +60,7 @@ describe('pr related issues', () => {
6060
});
6161
const sheetName = workbook.getWorksheet(1).name;
6262
expect(sheetName).to.equal('Sheet1');
63-
});
63+
}).timeout(10000);
6464

6565
it('workbook.xlsx.load, options.base64 = true, ecma376_agile encryption method decrypted successfully ', async () => {
6666
const workbook = new ExcelJS.Workbook();
@@ -73,7 +73,7 @@ describe('pr related issues', () => {
7373
});
7474
const sheetName = workbook.getWorksheet(1).name;
7575
expect(sheetName).to.equal('Sheet1');
76-
});
76+
}).timeout(10000);
7777

7878
it('workbook.xlsx.read, ecma376_standard encryption method decrypted successfully ', async () => {
7979
const workbook = new ExcelJS.Workbook();
@@ -83,7 +83,7 @@ describe('pr related issues', () => {
8383
});
8484
const sheetName = workbook.getWorksheet(1).name;
8585
expect(sheetName).to.equal('Sheet1');
86-
});
86+
}).timeout(10000);
8787

8888
it('workbook.xlsx.read, ecma376_agile encryption method decrypted successfully ', async () => {
8989
const workbook = new ExcelJS.Workbook();
@@ -93,6 +93,6 @@ describe('pr related issues', () => {
9393
});
9494
const sheetName = workbook.getWorksheet(1).name;
9595
expect(sheetName).to.equal('Sheet1');
96-
});
96+
}).timeout(10000);
9797
});
9898
});

spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,43 @@ const expectations = [
1616
tests: ['render', 'renderIn', 'parse'],
1717
},
1818
{
19-
title: 'Internal Link',
19+
title: 'Internal Link sheet1!B2',
2020
create() {
2121
return new HyperlinkXform();
2222
},
23-
preparedModel: {address: 'B6', rId: 'rId1', target: 'sheet1!B2'},
23+
preparedModel: {address: 'B6', target: '#sheet1!B2'},
2424
get parsedModel() {
2525
return this.preparedModel;
2626
},
27-
xml: '<hyperlink ref="B6" r:id="rId1" location="sheet1!B2"/>',
28-
tests: ['render', 'renderIn', 'parse'],
27+
xml: '<hyperlink ref="B6" location="sheet1!B2"/>',
28+
// tests: ['render', 'renderIn', 'parse'],
29+
tests: ['render', 'renderIn'],
30+
},
31+
{
32+
title: 'Internal Link B2:C4',
33+
create() {
34+
return new HyperlinkXform();
35+
},
36+
preparedModel: {address: 'B6', target: '#B2:C4'},
37+
get parsedModel() {
38+
return this.preparedModel;
39+
},
40+
xml: '<hyperlink ref="B6" location="B2:C4"/>',
41+
// tests: ['render', 'renderIn', 'parse'],
42+
tests: ['render', 'renderIn'],
43+
},
44+
{
45+
title: 'Internal Link sheet1!B2:C4',
46+
create() {
47+
return new HyperlinkXform();
48+
},
49+
preparedModel: {address: 'B6', target: '#sheet1!B2:C4'},
50+
get parsedModel() {
51+
return this.preparedModel;
52+
},
53+
xml: '<hyperlink ref="B6" location="sheet1!B2:C4"/>',
54+
// tests: ['render', 'renderIn', 'parse'],
55+
tests: ['render', 'renderIn'],
2956
},
3057
];
3158

spec/unit/xlsx/xform/test-xform-helper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const its = {
9999

100100
const xmlStream = new XmlStream();
101101
xform.render(xmlStream, model);
102-
// console.log(xmlStream.xml);
102+
// console.log(xmlStream.xml, result);
103103

104104
expect(xmlStream.xml).xml.to.equal(result);
105105
resolve();

0 commit comments

Comments
 (0)