Skip to content

Commit

Permalink
Merge pull request #5 from zurmokeeper/bugfix/internal_hyperlink_error
Browse files Browse the repository at this point in the history
Bugfix/internal hyperlink error
  • Loading branch information
zurmokeeper authored Jun 18, 2023
2 parents 677a1c5 + a896c80 commit c1e348c
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 34 deletions.
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)

Expand All @@ -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 <a href="https://github.com/ytjmt">ytjmt</a>, Merged <a href="https://github.com/exceljs/exceljs/pull/2281"> PR2281</a>.
* 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>.

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:
Expand Down Expand Up @@ -227,7 +254,7 @@ To be clear, all contributions added to this library will be included in the lib
# Importing[](#contents)<!-- Link generated with jump2header -->

```javascript
const ExcelJS = require('exceljs');
const ExcelJS = require('@zurmokeeper/exceljs');
```

## ES5 Imports[](#contents)<!-- Link generated with jump2header -->
Expand Down
31 changes: 29 additions & 2 deletions README_zh.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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 <a href="https://github.com/ytjmt">ytjmt</a>, Merged <a href="https://github.com/exceljs/exceljs/pull/2281"> PR2281</a>.
* 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>.

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 新的功能!

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

```javascript
const ExcelJS = require('exceljs');
const ExcelJS = require('@zurmokeeper/exceljs');
```

## ES5 导入[](#目录)<!-- Link generated with jump2header -->
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,7 @@ export interface WorksheetProtection {
sort: boolean;
autoFilter: boolean;
pivotTables: boolean;
spinCount: number;
}
export interface Image {
extension: 'jpeg' | 'png' | 'gif';
Expand Down Expand Up @@ -989,6 +990,7 @@ export interface WorksheetModel {
views: WorksheetView[];
autoFilter: AutoFilter;
media: Media[];
merges: Range['range'][];
}
export type WorksheetState = 'visible' | 'hidden' | 'veryHidden';

Expand Down
21 changes: 19 additions & 2 deletions lib/xlsx/xform/sheet/hyperlink-xform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down Expand Up @@ -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) === '#';
}
}

Expand Down
30 changes: 16 additions & 14 deletions lib/xlsx/xform/sheet/worksheet-xform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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']
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
16 changes: 8 additions & 8 deletions spec/integration/issues/issue-2247-cryptor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -93,6 +93,6 @@ describe('pr related issues', () => {
});
const sheetName = workbook.getWorksheet(1).name;
expect(sheetName).to.equal('Sheet1');
});
}).timeout(10000);
});
});
35 changes: 31 additions & 4 deletions spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<hyperlink ref="B6" r:id="rId1" location="sheet1!B2"/>',
tests: ['render', 'renderIn', 'parse'],
xml: '<hyperlink ref="B6" location="sheet1!B2"/>',
// 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: '<hyperlink ref="B6" location="B2:C4"/>',
// 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: '<hyperlink ref="B6" location="sheet1!B2:C4"/>',
// tests: ['render', 'renderIn', 'parse'],
tests: ['render', 'renderIn'],
},
];

Expand Down
2 changes: 1 addition & 1 deletion spec/unit/xlsx/xform/test-xform-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit c1e348c

Please sign in to comment.