diff --git a/.gitignore b/.gitignore index 543ecab..5b048cd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,4 @@ /.idea /coverage/ /.nyc_output/ -/.idea/ - - -/src/example.ts -*.xlsx \ No newline at end of file +/.idea/ \ No newline at end of file diff --git a/README.md b/README.md index d263d30..c45a579 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ # WIP current version of lib is under developing +# Introduction + +This library makes generating xlsx files (Excel) easly. + +It consumes template which is common Excel file, then add yours data (called ViewModel). Blend it and done, as result you'll get pretty Excel. + # Getting Started: 1. install package @@ -11,50 +17,58 @@ npm i xlsx-renderer --save ``` -`npm install` +2. TODO add some example here (https://github.com/Siemienik/xlsx-renderer/issues/6) -## In Cell Commands: - -1. `## varToDisplayInThisCell` -2. `#! FINISH` - finish processing current worksheet -2. `#! FINISH booleanVar` - if true it finishes processing current template worksheet, if false it add again this worksheet to output -3. `#! END_ROW` -4. `#! DELETE varName` -5. `#! HYPERLINK labelVar urlVar` -6. `#! WS_NAME nameVar` set worksheet name -7. `#! FOR_EACH item collection` (to write item property `## item.property`), -8. `#! CONTINUE item` item is set to the next collection item. -9. `#! END_LOOP item` -10. `#! AVERAGE item` write average formula of all items from previous for-each, it has to be placed after the for-each was finished. -11. `#! SUM item` similar to average -12. `#! DUMP_COLS arrayVar` write to next columns all array items (1 item = 1 column) - - -## Sample code: (possible to be outdated yet - todo) +## Sample code: ```javascript import Renderer from './xls-renderer/Renderer' -import {Workbook} from 'exceljs' - -//* -import DebugCellTemplatePool from "./xls-renderer-debug/CellTemplateDebugPool"; -const renderer = new Renderer(new DebugCellTemplatePool()); -/*/ -import CellTemplatePool from "./xls-renderer/CellTemplatePool"; -const renderer = new Renderer(new CellTemplatePool()); -//*/ - const viewModel = new MyAwesomeReportVm(); //or something else (async () => { - const result = await renderer.render(async () => { - const template = new Workbook(); - return await template.xlsx.readFile('./my-awesome-raport-template.xlsx'); - }, viewModel); + const result = await renderer.renderFromFile('./my-awesome-raport-template.xlsx', viewModel); await result.xlsx.writeFile('./my-awesome-raport.xlsx'); })(); ``` + +# Documentation: + +## Cells: + +| Category | Name | Order | match rule | Description | More info | +|----------|-----:|-------|--------|-------------|:---------| +| - | [BaseCell](./src/cell/BaseCell.ts) | n/o | n/o | All Cell\`s definition classes extend it. | **abstract** | +| Content | [NormalCell](./src/cell/NormalCell.ts) | 1 | not started by `##` or `#!` | This one copy all styles, width, properties and value form template. | **default** | +| Content | [VariableCell](./src/cell/VariableCell.ts) | 3 | `## pathToVariable ` | Write variable from `ViewModel`.
Paths to object's property or array item are allowed. | **Examples:**
`simplePath`
`someObject.property`
`array.0.field`
`items.1.path.to.object.prop`| +| Content | **TODO: describe it!** [HyperlinkCell](./src/cell/HyperlinkCell.ts) | | | | | +| Content | **TODO: describe it!** [FormulaCell](./src/cell/FormulaCell.ts) | | | | | +| Navigation | **TODO: describe it!** [EndRowCell](./src/cell/EndRowCell.ts) | | | | | +| Worksheet
Navigation
Loop | [FinishCell](./src/cell/FinishCell.ts) | 7 | `#! FINISH conditionPath` | Finish rendering for current worksheet and:
1) go to next worksheet if `conditionPath===true`
2) repeat this template worksheet again (`conditionPath === false`) - looping through worksheets
3) finished whole rendering when this worksheet is the last one. | **Examples:**
`#! FINISHED ` or `#! FINISHED itemFromLoop.__iterated` | +| Worksheet | **TODO: describe it!** [WsNameCell](./src/cell/WsNameCell.ts) | | | | | +| View Model | **TODO: describe it!** [DeleteCell](./src/cell/DeleteCell.ts) | | | | | +| Loop | **TODO: describe it!** [DumpColsCell](./src/cell/DumpColsCell.ts) | | | | | +| Loop | **TODO: describe it!** [ForEachCell](./src/cell/ForEachCell.ts) | | | | | +| Loop | **TODO: describe it!** [ContinueCell](./src/cell/ContinueCell.ts) | | | | | +| Loop | **TODO: describe it!** [EndLoopCell](./src/cell/EndLoopCell.ts) | | | | | +| Aggregation | **TODO: describe it!** [AverageCell](./src/cell/AverageCell.ts) | | | | | +| Aggregation| **TODO: describe it!** [SumCell](./src/cell/SumCell.ts) | | | | | + + +## Commands [PREVIOUS VERSION]: + +1. `#! END_ROW` +4. `#! DELETE varName` +5. `#! HYPERLINK labelVar urlVar` +6. `#! WS_NAME nameVar` set worksheet name +7. `#! FOR_EACH item collection` (to write item property `## item.property`), +8. `#! CONTINUE item` item is set to the next collection item. +9. `#! END_LOOP item` +10. `#! AVERAGE item` write average formula of all items from previous for-each, it has to be placed after the for-each was finished. +11. `#! SUM item` similar to average +12. `#! DUMP_COLS arrayVar` write to next columns all array items (1 item = 1 column) + + [LICENSE](LICENSE) diff --git a/package.json b/package.json index e34fe8f..0132259 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "postversion": "git push && git push --tags" }, "devDependencies": { + "@types/node": "^12.12.6", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.6", "chai": "^4.2.0", diff --git a/src/Renderer.ts b/src/Renderer.ts index fa36b30..203a56f 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -19,4 +19,13 @@ export class Renderer { return output; } + + public async renderFromFile(templatePath: string, viewModel: any): Promise { + const result = await this.render(async () => { + const template = new Workbook(); + return await template.xlsx.readFile(templatePath); + }, viewModel); + + return await result; + } } diff --git a/src/cell/BaseCell.ts b/src/cell/BaseCell.ts index 93d1c9b..082c023 100644 --- a/src/cell/BaseCell.ts +++ b/src/cell/BaseCell.ts @@ -4,7 +4,7 @@ import { Scope } from '../Scope'; export declare type CellType = typeof BaseCell; -export class BaseCell { +export /* abstract */ class BaseCell { // can't be abstract :( /** * check if this commend can parse `value` */ @@ -12,7 +12,7 @@ export class BaseCell { return false; } - public BaseCell() { + public BaseCell() { // can't be marked by abstract keyword, so it throw type error. throw new TypeError(`Cannot construct ${BaseCell.name} instances directly. It's abstract.`); } diff --git a/src/example.ts b/src/example.ts deleted file mode 100644 index 64e9431..0000000 --- a/src/example.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Renderer } from './Renderer'; -import { CellTemplateDebugPool } from './CellTemplateDebugPool'; -import { CellTemplatePool } from './CellTemplatePool'; -import { Workbook } from 'exceljs'; - -// tslint:disable:comment-format -//* -const debug = true; -/*/ -const debug = false; -//*/ - -let renderer; -if (debug) { - renderer = new Renderer(new CellTemplateDebugPool()); -} else { - renderer = new Renderer(new CellTemplatePool()); -} - -(async () => { - const result = await renderer.render(async () => { - const template = new Workbook(); - return await template.xlsx.readFile('./my-awesome-raport-template.xlsx'); - }, {}); - - await result.xlsx.writeFile('./my-awesome-raport.xlsx'); -})(); diff --git a/tests/integration/Renderer.ts b/tests/integration/Renderer.ts new file mode 100644 index 0000000..c47a12c --- /dev/null +++ b/tests/integration/Renderer.ts @@ -0,0 +1,92 @@ +import {Renderer} from "../../src/Renderer"; +import * as fs from "fs"; +import * as path from "path"; +import {Workbook} from "exceljs"; +import * as chai from 'chai' + + +function assertCells(expected: Workbook, result: Workbook, factor: number = 10) { + chai.expect(expected.worksheets.length).eql(result.worksheets.length); + chai.expect(expected.worksheets.map(x => x.name)).eql(result.worksheets.map(x => x.name)); + + for (let wi = 0; wi < expected.worksheets.length; wi++) { + const ws = {e: expected.worksheets[wi], r: result.worksheets[wi]}; + for (let i = 0; i < factor * factor; i++) { + const r = Math.floor(i / factor) + 1; + const c = i % factor + 1; + const cell = { + e: ws.e.getCell(r, c), + r: ws.r.getCell(r, c) + }; + + + if (r === 1) { + chai.expect(ws.e.getColumn(c).width).eql(ws.r.getColumn(c).width); + } + if (c === 1) { + chai.expect(ws.e.getRow(r).height).eql(ws.r.getRow(r).height); + } + + chai.expect(cell.e.style).eql(cell.r.style); + chai.expect(cell.e.text).eql(cell.r.text); + chai.expect(cell.e.value).eql(cell.r.value); + } + } +} + +describe('INTEGRATION:: Test xlsx renderer ', function () { + + describe('Checking if assertCells works ok.', function () { + it('Same - should pass ok', async function () { + const expected = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'main.xlsx')); + const correct = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'correct.xlsx')); + + assertCells(expected, correct, 20); + }); + + it('Different - attempt to broke assertions', async function () { + const expected = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'main.xlsx')); + const failedWorksheetAmount = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'f-ws-amount.xlsx')); + const failedWorksheetNames = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'f-ws-names.xlsx')); + const failedWidth = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'f-width.xlsx')); + const failedHeight = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'f-height.xlsx')); + const failedStyle = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'f-style.xlsx')); + const failedText = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'f-text.xlsx')); + const failedValue = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'f-value.xlsx')); + const failedTable = await new Workbook().xlsx.readFile(path.join(__dirname, 'data', 'assertCells', 'f-table.xlsx')); + + chai.expect(() => assertCells(expected, failedWorksheetAmount, 20)).throw("expected 2 to deeply equal 3"); + chai.expect(() => assertCells(expected, failedWorksheetNames, 20)).throw('expected [ \'Sheet1\', \'Sheet2\' ] to deeply equal [ \'Sheet1\', \'Sheet3\' ]'); + chai.expect(() => assertCells(expected, failedWidth, 20)).throw("expected 13 to deeply equal 7.90625"); + chai.expect(() => assertCells(expected, failedHeight, 20)).throw("expected 15 to deeply equal 34.5"); + chai.expect(() => assertCells(expected, failedStyle, 20)).throw("expected { Object (font, border, ...) } to deeply equal { Object (font, border, ...) }"); + chai.expect(() => assertCells(expected, failedText, 20)).throw('expected \'sadasd\' to deeply equal \'sadas\''); + chai.expect(() => assertCells(expected, failedValue, 20)).throw('expected { Object (formula, result) } to deeply equal \'asdasda\''); + chai.expect(() => assertCells(expected, failedTable, 20)).throw('expected { Object (font, border, ...) } to deeply equal { Object (font, border, ...) }'); + }); + }); + describe('Load examples, render and compare with expected result', function () { + const dataPath = path.normalize(path.join(__dirname, 'data/')); + const sets = fs.readdirSync(path.normalize(dataPath), {withFileTypes: true}) + .filter(i => i.isDirectory()) + .filter(d => /^Renderer[0-9]*-/.test(d.name)); + + + const renderer = new Renderer(); + sets.forEach(s => { + it(`Test for ${s.name}`, async function () { + const result = await renderer.renderFromFile( + path.join(dataPath, s.name, "template.xlsx"), + require(path.join(dataPath, s.name, 'viewModel.json')) + ); + + const expected = await new Workbook().xlsx.readFile(path.join(dataPath, s.name, "expected.xlsx")); + + assertCells(expected, result); + }); + + + }) + + }); +}); \ No newline at end of file diff --git a/tests/integration/data/Renderer000-FinishCell/expected.xlsx b/tests/integration/data/Renderer000-FinishCell/expected.xlsx new file mode 100644 index 0000000..b4eb627 Binary files /dev/null and b/tests/integration/data/Renderer000-FinishCell/expected.xlsx differ diff --git a/tests/integration/data/Renderer000-FinishCell/template.xlsx b/tests/integration/data/Renderer000-FinishCell/template.xlsx new file mode 100644 index 0000000..decb5f3 Binary files /dev/null and b/tests/integration/data/Renderer000-FinishCell/template.xlsx differ diff --git a/tests/integration/data/Renderer000-FinishCell/viewModel.json b/tests/integration/data/Renderer000-FinishCell/viewModel.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/tests/integration/data/Renderer000-FinishCell/viewModel.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/integration/data/assertCells/correct.xlsx b/tests/integration/data/assertCells/correct.xlsx new file mode 100644 index 0000000..aaf5eff Binary files /dev/null and b/tests/integration/data/assertCells/correct.xlsx differ diff --git a/tests/integration/data/assertCells/f-height.xlsx b/tests/integration/data/assertCells/f-height.xlsx new file mode 100644 index 0000000..25e9730 Binary files /dev/null and b/tests/integration/data/assertCells/f-height.xlsx differ diff --git a/tests/integration/data/assertCells/f-style.xlsx b/tests/integration/data/assertCells/f-style.xlsx new file mode 100644 index 0000000..87a096e Binary files /dev/null and b/tests/integration/data/assertCells/f-style.xlsx differ diff --git a/tests/integration/data/assertCells/f-table.xlsx b/tests/integration/data/assertCells/f-table.xlsx new file mode 100644 index 0000000..2796514 Binary files /dev/null and b/tests/integration/data/assertCells/f-table.xlsx differ diff --git a/tests/integration/data/assertCells/f-text.xlsx b/tests/integration/data/assertCells/f-text.xlsx new file mode 100644 index 0000000..c57cb10 Binary files /dev/null and b/tests/integration/data/assertCells/f-text.xlsx differ diff --git a/tests/integration/data/assertCells/f-value.xlsx b/tests/integration/data/assertCells/f-value.xlsx new file mode 100644 index 0000000..60bf56e Binary files /dev/null and b/tests/integration/data/assertCells/f-value.xlsx differ diff --git a/tests/integration/data/assertCells/f-width.xlsx b/tests/integration/data/assertCells/f-width.xlsx new file mode 100644 index 0000000..0d7bf6e Binary files /dev/null and b/tests/integration/data/assertCells/f-width.xlsx differ diff --git a/tests/integration/data/assertCells/f-ws-amount.xlsx b/tests/integration/data/assertCells/f-ws-amount.xlsx new file mode 100644 index 0000000..a9b218c Binary files /dev/null and b/tests/integration/data/assertCells/f-ws-amount.xlsx differ diff --git a/tests/integration/data/assertCells/f-ws-names.xlsx b/tests/integration/data/assertCells/f-ws-names.xlsx new file mode 100644 index 0000000..9d41240 Binary files /dev/null and b/tests/integration/data/assertCells/f-ws-names.xlsx differ diff --git a/tests/integration/data/assertCells/main.xlsx b/tests/integration/data/assertCells/main.xlsx new file mode 100644 index 0000000..d4ce6eb Binary files /dev/null and b/tests/integration/data/assertCells/main.xlsx differ