diff --git a/README.md b/README.md
index bdb8fda..3000dcf 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# ExcelJS
+# @zurmokeeper/exceljs
[![Build status](https://github.com/exceljs/exceljs/workflows/ExcelJS/badge.svg)](https://github.com/exceljs/exceljs/actions?query=workflow%3AExcelJS)
@@ -16,6 +16,33 @@ Reverse engineered from Excel spreadsheet files as a project.
npm install @zurmokeeper/exceljs
```
+# V4.4.2 New Features!
+
+Change Log:
+
+* 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。
+* 2:Add type definition for WorksheetModel.merges, Thank you ytjmt, Merged PR2281.
+* 3:Add type definition for WorksheetProtection.spinCount,Thank you damingerdai, Merged PR2284.
+
+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::
+
+
+```
+const wb = new ExcelJS.Workbook();
+const ws1 = wb.addWorksheet('Sheet1');
+const ws2 = wb.addWorksheet('Sheet2');
+
+'#' 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
+// internal hyperlink
+ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#Sheet2!A1' };
+
+// internal hyperlink
+ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#Sheet2!A1:B1' };
+
+// internal hyperlink
+ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#A1:B1' };
+```
+
# V4.4.1 New Features!
Change Log:
@@ -227,7 +254,7 @@ To be clear, all contributions added to this library will be included in the lib
# Importing[⬆](#contents)
```javascript
-const ExcelJS = require('exceljs');
+const ExcelJS = require('@zurmokeeper/exceljs');
```
## ES5 Imports[⬆](#contents)
diff --git a/README_zh.md b/README_zh.md
index 83ce845..4854ecc 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -1,4 +1,4 @@
-# ExcelJS
+# @zurmokeeper/exceljs
[![Build status](https://github.com/exceljs/exceljs/workflows/ExcelJS/badge.svg)](https://github.com/exceljs/exceljs/actions?query=workflow%3AExcelJS)
[![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 @@
npm install @zurmokeeper/exceljs
```
+# V4.4.2 新的功能!
+
+变更日志:
+
+* 1: Fixbug: [Internal hyperlink does not work on wps office](https://github.com/zurmokeeper/excelize/issues/4). (Break change) 和支持新的内部链接方式。
+* 2:Add type definition for WorksheetModel.merges, Thank you ytjmt, Merged PR2281.
+* 3:Add type definition for WorksheetProtection.spinCount,Thank you damingerdai, Merged PR2284.
+
+PS: 自 V4.4.2 @zurmokeeper/exceljs 新增单元格插入内部链接支持 Sheet2!A1:B1 和 A1:B1等形式,原来只支持 Sheet2!A1,使用方式如下:
+
+
+```
+const wb = new ExcelJS.Workbook();
+const ws1 = wb.addWorksheet('Sheet1');
+const ws2 = wb.addWorksheet('Sheet2');
+
+'#'是必须的,@zurmokeeper/exceljs 是通过'#'来区分内部链接的,默认会被认为是非内部链接,旧版本使用也要手动加上 '#' ,如何没加的话
+// internal hyperlink
+ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#Sheet2!A1' };
+
+// internal hyperlink
+ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#Sheet2!A1:B1' };
+
+// internal hyperlink
+ws1.getCell('A1').value = { text: 'Sheet2', hyperlink: '#A1:B1' };
+```
+
# V4.4.1 新的功能!
变更日志:
@@ -188,7 +215,7 @@ npm install @zurmokeeper/exceljs
# 导入[⬆](#目录)
```javascript
-const ExcelJS = require('exceljs');
+const ExcelJS = require('@zurmokeeper/exceljs');
```
## ES5 导入[⬆](#目录)
diff --git a/index.d.ts b/index.d.ts
index 1303f77..be2012e 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -880,6 +880,7 @@ export interface WorksheetProtection {
sort: boolean;
autoFilter: boolean;
pivotTables: boolean;
+ spinCount: number;
}
export interface Image {
extension: 'jpeg' | 'png' | 'gif';
@@ -989,6 +990,7 @@ export interface WorksheetModel {
views: WorksheetView[];
autoFilter: AutoFilter;
media: Media[];
+ merges: Range['range'][];
}
export type WorksheetState = 'visible' | 'hidden' | 'veryHidden';
diff --git a/lib/xlsx/xform/sheet/hyperlink-xform.js b/lib/xlsx/xform/sheet/hyperlink-xform.js
index 88f4ee2..499ea2f 100644
--- a/lib/xlsx/xform/sheet/hyperlink-xform.js
+++ b/lib/xlsx/xform/sheet/hyperlink-xform.js
@@ -7,11 +7,14 @@ class HyperlinkXform extends BaseXform {
render(xmlStream, model) {
if (this.isInternalLink(model)) {
+ // Remove '#' example #sheet1!A1 -> sheet1!A1
+ model.target = model.target ? model.target.slice(1) : model.target;
xmlStream.leafNode('hyperlink', {
ref: model.address,
- 'r:id': model.rId,
+ // 'r:id': model.rId, // Internal hyperlink don't need 'r:id', it's enough to have location
tooltip: model.tooltip,
location: model.target,
+ display: model.tooltip, // TODO: For the time being, this is compatible with google sheet. https://www.google.cn/sheets/about/
});
} else {
xmlStream.leafNode('hyperlink', {
@@ -45,9 +48,23 @@ class HyperlinkXform extends BaseXform {
return false;
}
+ /**
+ * @desc example Sheet2!D3 Sheet2!D3:E3 D3:E3
+ * @returns
+ */
isInternalLink(model) {
// @example: Sheet2!D3, return true
- return model.target && /^[^!]+![a-zA-Z]+[\d]+$/.test(model.target);
+ // return model.target && /^[^!]+![a-zA-Z]+[\d]+$/.test(model.target);
+
+ // Using regular expressions is not enough to cover all cases like the one below,
+ // An example of the xlsx library, which is also generic
+ // https://docs.sheetjs.com/docs/csf/features/hyperlinks#internal-links
+ // ws["C1"].l = { Target: "#SheetJSDN", Tooltip: "Defined Name" };
+ // wb.Workbook = {
+ // Names: [{Name: "SheetJSDN", Ref:"Sheet2!A1:B2"}]
+ // }
+ // an example of the xlsx library, so instead pass '#' manually to determine if it is an internal hyperlink.
+ return model.target && model.target.slice(0, 1) === '#';
}
}
diff --git a/lib/xlsx/xform/sheet/worksheet-xform.js b/lib/xlsx/xform/sheet/worksheet-xform.js
index 4b36e37..ce861ff 100644
--- a/lib/xlsx/xform/sheet/worksheet-xform.js
+++ b/lib/xlsx/xform/sheet/worksheet-xform.js
@@ -152,15 +152,23 @@ class WorkSheetXform extends BaseXform {
return `rId${r.length + 1}`;
}
+ // TODO: The same as HyperlinkXform. isInternalLink
+ function isInternalLink(m) {
+ return m.target && m.target.slice(0, 1) === '#';
+ }
+
model.hyperlinks.forEach(hyperlink => {
const rId = nextRid(rels);
hyperlink.rId = rId;
- rels.push({
- Id: rId,
- Type: RelType.Hyperlink,
- Target: hyperlink.target,
- TargetMode: 'External',
- });
+ // Internal hyperlink do not need to generate worksheets/_rels/sheetx.xml.rels, but external hyperlink do.
+ if (!isInternalLink(hyperlink)) {
+ rels.push({
+ Id: rId,
+ Type: RelType.Hyperlink,
+ Target: hyperlink.target,
+ TargetMode: 'External',
+ });
+ }
});
// prepare comment relationships
@@ -221,9 +229,7 @@ class WorkSheetXform extends BaseXform {
});
}
let rIdImage =
- this.preImageId === medium.imageId
- ? drawingRelsHash[medium.imageId]
- : drawingRelsHash[drawing.rels.length];
+ this.preImageId === medium.imageId ? drawingRelsHash[medium.imageId] : drawingRelsHash[drawing.rels.length];
if (!rIdImage) {
rIdImage = nextRid(drawing.rels);
drawingRelsHash[drawing.rels.length] = rIdImage;
@@ -405,11 +411,7 @@ class WorkSheetXform extends BaseXform {
false,
margins: this.map.pageMargins.model,
};
- const pageSetup = Object.assign(
- sheetProperties,
- this.map.pageSetup.model,
- this.map.printOptions.model
- );
+ const pageSetup = Object.assign(sheetProperties, this.map.pageSetup.model, this.map.printOptions.model);
const conditionalFormattings = mergeConditionalFormattings(
this.map.conditionalFormatting.model,
this.map.extLst.model && this.map.extLst.model['x14:conditionalFormattings']
diff --git a/package.json b/package.json
index 13ae6b2..e6fc009 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,12 @@
{
"name": "@zurmokeeper/exceljs",
- "version": "4.4.1",
+ "version": "4.4.2",
"description": "Excel Workbook Manager - Read and Write xlsx and csv Files.",
"private": false,
"license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
"author": {
"name": "zurmokeeper",
"email": "3382272560@qq.com"
diff --git a/spec/integration/issues/issue-2247-cryptor.spec.js b/spec/integration/issues/issue-2247-cryptor.spec.js
index 7130d19..15ba7d6 100644
--- a/spec/integration/issues/issue-2247-cryptor.spec.js
+++ b/spec/integration/issues/issue-2247-cryptor.spec.js
@@ -15,7 +15,7 @@ describe('pr related issues', () => {
});
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
- });
+ }).timeout(10000);
it('workbook.xlsx.readFile, ecma376_agile encryption method decrypted successfully', async () => {
const workbook = new ExcelJS.Workbook();
@@ -24,7 +24,7 @@ describe('pr related issues', () => {
});
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
- });
+ }).timeout(10000);
it('workbook.xlsx.load, ecma376_standard encryption method decrypted successfully ', async () => {
const workbook = new ExcelJS.Workbook();
@@ -34,7 +34,7 @@ describe('pr related issues', () => {
});
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
- });
+ }).timeout(10000);
it('workbook.xlsx.load, ecma376_agile encryption method decrypted successfully ', async () => {
const workbook = new ExcelJS.Workbook();
@@ -47,7 +47,7 @@ describe('pr related issues', () => {
);
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
- });
+ }).timeout(10000);
it('workbook.xlsx.load, options.base64 = true, ecma376_standard encryption method decrypted successfully ', async () => {
const workbook = new ExcelJS.Workbook();
@@ -60,7 +60,7 @@ describe('pr related issues', () => {
});
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
- });
+ }).timeout(10000);
it('workbook.xlsx.load, options.base64 = true, ecma376_agile encryption method decrypted successfully ', async () => {
const workbook = new ExcelJS.Workbook();
@@ -73,7 +73,7 @@ describe('pr related issues', () => {
});
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
- });
+ }).timeout(10000);
it('workbook.xlsx.read, ecma376_standard encryption method decrypted successfully ', async () => {
const workbook = new ExcelJS.Workbook();
@@ -83,7 +83,7 @@ describe('pr related issues', () => {
});
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
- });
+ }).timeout(10000);
it('workbook.xlsx.read, ecma376_agile encryption method decrypted successfully ', async () => {
const workbook = new ExcelJS.Workbook();
@@ -93,6 +93,6 @@ describe('pr related issues', () => {
});
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
- });
+ }).timeout(10000);
});
});
diff --git a/spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js b/spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js
index 9e25bbc..d064b56 100644
--- a/spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js
+++ b/spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js
@@ -16,16 +16,43 @@ const expectations = [
tests: ['render', 'renderIn', 'parse'],
},
{
- title: 'Internal Link',
+ title: 'Internal Link sheet1!B2',
create() {
return new HyperlinkXform();
},
- preparedModel: {address: 'B6', rId: 'rId1', target: 'sheet1!B2'},
+ preparedModel: {address: 'B6', target: '#sheet1!B2'},
get parsedModel() {
return this.preparedModel;
},
- xml: '',
- tests: ['render', 'renderIn', 'parse'],
+ xml: '',
+ // tests: ['render', 'renderIn', 'parse'],
+ tests: ['render', 'renderIn'],
+ },
+ {
+ title: 'Internal Link B2:C4',
+ create() {
+ return new HyperlinkXform();
+ },
+ preparedModel: {address: 'B6', target: '#B2:C4'},
+ get parsedModel() {
+ return this.preparedModel;
+ },
+ xml: '',
+ // tests: ['render', 'renderIn', 'parse'],
+ tests: ['render', 'renderIn'],
+ },
+ {
+ title: 'Internal Link sheet1!B2:C4',
+ create() {
+ return new HyperlinkXform();
+ },
+ preparedModel: {address: 'B6', target: '#sheet1!B2:C4'},
+ get parsedModel() {
+ return this.preparedModel;
+ },
+ xml: '',
+ // tests: ['render', 'renderIn', 'parse'],
+ tests: ['render', 'renderIn'],
},
];
diff --git a/spec/unit/xlsx/xform/test-xform-helper.js b/spec/unit/xlsx/xform/test-xform-helper.js
index 8cadea8..a74f5f3 100644
--- a/spec/unit/xlsx/xform/test-xform-helper.js
+++ b/spec/unit/xlsx/xform/test-xform-helper.js
@@ -99,7 +99,7 @@ const its = {
const xmlStream = new XmlStream();
xform.render(xmlStream, model);
- // console.log(xmlStream.xml);
+ // console.log(xmlStream.xml, result);
expect(xmlStream.xml).xml.to.equal(result);
resolve();