From 045adba80d8d367d1b6e676e6500aea10c3db624 Mon Sep 17 00:00:00 2001 From: SheetJS <dev@sheetjs.com> Date: Sun, 21 Aug 2022 20:51:51 -0400 Subject: [PATCH] parse ZIP64 length (fixes #2766 h/t @silvialeung) --- bits/18_cfb.js | 24 +- demos/README.md | 19 +- demos/angular2/.angular-cli.json | 23 -- demos/angular2/.eslintrc | 6 - demos/angular2/.gitattributes | 1 - demos/angular2/.gitignore | 8 - demos/angular2/Makefile | 34 --- demos/angular2/README.md | 147 +---------- demos/angular2/ionic-app.module.ts | 22 -- demos/angular2/ionic.sh | 16 -- demos/angular2/ionic.ts | 126 --------- demos/angular2/package.json | 39 --- demos/angular2/screen.png | Bin 95983 -> 0 bytes demos/angular2/src/app/app.module.ts | 28 -- demos/angular2/src/app/sheetjs.component.ts | 64 ----- .../src/environments/environment.prod.ts | 3 - .../angular2/src/environments/environment.ts | 3 - demos/angular2/src/index.html | 30 --- demos/angular2/src/main.ts | 3 - demos/angular2/src/styles.css | 1 - demos/angular2/src/tsconfig.app.json | 9 - demos/angular2/tsconfig.json | 20 -- demos/angular2/versions/angular.json-ng10 | 125 --------- demos/angular2/versions/angular.json-ng11 | 124 --------- demos/angular2/versions/angular.json-ng12 | 106 -------- demos/angular2/versions/angular.json-ng13 | 106 -------- demos/angular2/versions/angular.json-ng6 | 127 --------- demos/angular2/versions/angular.json-ng7 | 136 ---------- demos/angular2/versions/angular.json-ng8 | 126 --------- demos/angular2/versions/angular.json-ng9 | 125 --------- demos/angular2/versions/package.json-ng10 | 39 --- demos/angular2/versions/package.json-ng11 | 39 --- demos/angular2/versions/package.json-ng12 | 39 --- demos/angular2/versions/package.json-ng13 | 39 --- demos/angular2/versions/package.json-ng2 | 37 --- demos/angular2/versions/package.json-ng4 | 38 --- demos/angular2/versions/package.json-ng5 | 38 --- demos/angular2/versions/package.json-ng6 | 39 --- demos/angular2/versions/package.json-ng7 | 39 --- demos/angular2/versions/package.json-ng8 | 39 --- demos/angular2/versions/package.json-ng9 | 39 --- demos/angular2/versions/polyfills.ts-ng10 | 1 - demos/angular2/versions/polyfills.ts-ng11 | 1 - demos/angular2/versions/polyfills.ts-ng12 | 1 - demos/angular2/versions/polyfills.ts-ng13 | 1 - demos/angular2/versions/polyfills.ts-ng2 | 3 - demos/angular2/versions/polyfills.ts-ng4 | 3 - demos/angular2/versions/polyfills.ts-ng5 | 3 - demos/angular2/versions/polyfills.ts-ng6 | 3 - demos/angular2/versions/polyfills.ts-ng7 | 3 - demos/angular2/versions/polyfills.ts-ng8 | 1 - demos/angular2/versions/polyfills.ts-ng9 | 1 - .../angular2/versions/tsconfig.app.json-ng10 | 14 - .../angular2/versions/tsconfig.app.json-ng11 | 14 - .../angular2/versions/tsconfig.app.json-ng12 | 14 - .../angular2/versions/tsconfig.app.json-ng13 | 14 - demos/angular2/versions/tsconfig.app.json-ng8 | 18 -- demos/angular2/versions/tsconfig.app.json-ng9 | 14 - demos/function/.eslintrc | 7 - demos/function/AzureHTTPTrigger/function.json | 17 -- demos/function/AzureHTTPTrigger/index.js | 44 ---- demos/function/Firebase/.gitignore | 65 ----- demos/function/Firebase/firebase.json | 1 - demos/function/Firebase/functions/.gitignore | 1 - demos/function/Firebase/functions/index.js | 39 --- .../function/Firebase/functions/package.json | 24 -- demos/function/LambdaProxy/index.js | 40 --- demos/function/LambdaProxy/template.yaml | 18 -- demos/function/Makefile | 28 -- demos/function/README.md | 112 +------- demos/function/host.json | 3 - demos/function/local.settings.json | 11 - demos/react/.gitignore | 4 - demos/react/Makefile | 24 -- demos/react/NOTES.md | 22 -- demos/react/README.md | 154 +---------- demos/react/index.html | 29 --- demos/react/modify/.gitignore | 23 -- demos/react/modify/README.md | 10 - demos/react/modify/package.json | 36 --- demos/react/modify/public/index.html | 43 ---- demos/react/modify/src/components/App.tsx | 107 -------- demos/react/modify/src/index.tsx | 12 - demos/react/modify/src/react-app-env.d.ts | 1 - demos/react/modify/src/styles/App.css | 14 - demos/react/modify/src/styles/index.css | 8 - demos/react/modify/tsconfig.json | 26 -- demos/react/nexthdr.js | 3 - demos/react/pages/getServerSideProps.js | 32 --- demos/react/pages/getStaticPaths.js | 38 --- demos/react/pages/getStaticProps.js | 32 --- demos/react/pages/index.js | 24 -- demos/react/pages/sheets/[id].js | 51 ---- demos/react/public/sheetjs.xlsx | Bin 10328 -> 0 bytes demos/react/sheetjs.js | 144 ----------- demos/server/.gitignore | 2 - demos/server/Makefile | 29 --- demos/server/README.md | 207 +-------------- demos/server/_cors.js | 4 - demos/server/_logit.js | 7 - demos/server/_request.js | 9 - demos/server/drash.ts | 68 ----- demos/server/express.js | 65 ----- demos/server/hapi.js | 67 ----- demos/server/koa.js | 79 ------ demos/server/koasub.js | 39 --- demos/server/nest.sh | 24 -- demos/server/nodejs.js | 84 ------ demos/server/sheetjs.controller.ts | 19 -- demos/server/sheetjs.csv | 19 -- demos/server/sheetjs.module.ts | 13 - demos/server/worker.js | 22 -- demos/vue/Makefile | 3 - demos/vue/README.md | 83 +----- demos/vue/SheetJS-vue.js | 68 ----- demos/vue/index2.html | 63 ----- demos/vue/index3.html | 63 ----- demos/vue/modify/.gitignore | 24 -- demos/vue/modify/README.md | 10 - demos/vue/modify/index.html | 13 - demos/vue/modify/package.json | 21 -- demos/vue/modify/src/App.vue | 241 ------------------ demos/vue/modify/src/env.d.ts | 8 - demos/vue/modify/src/main.ts | 4 - demos/vue/modify/tsconfig.json | 16 -- demos/vue/modify/tsconfig.node.json | 8 - demos/vue/modify/vite.config.ts | 7 - demos/vue/shim.js | 1 - demos/vue/xlsx.full.min.js | 1 - demos/xspreadsheet/README.md | 60 +---- demos/xspreadsheet/index.html | 135 ---------- demos/xspreadsheet/shim.js | 1 - demos/xspreadsheet/xlsx.full.min.js | 1 - demos/xspreadsheet/xlsxspread.js | 2 + 134 files changed, 65 insertions(+), 4907 deletions(-) delete mode 100644 demos/angular2/.angular-cli.json delete mode 100644 demos/angular2/.eslintrc delete mode 100644 demos/angular2/.gitattributes delete mode 100644 demos/angular2/.gitignore delete mode 100644 demos/angular2/Makefile delete mode 100644 demos/angular2/ionic-app.module.ts delete mode 100755 demos/angular2/ionic.sh delete mode 100644 demos/angular2/ionic.ts delete mode 100644 demos/angular2/package.json delete mode 100644 demos/angular2/screen.png delete mode 100644 demos/angular2/src/app/app.module.ts delete mode 100644 demos/angular2/src/app/sheetjs.component.ts delete mode 100644 demos/angular2/src/environments/environment.prod.ts delete mode 100644 demos/angular2/src/environments/environment.ts delete mode 100644 demos/angular2/src/index.html delete mode 100644 demos/angular2/src/main.ts delete mode 100644 demos/angular2/src/styles.css delete mode 100644 demos/angular2/src/tsconfig.app.json delete mode 100644 demos/angular2/tsconfig.json delete mode 100644 demos/angular2/versions/angular.json-ng10 delete mode 100644 demos/angular2/versions/angular.json-ng11 delete mode 100644 demos/angular2/versions/angular.json-ng12 delete mode 100644 demos/angular2/versions/angular.json-ng13 delete mode 100644 demos/angular2/versions/angular.json-ng6 delete mode 100644 demos/angular2/versions/angular.json-ng7 delete mode 100644 demos/angular2/versions/angular.json-ng8 delete mode 100644 demos/angular2/versions/angular.json-ng9 delete mode 100644 demos/angular2/versions/package.json-ng10 delete mode 100644 demos/angular2/versions/package.json-ng11 delete mode 100644 demos/angular2/versions/package.json-ng12 delete mode 100644 demos/angular2/versions/package.json-ng13 delete mode 100644 demos/angular2/versions/package.json-ng2 delete mode 100644 demos/angular2/versions/package.json-ng4 delete mode 100644 demos/angular2/versions/package.json-ng5 delete mode 100644 demos/angular2/versions/package.json-ng6 delete mode 100644 demos/angular2/versions/package.json-ng7 delete mode 100644 demos/angular2/versions/package.json-ng8 delete mode 100644 demos/angular2/versions/package.json-ng9 delete mode 100644 demos/angular2/versions/polyfills.ts-ng10 delete mode 100644 demos/angular2/versions/polyfills.ts-ng11 delete mode 100644 demos/angular2/versions/polyfills.ts-ng12 delete mode 100644 demos/angular2/versions/polyfills.ts-ng13 delete mode 100644 demos/angular2/versions/polyfills.ts-ng2 delete mode 100644 demos/angular2/versions/polyfills.ts-ng4 delete mode 100644 demos/angular2/versions/polyfills.ts-ng5 delete mode 100644 demos/angular2/versions/polyfills.ts-ng6 delete mode 100644 demos/angular2/versions/polyfills.ts-ng7 delete mode 100644 demos/angular2/versions/polyfills.ts-ng8 delete mode 100644 demos/angular2/versions/polyfills.ts-ng9 delete mode 100644 demos/angular2/versions/tsconfig.app.json-ng10 delete mode 100644 demos/angular2/versions/tsconfig.app.json-ng11 delete mode 100644 demos/angular2/versions/tsconfig.app.json-ng12 delete mode 100644 demos/angular2/versions/tsconfig.app.json-ng13 delete mode 100644 demos/angular2/versions/tsconfig.app.json-ng8 delete mode 100644 demos/angular2/versions/tsconfig.app.json-ng9 delete mode 100644 demos/function/.eslintrc delete mode 100644 demos/function/AzureHTTPTrigger/function.json delete mode 100644 demos/function/AzureHTTPTrigger/index.js delete mode 100644 demos/function/Firebase/.gitignore delete mode 100644 demos/function/Firebase/firebase.json delete mode 100644 demos/function/Firebase/functions/.gitignore delete mode 100644 demos/function/Firebase/functions/index.js delete mode 100644 demos/function/Firebase/functions/package.json delete mode 100644 demos/function/LambdaProxy/index.js delete mode 100644 demos/function/LambdaProxy/template.yaml delete mode 100644 demos/function/Makefile delete mode 100644 demos/function/host.json delete mode 100644 demos/function/local.settings.json delete mode 100644 demos/react/.gitignore delete mode 100644 demos/react/Makefile delete mode 100644 demos/react/NOTES.md delete mode 100644 demos/react/index.html delete mode 100644 demos/react/modify/.gitignore delete mode 100644 demos/react/modify/README.md delete mode 100644 demos/react/modify/package.json delete mode 100644 demos/react/modify/public/index.html delete mode 100644 demos/react/modify/src/components/App.tsx delete mode 100644 demos/react/modify/src/index.tsx delete mode 100644 demos/react/modify/src/react-app-env.d.ts delete mode 100644 demos/react/modify/src/styles/App.css delete mode 100644 demos/react/modify/src/styles/index.css delete mode 100644 demos/react/modify/tsconfig.json delete mode 100644 demos/react/nexthdr.js delete mode 100644 demos/react/pages/getServerSideProps.js delete mode 100644 demos/react/pages/getStaticPaths.js delete mode 100644 demos/react/pages/getStaticProps.js delete mode 100644 demos/react/pages/index.js delete mode 100644 demos/react/pages/sheets/[id].js delete mode 100644 demos/react/public/sheetjs.xlsx delete mode 100644 demos/react/sheetjs.js delete mode 100644 demos/server/.gitignore delete mode 100644 demos/server/Makefile delete mode 100644 demos/server/_cors.js delete mode 100644 demos/server/_logit.js delete mode 100644 demos/server/_request.js delete mode 100644 demos/server/drash.ts delete mode 100644 demos/server/express.js delete mode 100644 demos/server/hapi.js delete mode 100644 demos/server/koa.js delete mode 100644 demos/server/koasub.js delete mode 100755 demos/server/nest.sh delete mode 100644 demos/server/nodejs.js delete mode 100644 demos/server/sheetjs.controller.ts delete mode 100644 demos/server/sheetjs.csv delete mode 100644 demos/server/sheetjs.module.ts delete mode 100644 demos/server/worker.js delete mode 100644 demos/vue/Makefile delete mode 100644 demos/vue/SheetJS-vue.js delete mode 100644 demos/vue/index2.html delete mode 100644 demos/vue/index3.html delete mode 100644 demos/vue/modify/.gitignore delete mode 100644 demos/vue/modify/README.md delete mode 100644 demos/vue/modify/index.html delete mode 100644 demos/vue/modify/package.json delete mode 100644 demos/vue/modify/src/App.vue delete mode 100644 demos/vue/modify/src/env.d.ts delete mode 100644 demos/vue/modify/src/main.ts delete mode 100644 demos/vue/modify/tsconfig.json delete mode 100644 demos/vue/modify/tsconfig.node.json delete mode 100644 demos/vue/modify/vite.config.ts delete mode 120000 demos/vue/shim.js delete mode 120000 demos/vue/xlsx.full.min.js delete mode 100644 demos/xspreadsheet/index.html delete mode 120000 demos/xspreadsheet/shim.js delete mode 120000 demos/xspreadsheet/xlsx.full.min.js diff --git a/bits/18_cfb.js b/bits/18_cfb.js index 58dbe25..8ac3c6e 100644 --- a/bits/18_cfb.js +++ b/bits/18_cfb.js @@ -211,8 +211,15 @@ function parse_extra_field(blob/*:CFBlob*/)/*:any*/ { if(flags & 4) p.ctime = blob.read_shift(4); } if(p.mtime) p.mt = new Date(p.mtime*1000); - } - break; + } break; + /* ZIP64 Extended Information Field */ + case 0x0001: { + var sz1 = blob.read_shift(4), sz2 = blob.read_shift(4); + p.usz = (sz2 * Math.pow(2,32) + sz1); + sz1 = blob.read_shift(4); sz2 = blob.read_shift(4); + p.csz = (sz2 * Math.pow(2,32) + sz1); + // NOTE: volume fields are skipped + } break; } blob.l = tgt; o[type] = p; @@ -1401,6 +1408,11 @@ function parse_zip(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ var L = blob.l; blob.l = offset + 4; + /* ZIP64 lengths */ + if(EF && EF[0x0001]) { + if((EF[0x0001]||{}).usz) usz = EF[0x0001].usz; + if((EF[0x0001]||{}).csz) csz = EF[0x0001].csz; + } parse_local_file(blob, csz, usz, o, EF); blob.l = L; } @@ -1430,7 +1442,13 @@ function parse_local_file(blob/*:CFBlob*/, csz/*:number*/, usz/*:number*/, o/*:C if(efsz) { var ef = parse_extra_field(/*::(*/blob.slice(blob.l, blob.l + efsz)/*:: :any)*/); if((ef[0x5455]||{}).mt) date = ef[0x5455].mt; - if(((EF||{})[0x5455]||{}).mt) date = EF[0x5455].mt; + if((ef[0x0001]||{}).usz) _usz = ef[0x0001].usz; + if((ef[0x0001]||{}).csz) _csz = ef[0x0001].csz; + if(EF) { + if((EF[0x5455]||{}).mt) date = EF[0x5455].mt; + if((EF[0x0001]||{}).usz) _usz = ef[0x0001].usz; + if((EF[0x0001]||{}).csz) _csz = ef[0x0001].csz; + } } blob.l += efsz; diff --git a/demos/README.md b/demos/README.md index 98db244..e7df176 100644 --- a/demos/README.md +++ b/demos/README.md @@ -26,23 +26,23 @@ can be installed with Bash on Windows or with `cygwin`. - [`IndexedDB`](https://docs.sheetjs.com/docs/demos/database#indexeddb) **Frameworks** +- [`Angular 2+ and Ionic`](https://docs.sheetjs.com/docs/demos/angular) +- [`React`](https://docs.sheetjs.com/docs/demos/react) +- [`VueJS`](https://docs.sheetjs.com/docs/demos/vue) - [`Angular.JS`](https://docs.sheetjs.com/docs/demos/legacy#angularjs) -- [`Angular 2+ and Ionic`](angular2/) - [`Knockout`](https://docs.sheetjs.com/docs/demos/legacy#knockoutjs) -- [`React and NextJS`](react/) -- [`VueJS`](vue/) **Front-End UI Components** - [`canvas-datagrid`](https://docs.sheetjs.com/docs/demos/grid#canvas-datagrid) -- [`x-spreadsheet`](xspreadsheet/) -- [`react-data-grid`](react/modify/) -- [`vue3-table-light`](vue/modify/) +- [`x-spreadsheet`](https://docs.sheetjs.com/docs/demos/grid#x-spreadsheet) +- [`react-data-grid`](https://docs.sheetjs.com/docs/demos/grid#react-data-grid) +- [`vue3-table-lite`](https://docs.sheetjs.com/docs/demos/grid#vue3-table-lite) - [`angular-ui-grid`](https://docs.sheetjs.com/docs/demos/grid#angular-ui-grid) **Platforms and Integrations** - [`Command-Line Tools`](https://docs.sheetjs.com/docs/demos/cli) - [`iOS and Android Mobile Applications`](https://docs.sheetjs.com/docs/demos/mobile) -- [`NodeJS Server-Side Processing`](server/) +- [`NodeJS Server-Side Processing`](https://docs.sheetjs.com/docs/demos/server#nodejs) - [`Content Management and Static Sites`](https://docs.sheetjs.com/docs/demos/content) - [`Electron`](https://docs.sheetjs.com/docs/demos/desktop#electron) - [`NW.js`](https://docs.sheetjs.com/docs/demos/desktop#nwjs) @@ -54,8 +54,9 @@ can be installed with Bash on Windows or with `cygwin`. - [`SalesForce Lightning Web Components`](https://docs.sheetjs.com/docs/demos/salesforce) - [`Excel JavaScript API`](https://docs.sheetjs.com/docs/demos/excel) - [`Headless Automation`](https://docs.sheetjs.com/docs/demos/headless) -- [`Swift JSC and Other JavaScript Engines`](https://docs.sheetjs.com/docs/demos/engines) -- [`"serverless" functions`](function/) +- [`Other JavaScript Engines`](https://docs.sheetjs.com/docs/demos/engines) +- [`Azure Functions and Storage`](https://docs.sheetjs.com/docs/demos/azure) +- [`Amazon Web Services`](https://docs.sheetjs.com/docs/demos/aws) - [`Databases and Structured Data Stores`](https://docs.sheetjs.com/docs/demos/database) - [`NoSQL and Unstructured Data Stores`](https://docs.sheetjs.com/docs/demos/nosql) - [`Legacy Internet Explorer`](https://docs.sheetjs.com/docs/demos/legacy#internet-explorer) diff --git a/demos/angular2/.angular-cli.json b/demos/angular2/.angular-cli.json deleted file mode 100644 index 35d76d7..0000000 --- a/demos/angular2/.angular-cli.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "project": { - "name": "angular2" - }, - "apps": [ - { - "root": "src", - "outDir": "dist", - "index": "index.html", - "main": "main.ts", - "polyfills": "polyfills.ts", - "test": "test.ts", - "tsconfig": "tsconfig.app.json", - "prefix": "app", - "scripts": [] - } - ], - "defaults": { - "styleExt": "css", - "component": {} - } -} diff --git a/demos/angular2/.eslintrc b/demos/angular2/.eslintrc deleted file mode 100644 index 31ada8c..0000000 --- a/demos/angular2/.eslintrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "extends": [ - "eslint:recommended" - ] -} diff --git a/demos/angular2/.gitattributes b/demos/angular2/.gitattributes deleted file mode 100644 index 80fa1f4..0000000 --- a/demos/angular2/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.*-ng* linguist-generated=true binary diff --git a/demos/angular2/.gitignore b/demos/angular2/.gitignore deleted file mode 100644 index f1fd2db..0000000 --- a/demos/angular2/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -dist -hooks -SheetJSIonic -SheetJSNS -angular.json -tsconfig.app.json -src/polyfills.ts -.angular diff --git a/demos/angular2/Makefile b/demos/angular2/Makefile deleted file mode 100644 index b37e495..0000000 --- a/demos/angular2/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -.PHONY: ng2 ng4 ng5 ng6 ng7 ng8 ng9 ng10 ng11 ng12 ng13 -ng2 ng4 ng5 ng6 ng7 ng8 ng9 ng10 ng11 ng12 ng13: - rm -f angular.json tsconfig.app.json src/polyfills.ts - cp versions/package.json-$@ package.json - if [ -e versions/angular.json-$@ ]; then cp versions/angular.json-$@ angular.json; fi - if [ -e versions/tsconfig.app.json-$@ ]; then cp versions/tsconfig.app.json-$@ tsconfig.app.json; fi - if [ -e versions/polyfills.ts-$@ ]; then cp versions/polyfills.ts-$@ src/polyfills.ts; fi - rm -rf node_modules - if [ ! -e node_modules ]; then mkdir node_modules; fi - npm install - if [ ! -e node_modules/xlsx ]; then cd node_modules; ln -s ../../../ xlsx; cd -; fi - npm run build - -.PHONY: refresh -refresh: ## refresh the `xlsx` symlink to force angular to rebuild - rm -rf .angular/ - rm -f node_modules/xlsx - cd node_modules; ln -s ../../../ xlsx; cd - - touch node_modules/xlsx - -.PHONY: all -all: - for i in 2 4 5 6 7 8 9 10 11 12 13; do make ng$$i; done - -.PHONY: ionic -ionic: - bash ./ionic.sh - -.PHONY: ios android browser -ios browser: ionic - cd SheetJSIonic; ionic cordova emulate $@ </dev/null; cd - -android: ionic - cd SheetJSIonic; ionic cordova prepare $@ </dev/null; ionic cordova emulate $@ </dev/null; cd - - diff --git a/demos/angular2/README.md b/demos/angular2/README.md index 746d448..68bd4b8 100644 --- a/demos/angular2/README.md +++ b/demos/angular2/README.md @@ -1,148 +1,11 @@ # Angular 2+ -The ESM build can be imported directly from TS code with: +[The new demo](https://docs.sheetjs.com/docs/demos/angular) has an updated +exposition for legacy and modern deployments alike. -```typescript -import { read, utils, writeFileXLSX } from 'xlsx'; -``` +The ecosystem demos were grouped by type in the new demo site: -This demo uses an array of arrays (type `Array<Array<any>>`) as the core state. -The component template includes a file input element, a table that updates with -the data, and a button to export the data. - -Other scripts in this demo show: -- `ionic` deployment for iOS, android, and browser -- `nativescript` deployment for iOS and android - -## Array of Arrays - -`Array<Array<any>>` neatly maps to a table with `ngFor`: - -```html -<table class="sjs-table"> - <tr *ngFor="let row of data"> - <td *ngFor="let val of row"> - {{val}} - </td> - </tr> -</table> -``` - -The `aoa_to_sheet` utility function returns a worksheet. Exporting is simple: - -```typescript -/* generate worksheet */ -const ws: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet(this.data); - -/* generate workbook and add the worksheet */ -const wb: XLSX.WorkBook = XLSX.utils.book_new(); -XLSX.utils.book_append_sheet(wb, ws, 'Sheet1'); - -/* save to file */ -XLSX.writeFile(wb, 'SheetJS.xlsx'); -``` - -`sheet_to_json` with the option `header:1` makes importing simple: - -```typescript -/* <input type="file" (change)="onFileChange($event)" multiple="false" /> */ -/* ... (within the component class definition) ... */ - onFileChange(evt: any) { - /* wire up file reader */ - const target: DataTransfer = <DataTransfer>(evt.target); - if (target.files.length !== 1) throw new Error('Cannot use multiple files'); - const reader: FileReader = new FileReader(); - reader.onload = (e: any) => { - /* read workbook */ - const ab: ArrayBuffer = e.target.result; - const wb: XLSX.WorkBook = XLSX.read(ab); - - /* grab first sheet */ - const wsname: string = wb.SheetNames[0]; - const ws: XLSX.WorkSheet = wb.Sheets[wsname]; - - /* save data */ - this.data = <AOA>(XLSX.utils.sheet_to_json(ws, {header: 1})); - }; - reader.readAsArrayBuffer(target.files[0]); - } -``` - -## Switching between Angular versions - -Modules that work with Angular 2 largely work as-is with Angular 4+. Switching -between versions is mostly a matter of installing the correct version of the -core and associated modules. This demo includes `package.json-angular#` files -for every major version of Angular up to 12. - -To test a particular Angular version, overwrite `package.json`: - -```bash -# switch to Angular 2 -$ cp package.json-ng2 package.json -$ npm install -$ ng serve -``` - -Note: when running the demos, Angular 2 requires Node <= 14. This is due to a -tooling issue with `ng` and does not affect browser use. - -## XLSX Symbolic Link - -In this tree, `node_modules/xlsx` is a link pointing back to the root. This -enables testing the development version of the library. In order to use this -demo in other applications, add the `xlsx` dependency: - -```bash -$ npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -``` - -## SystemJS Configuration - -The default angular-cli configuration requires no additional configuration. - -Some deployments use the SystemJS loader, which does require configuration. -[SystemJS](https://docs.sheetjs.com/docs/demos/bundler#systemjs) -demo in the SheetJS CE docs describe the required settings. - -## Ionic - -<img src="screen.png" width="400px"/> - -Reproducing the full project is a little bit tricky. The included `ionic.sh` -script performs the necessary installation steps. - -`Array<Array<any>>` neatly maps to a table with `ngFor`: - -```html -<ion-grid> - <ion-row *ngFor="let row of data"> - <ion-col *ngFor="let val of row"> - {{val}} - </ion-col> - </ion-row> -</ion-grid> -``` - - -`@ionic-native/file` reads and writes files on devices. `readAsArrayBuffer` -returns `ArrayBuffer` objects suitable for `array` type, and `array` type can -be converted to blobs that can be exported with `writeFile`: - -```typescript -/* read a workbook */ -const ab: ArrayBuffer = await this.file.readAsArrayBuffer(url, filename); -const wb: XLSX.WorkBook = XLSX.read(bstr, {type: 'array'}); - -/* write a workbook */ -const wbout: ArrayBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); -let blob = new Blob([wbout], {type: 'application/octet-stream'}); -this.file.writeFile(url, filename, blob, {replace: true}); -``` - -## NativeScript - -[The new demo](https://docs.sheetjs.com/docs/demos/mobile#nativescript) -is updated for NativeScript 8 and uses more idiomatic data patterns. +- [NativeScript](https://docs.sheetjs.com/docs/demos/mobile#nativescript) is now part of "iOS and Android Apps" +- [Ionic](https://docs.sheetjs.com/docs/demos/mobile#ionic) is now part of "iOS and Android Apps" [](https://github.com/SheetJS/js-xlsx) diff --git a/demos/angular2/ionic-app.module.ts b/demos/angular2/ionic-app.module.ts deleted file mode 100644 index 5d92df0..0000000 --- a/demos/angular2/ionic-app.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -/* vim: set ts=2: */ -/* NOTE: this file exists because `File` must be added as a provider */ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { RouteReuseStrategy } from '@angular/router'; - -import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; - -import { AppComponent } from './app.component'; -import { AppRoutingModule } from './app-routing.module'; - -import { File } from '@ionic-native/file/ngx'; - -@NgModule({ - declarations: [AppComponent], - entryComponents: [], - imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule], - providers: [File, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }], - bootstrap: [AppComponent], -}) -export class AppModule {} diff --git a/demos/angular2/ionic.sh b/demos/angular2/ionic.sh deleted file mode 100755 index c12a32e..0000000 --- a/demos/angular2/ionic.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -if [ ! -e SheetJSIonic ]; then - ionic start SheetJSIonic blank --type angular --cordova --quiet --no-git --no-link --confirm </dev/null - cd SheetJSIonic - ionic cordova platform add browser --confirm </dev/null - ionic cordova platform add ios --confirm </dev/null - ionic cordova platform add android --confirm </dev/null - ionic cordova plugin add cordova-plugin-file </dev/null - npm install --save @ionic-native/core - npm install --save @ionic-native/file - npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz - cp ../ionic-app.module.ts src/app/app.module.ts - cd - -fi - -cp ionic.ts SheetJSIonic/src/app/home/home.page.ts diff --git a/demos/angular2/ionic.ts b/demos/angular2/ionic.ts deleted file mode 100644 index fedc321..0000000 --- a/demos/angular2/ionic.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -/* vim: set ts=2: */ -import { Component } from '@angular/core'; -import { File } from '@ionic-native/file/ngx'; -import * as XLSX from 'xlsx'; - - -type AOA = any[][]; - -@Component({ - selector: 'app-home', - //templateUrl: 'home.page.html', - styleUrls: ['home.page.scss'], - template: ` -<ion-header> - <ion-toolbar> - <ion-title>SheetJS Ionic Demo</ion-title> - </ion-toolbar> -</ion-header> - -<ion-content [fullscreen]="true"> - <ion-header collapse="condense"> - <ion-toolbar> - <ion-title>SheetJS Demo</ion-title> - </ion-toolbar> - </ion-header> - - <ion-grid> - <ion-row *ngFor="let row of data"> - <ion-col *ngFor="let val of row"> - {{val}} - </ion-col> - </ion-row> - </ion-grid> -</ion-content> - -<ion-footer padding> - <input type="file" (change)="onFileChange($event)" multiple="false" /> - <button ion-button color="secondary" (click)="import()">Import Data</button> - <button ion-button color="secondary" (click)="export()">Export Data</button> -</ion-footer> -` -}) - -export class HomePage { - data: any[][] = [[1,2,3],[4,5,6]]; - constructor(public file: File) {} - - read(ab: ArrayBuffer) { - /* read workbook */ - const wb: XLSX.WorkBook = XLSX.read(new Uint8Array(ab), {type: 'array'}); - - /* grab first sheet */ - const wsname: string = wb.SheetNames[0]; - const ws: XLSX.WorkSheet = wb.Sheets[wsname]; - - /* save data */ - this.data = (XLSX.utils.sheet_to_json(ws, {header: 1}) as AOA); - }; - - write(): XLSX.WorkBook { - /* generate worksheet */ - const ws: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet(this.data); - - /* generate workbook and add the worksheet */ - const wb: XLSX.WorkBook = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, 'SheetJS'); - - return wb; - }; - - /* File Input element for browser */ - onFileChange(evt: any) { - /* wire up file reader */ - const target: DataTransfer = (evt.target as DataTransfer); - if (target.files.length !== 1) { throw new Error('Cannot use multiple files'); } - const reader: FileReader = new FileReader(); - reader.onload = (e: any) => { - const ab: ArrayBuffer = e.target.result; - this.read(ab); - }; - reader.readAsArrayBuffer(target.files[0]); - }; - - /* Import button for mobile */ - async import() { - try { - const target: string = this.file.documentsDirectory || this.file.externalDataDirectory || this.file.dataDirectory || ''; - const dentry = await this.file.resolveDirectoryUrl(target); - const url: string = dentry.nativeURL || ''; - alert(`Attempting to read SheetJSIonic.xlsx from ${url}`); - const ab: ArrayBuffer = await this.file.readAsArrayBuffer(url, 'SheetJSIonic.xlsx'); - this.read(ab); - } catch(e) { - const m: string = e.message; - alert(m.match(/It was determined/) ? 'Use File Input control' : `Error: ${m}`); - } - }; - - /* Export button */ - async export() { - const wb: XLSX.WorkBook = this.write(); - const filename = 'SheetJSIonic.xlsx'; - try { - /* generate Blob */ - const wbout: ArrayBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); - - /* find appropriate path for mobile */ - const target: string = this.file.documentsDirectory || this.file.externalDataDirectory || this.file.dataDirectory || ''; - const dentry = await this.file.resolveDirectoryUrl(target); - const url: string = dentry.nativeURL || ''; - - /* attempt to save blob to file */ - await this.file.writeFile(url, filename, wbout, {replace: true}); - alert(`Wrote to SheetJSIonic.xlsx in ${url}`); - } catch(e) { - if(e.message.match(/It was determined/)) { - /* in the browser, use writeFile */ - XLSX.writeFile(wb, filename); - } else { - alert(`Error: ${e.message}`); - } - } - }; -} - diff --git a/demos/angular2/package.json b/demos/angular2/package.json deleted file mode 100644 index a214cef..0000000 --- a/demos/angular2/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular13", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "~13.2.0", - "@angular/common": "~13.2.0", - "@angular/compiler": "~13.2.0", - - "@angular/core": "~13.2.0", - "@angular/forms": "~13.2.0", - - "@angular/platform-browser": "~13.2.0", - "@angular/platform-browser-dynamic": "~13.2.0", - - "@angular/router": "~13.2.0", - - - "rxjs": "~7.5.0", - "tslib": "^2.3.0", - "zone.js": "~0.11.4" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~13.2.1", - "@angular/cli": "~13.2.1", - "@angular/compiler-cli": "~13.2.0", - - "@types/node": "^12.11.1", - - - "typescript": "~4.5.2" - } -} diff --git a/demos/angular2/screen.png b/demos/angular2/screen.png deleted file mode 100644 index 18963733cfc5909e99d89b579f07baf054fe4ef9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95983 zcmeFabySpV8$POtiiiS&f*>IZNDC558;D8@(gHdNN=rz?fPsR9N(_jENJx%!hf>lp z(j_8Y0yFds=YHAW-tM1ko%82eXRWjU_}0f}m^Ys1x$C;F`_@lgRq+rd6XmX5yAEBy zc12^?E^^adyAC|1*ayE!=foAk|Lu0rP`tD&v*pCpu3hrGu3x!$%VqcM;DOT9xU%!V z&INcLip*jz@skOsK`Ej9UXh=CilNAo+v3cBMf3U@&Bd>$PM*Ga8pX=-f|HR|*d<^0 z3r)4i%4aH*?afa2L9FfYbV+krL27Zu#Bx#Wd}?N@L(A%|Rpsps{!>XRQ7c|=C}`N_ zy?5;<Bmd_IU#&a7cbiqre*fn0k0QO}0+@aNk3WWw)V;mqly^^NpZ?=I6ci1G`~Uvx zKNs5zzoz#VRQlHqcm4)0?f!p0$A6dpZ$bWjHvc;z{}|C<Km7k;K?1J);Z-EBe}Bec zbWLK|!@=#>=l!^xR@o?`%3IMC(iU-bSnVMHC;63v?~f05#RjuTiD#6|7n^_kcr)?4 zzrE_a^TG<f#%@!IqK~G(;pa;hl2(V@g*IPyk78%dt6$KX`yMy@N-}$AS1Vd~!XaUO zJW9yH*s0evhiHnel5dni_2rt6*}nJb%2a9H9G9qwWc9?ihv)Tst`6dk8CbP15k@@j zo^2@Zw<$4YJNv|NUQ_h~r$BxW>WQab_o?6S@g2RFM8KjG8H_!I57K;7|IxU*wcIK< zyUkEB8YI;*hB1y2ESb%ATqs*r)%k=P3)4|GuqiTKDYI-A>rU6Q)@6%Dlhz$a10|B` zL%7v+41M$3_I$O9aUH+TZ1w3PlZ+dFSBu~_k6S}%MPPC0uhQifrQ~QQeT-)_bCgZ7 z`Eur^{QYU^-G<JFZW~j{`^yev%U1g0jB$Gp{0;_dY(5lUuY8ZYpdb{~M|w|sjenv_ zG4;-&<@(~2S(9LyE>0I=8JTMwAL-C?8|}dzN0j(quMDHNa~lOq5_-*|GKz{XXS28t zdu`6?c_t;ZUe+kQSU$;zEq$}Mqf6g1gZ}d;C#A<CM=jbXwVh!jb>geFcr7{QkG}aQ zzR1^*$3ItA<1u1OjcbasEsykCd}*9Or+WV6!CjqX40&}i4V{>7CM@0O7+a4~W<Brm zxF?5ixX)%OzJ1iCw`m`_n}u$7B<w~8>m7f2b#lIV#d`jnrZ;A9&68$@V}eiU1!A{^ zfAaS6lZN0iXN4aN-|UtcaygYY^Yj3n2-fLf)2$EB_Bp2I4_~MC477W!bqyZzsC3xt z-;dZy9<QjZmQv8|E?JP=?X_AmoVegIA*E-#ssAfsr3dG_$1}reZj$IQ`TluQh??vL z&+W}&HL3Z+QF)=>rP6L=v`o#}bbWo7k!QGCW?Simi*fo!m)9AWzc$Hk&h{D$#8ULs zc!;2hGsd`ct<oWT&ElQgG?WCwnlETdWljfc8>e17NLma=D`RfkU&nN`h;`pi4DdMY zq&ES3B`Sla<q7{+41QqigWeito6?NkxaEI=C)RB`Wuq-dzwO>fOSbiDW970>Gnpkx zYF<kry;r_GiCh>BmNSa2lyIxH%I`G`qyCEjNmu46KJ2la&Y-Tm?}Sh3*$2s|&uIE| z1=x6QFKiPlY&c~`1I{J6cP&h=IDPlopCp7{Z`2cAg+o0bu7-A^QqWr{nuwv#`9O`g zP0Pri)_(TvnEsiUpPbx3AO2TPz{~w>Id)q*0}f`d|Bc0(vSaK#@f>-jCXbTOXxchF zUP%tfD{}eq?x?oz`^MBO!llXLSQV<+>8XV@W1>Ydq03-1<k8)_t7pw>j_RaO3_nmG z)*IpW71D}ZMD6`RaZNNSqhLtVI;UBj$B6ksF*uBD3Y(1n?}c`w9eVep!^$q~m&_rk zhdqjWRJu^U*-^t_JTQm9eE32_#;JxuUaS58WK2Hd^e--4LmkG*ZH-Xtu_wuC-S$|l z!MV+3U{HjFwf20wshPqShgKOKV_qrW*6Oy%?G5bq4K?Y-E3wP|OEu036yDV*BydvQ z%A>)PY@}b$XF3DDn|zsVdM50Nq}2+wMdzwR68C&QY&^TMaawyOHSy|`zH(kU%7)NV zLT2%FD*fk>TKbv1y7M(t*M(ne;9sTey=T$wPaVa!^?m=IVv>4To_4;EG~%rWrx`~D zKS;g5@;lX%A0}_e&3%+nP5bKwTdGOR>&fU2jHp>tL@rKMDp3e*PERc)rp1qk9z}XH zj<4K9oe+_IQadW}y{Kos)Z?p*m)*B$WMzCJQ#6yiUeb-FB~J2)zPdGYSM-&`^W}H7 z#o|m<_mBJX3RPizZ#dCpGE4M>v31YgsAEHKIbz#Mt!UD@JpZ#pt`pINOS9H>>fw0> zr3PiJbaxEFR}UHm8Hoss-{ko1KjWNz`Z~Ir<tA&Ls5t$)UtYT(<7yVWM@0CpRRCq- zHfa<0T9LKaQa&kp!{g^EwtB83L6$XQ81SL=m#y5CXr=u{)YSatZwZJAR<M*AZ5%m~ z8g1Ynu$=meX{PUi`m^j+?W-3fhn`Y;5Z#A2jS8*@pt3){m{qm)AGw|(dp^G32#te> ztT0Vs*&bZ*Dp<)S6@KM-nSk}2vm{un{ma}Y_2Pt-3|2j!y>}HGKkla>qwk6JyR8_= z6y~*(6PpOZiEoqjXxbK)_S?I@$FqwrjP|`&R2$CXXgy;&I&g+4plF@>)+M0SJ=GbF zHGDQ|`6y^ZVjqQxLMku!#mG1LL+I^Qbh4(JOrFvdhh$GFwsAMHC&GQc2zOX&_LX~W zcLj+sK+JGD{7BDWtRF{$CEzslBy<iQZdXcT_G9^-P`J@Q*dlx9e8(0$5q*wvQ@roN z2gjBR6A2W#Klh#?%?*<T2Hh9R%vT4UTZ7EQCi-nFqEXj62;Ii9DlD{`3hx{{uJeR< zmCqN?WDkkimJzhma5=lKdp!3q<!CL<zH<CSdX`6?Xd#&}`ON$0tV{zh507>>@th0j z@oeTDckjI)>!~3vo^&ecJcqNg$*3^Vm9>39_aF)4qMB{#;#L15ZIo=Jio{@UE9Q7+ z_DYfbw)^*+iY%RTp$ZI!w}c{Xc%6R7u8*1WO+0kFLv;5^J-*!j5*2h|{92ZVbrUm= zS79NxQK&-N5W@OYpCwjBnC0@je%=}}lcG6+f&mfhteRsfc5epzE@WMKq&&{tcnk4X z1k0<`!HoI2bNT&UhgsT8Og<%ll<)DTl)c8?*(V{$kd!BC-Ir?>KQUJ@EX#aDyWcph zZaeL|){TMPV*%HU9epg7+nWV!^=4~LHa4ZXl>Enff9x^uNVq=LuE?eW!N8<K@Z4?F zIkop3Z)Lo;)@+im2t`KTk16W+lKR(ag4YVzb)JaF<*@QDw@BF*FE#KP?7N3T4M7GX zAyD-5O>e3S5(eIinBEB99r?;HPNhlbIG;>S5$v6_+iX^yeLKh65z`mhSAMZKbPX@) z6*0`;y|1=AkyX_)4HNH~dOq~JdeXguyXju^%Q>RSlb0myf3`VV`BVF^RZiOS#;1Go zm<-((X;(7C7&=WgP6eI(msz67gsmoR(VGVTHL^&Ew@E}p?$QDK{Z`w^bqo)lnRmwU zT$}t=Drd>f*_<598tMv2VCKV(`EyuI{X{&+_>8Akidmir(a4ygrWR8Z->-)oi#<CM zCNcC4auj{c5rta>y4bZV7*ErlGbVc2!XN$v5<l)fXGy{+AGcvL{ij|yb1N=D$$<LU zSKhSrH|{BxnMGVtI$T%xQ;E`qIFC@Yyt8lM>1~TNCM{viLsZ14>~)o?Y0(|O4oI(k ze-ai|pr{r(QX=Qp%oBdC<ky#jB_|}hZ#yKi5hoQ5SHkXo2rvz#?)eZbTg=>wdMUsq zIjB<ePxSIpr8_gz(RI1^upDtp4L2dXT4Yu1`<d#iX!wQ4GOz3WxMO+>`<KbcjljK; z*+-Z2?=?+owQXw&s!v%O4_IF*sEFQ|eT5Krlz-xqslW^pdaMOw&XgO8TnK!?@OR*m zuR9R>$b9C##fyW2h3qU|Ya@gM-p_es3vI|fIIy}|*E!nQc2{9GK8QW78y6VO8?bLQ zO*?z&ZvLKP$K#Dm=j<k$YjW0pyqw`Y7pLuX<e$ry9Kh$J)NHXj%^zIF`ePc{PSMu} z91jWCMfuGN1qELBF%cPd3E<({=)fh(xvtz|#U4_=Q@%dV+~c=5hVzUjyO+Ss3tcuy z!}6LpMi=YYVv&4n!|8ZTXkL27HgN&Bhg!NV@Qho1X!9q7lXL%U8N2+Qw!Kb$=E;u3 zo~wzBUqTtw{Wz0MTxS@wK9M$NRwK5-{zFwIrxL7=&lPG95e`fLs>}sLSao3$I-F72 zlV$Q;{K?6};t{3G-hIPLtPV}WgJ!cs9?Q)+gt<-kjQ6tT0j8c4(as&K@EkT;!PlId zz6jF(et@<}G9-xSaDF<q3m1WUl)!9Lc+$|ZU7>8>y&t8zE@Q#yEfXizcl{n^x@IK- z4nE>tMT4=ek~8V%b9vpFx5WsdJcacrr%A}va+G-D?<?Zp@EGt~>Y+FLhm#apT@#ow zMk0gPX8*b613DikBL(wMQIk*URIk~VubUCwb@&54f4){sI&L<JW_zY<<G!`F*!%53 z9gFkrE)%nYK_?}-^?J2^B|XGqBXYkbw*~wR$5FZSTm~_oE4_R=2QNlGj<OmkwBvDe z6HU7L;c6m<D4tVHD3)bn3yPNONaaMtYv~w@u{gF}B+QL-3txWJ#8{5+P)gP_i%eV} zgS~3d7i!v;^`q{7;U$sGDa&{;6{DQvZ~b?I=Ho>3F(dxlZA=8+`1(X_t5Y3iLdW)j zt=ao>N_-i)8v6HMD|Cb#*sP<4sJ`ow%LhqLf25Z&i}6^h&#hy1r~1xIluHzz6nW*X zvF*U$ef;J`VO`ZpR?B_um$}G9gt;w`hQuj<&gfdJO+7j`l^m-o2Pv*)6wwPx87a-J zP7QLKrN<&drN1t%?6zMY!`y#f+q@OM;KA*=95tLC-8Yu?HQR~jT2(EQ%KczV9LHza zdh!LHm|C%M%2N3|_fhxsDNm0-eFj$+y*l8~Ixn^D+=@iBLn_6P1!o^M94%?JNXrN( zS{IJ^nnSrY?=*j3ZnM{BbC%J|2NpZ|jm$j@jq<#%(~`gJAT+A!+_qCF8^<N7C@G$> zyW&s%3j#uUc}2RK5t>M#H{2J6d!r;6iP8}A`&~guQR4G&XxL&4KGe(4(Bs%=3x>UT z9*Z=~UD_TKLSJ8G7`}i+WBM}daM&InGqJ%51?uR&+v0hwr}E~e%3byfw&_mj3q_uB zI{KVn%zhP3V>sFyqod94%P+R?9uwbE$uKd~E+@BDO^%4i&*ro`@x}h1r;uGf?)vxq zejBBk^c+<gcDjp^+GyHbKa<gS9Gat!bc-L{1U#pdj8pepS!`}BF-~Zm6zj@skE1kQ z$Ow2WQg$G3Y6e@nbffXDN{n>Tn5C)fN#A=lPYs*RQxd~(ATBn_xu5HIpzxMw@F`g+ zmr{wA%wv5qwS>Z5CAa+1!#H&7-dmHP3)8)7ks^sCY2RN|rw`#veSV6`Oo+n$?zm;@ zg*)surL^(WJ$Z(VYR|gmQC+_~3Zn$nRQ@<U-H2f6uP@CdIdhM?4JDk>H%*)QlpR?D zhGd(s3q_jI_Ww?HD1IKO{=zQIpMvEH5^xiKP3<{(neaorMF5`_?8zTRcmFwy)1+o_ z<m5>@rN=s^2c{yZ^TL@9S!||mJy4B&Y+ze^{J!#{#H(-8<5^CDg+}~ogPQ(X&+osp zyXr4t($uV>#Ftr!^5^`qq;b>KTCfo^j(4iE>p#Z_d#7)@)jOJGJ^5$<cln&~Vch4? zuE1c*w>sd6QyCl&HJ2Vkf}E2Te~`vK|1dXh)prp`N6I(nmEcMqG8A+^k!w;=-}&?Z zc=gkBo4DGIW!^UziVrnU?wY-CDBgQ7#1Y&@0xTxi9IW(|9BEW8J27`6#(mz)$lQfm zZYm)}!$|1;B<H!dUEROB<Zb@7IH4EsK(VI;=!9LDNkV5rQG3+sAfBqH*(S9C_2G|$ zvTL2=p^g_R1AxRd?!+6BQmD3i?=|KvpoKr%GUjS_XeLGLJM4Po7v_IhcJ2G_cX~}c z?bdTpPt1h7Ha#u}ki)6p_Fom06Wt4;B$<p)-wH)kOa{~JjZ7k+kGeM9w4X}EBmscJ zvDsd1cjXI{W%}zfad_{;iZ|VwPmx`QsBZT5?A=RUITMgAT_3FtRDuVye+%`12A<@1 zlWvg_t+n1RIX^)mSTl^|tTI>G$6*n!3)kCPlZ}6LFKOmEbp|(Mz2Gf!pxWa?V2E^Q zgVWl`bDi-$$=YVYzgJ}+$Jy<z<>ZN2bop4yqMK1_gR844+q9>!{`c%gK_x&ZT>TGt zpQ89QotkF0$<cE&DYxSWpJ|O>SX}%&B!fMcz@vHmc|5;mMxM65X_%JpkB(<Xn(9gF zUXiPccOVz+l|J7*!4J4k@4)%yO>4T}F+$O&=9mG0nzk~&ug8ST?aP(#i7RdngO;0( zUPcZ@&n?}dbW*_>d%5=6l+0z{sRMj0qCqmnirnm&?j0XdNGPosHBA;`MwXZ|J>m_! zjL}3<H~i#CgT3zZ@x_ZeajVb86eIr?2X{;rCqVN#;yIhwc^OF6dZ%-<tXAPDMeUv8 zxWj<eIeCZ9;pY`~VG`=A7S+wS5V<-Y9~6v0rTX^_XF?8XXrNv8uhbQJ86S;<xweHV z-)1Ql|MtND^9hc{z#t?K@19os;{$pQ_A{CkCCdN8rFO2Q%0Lr3g_n{i{`tWra&lA3 zyBMCovzh;yS=$uZW07dD{U7E2_(1&y-1+3J2<N}N6Y?ENLwWTi+`=vb_a7fn($a+T zJT*-EeKL05Qa<@0MT6r8OC0IX4^G0JpM7c!{cZ3&k1*-D`(mVO#h!1me|&J}3f!5- zDe}la`~TMu(Sqz}a8u6S8@~_6UmrX}2$e5_nt#XU|Gi-xZZEm1B=N91`p*w&;LewN zOmF<L*8gq7?*j6_P58%l|FeXDmV)9xcl3{U`_CQyvlQh2#e}~@jL-iU19QNv<L@{g zs&MDa(*sjL{;B3Pi74qiG>hRV`O?+H<9Ys<ke(%nh<(|6`VDMw1glT8ct3v`@Pvbc zb|cRik^rPG*g@q!mRIJQ<2r(*<|8rzg>o~!8o>B^1t3K{E|!#A-b(k^n#7fnU-kGE z>%H2acEjQ_Bth?KT?m~zIHaJ(PksnCLvL?Q$mP^g#POHmI}Axby62!~eI&m(66#Bb zCn9RH>-RQ(J(rV?vy@CZNA5erI!Dz!k}+0LPRI!VjLB({<U<ncDg*YS|0Qr$kTad* zZxT5vAJ;$z=vqrTe>BiM=F{I&JX0IU4)VVOZ1__y&|--Q#Hcp$?eLM%l<T<*qkwcq z9hRJo8=#b3tfnKD-}l{QZ5D8$mU0F_2A?cPOzEdZD#T(yjY{Sgs#(&o<!;4*w>zYj z6Ic3^hXG$G!iITHAc&tAFjlK25rDxc9(x)Cd?jwO{R0lxCt7^SWmME@w#Uo3@xYD| z#Wj)n3WXIt3Ju)lb7?P9DmwxSlt`y0EnK#>)JWuQL`Yr)?ctADp0h<uZHBo+1vc9) zc4DBM3;~<{<s0xxsRJKwwv=v^!zSRA+QcUmY2vl~<*l_e)AAQGWbG=X3EwmcRf2`z zAJ48|Iw%lYzR`(I+H?3kJ5+ycsTK1Fb~2}rsotHK)IR$L74OPdl+sIIPnmaW>8K2^ zw>WRGyfWw4#k=(Y{n-J_F@<m7!-|&wboC)axKw?Ml*D3UMg?hOZ+I$NuS9WkLTlxN z!67w&S5s8x@hLPpFtm}5fWjsL8z##FOEc9hF?<D11>Bie08-tlaZML8a^Fkev+7ut z5UO?q5PY!*n2ND*gO2ei+Za>}b&87*!cocq4m?PTf3A7-je9O-#<vQQ+Nb61X_<C{ zsR(gRT*6c+1AKLNTd-@lV%_~IYOh~UW^*<Gty%D9O5;G@H8;(BL}{)sds@M}(tGa; zKp=vH@KsU!pI>u_+-4FvF}FnmkDudmzraO|K5vKe|K7N{Fwq+81rS=&6KdJ>(RQ`R zn=!d6amHlx?Ot|T<KHP7sz1GpP?rv`HUfmbgxs80^YvZ+N#NSvb{RSkIwA#KFpx4; z5jp8QakcV0q_0##AJS;p+qlTGX7Pt423{FN9XE?O=?N!D4LMUYpCiWiuzCFY>^NXo zmpDwEcWxFk?H3*yYmr)bmny2vRlwnNjO#nAK!BI7NDLH(9d9i&FYvn_jjaE8^Fs$| zb0OB?>16$}GGo-o4m7bFO^|#l>Lv!oQof$L2W(1xSyJ?0Covst-_-i}7>9fujZcjB zfeRx~={kUj#hDbLOkcq<lrlK^^W%r01{rbRQxpw9J9>Ke?_Pbt_UeR&n-4X5txB); z^AP0*qAi#_L>F6uRAr4-Y{0zH%Y3z<BP)<@JNVa~?vag$^5{J|b9gsd&4KDG2ovmS zvl~E|5~DpLf!wtspW07x2^regb8;m&J;}oAz-_Qs4nH{Oqnjt_%N@upA!z727REE) zYaWJjC1{t?8$_G@Ro<M@msg97H;{)~Boyl1XW+A(?HlPr@GBBE*n9v1YzlCwyjN>l z&S%Hv3gN#s(ofS5u9~d(tbNJ;;m?-@r0U5Gs9$-!0$Bs_EB&_jt0+(pbbiuZK8Sj8 zk?P_LX;fmWig@p7f$XmwPhJ}`&2FI%Oc$C7s6>fptmHP10^NW~QjP92HMXi|sH#1n zTf%ZySt`{f`0!qpTpWw@z-tnm>?GZhS*TUNh7Sb2=d+U&1f|3dF|BVBD3+Z+qjnq> zInU_Z^X^Aqqd>uJ%%D&iUc-On9<|)o!xh$mO<wK2{;N(~8QBfIDcDB6y3MHt^tL-@ zrdu2#ANp{zGPOifK?PBO+5h-L_b<Oq$?-~kvq;m94kaM~0$R%>&EBS{b@NwUWVX(| zshtc!L;Tp4S)e67`hZc;NZ0Qi7VU|Z%G=s>P^(F`U@&yNxng_8BePX%q14=uGyLoP z7;u-%WkaotUN3fOZ!s^gjRq&{TFA`TJeYBjt4?*mMfolfCCW@4WWnB2&wz#3Xlt5* zg<nA`egQ1APY@|-jpHa^sxm-c{VDC7Xtxev@Ix}BkI)?~xIugE#kwtOsSR<|op)vC zgN35AzSP@#El1HC^SdeCoLKN&aNj{D=h_CkpYv7#L47*fYh$X&ReT=6R&E>+Xv2kk z?xe_84=&;ansmQ_SXK7n#_FVc&lR=zu_S!*6hp<Dg1>uWvLn7-F?k0HbB+7aXl<-P zE_o^r_fEXuDu=#ofJ5QJVY#hej->U8`=9G;O}4?oe3O7RX`e0Mgy3K}+&z6g{bQI! zy6{k)tIwCxnQG!<6&vYnwDXXwrzH2|bBiR>7l!gz&P5vYFYALxS=W9rR74n*Z!T2G z89wUN;^|Anntzmawpd>si}C;7dvQNY=%5KL{;Lx0JHO2hCF2c^0*s0MB%{bC_+Fm8 zjaANZh0Rr9K=B9(+xq!SZGi76u{$Dm0xWEj9;vU*%?YRF<8*uwJ<6EyqS6|`xm`;H zZ6SoO%rIj5MJhD&IB7*-TkU%`DmRV<Wn3%-WXJKy`$@rG_z!uQ4LoC+2dG@OX%{Su zfdJGXKbzlYk!EERePQBV4mR1W%-{E1g)Bq*mHpadtSpq-!`r0QDPYNG#TaoBs|fTL zKXIjLl-~HKGq|r>%znIdxios-Ayx?xV`J=u;wxJ?-c4keg?GE)^%+wg8+elM88>{W zQ!&=WH@q<7%zAs<L;H%FKX1a>K%;XRzEM}(GO-sUM@a7)mjk_TeB*Je#fNe{T<yeq zi7EUN(4I<7q#*Y~s5C;3B&qqu=1)MV8p-^L>k=9K3l;j&GJq|_MUyx2;8ZhG1j#9s zG4g6veyF7(?Fv~{og||41vmE-PCa*l^<WZWHf9CLyLDxzaTZyS*_ZdwWFmpCwb(r2 zcT<otSPl#>tv{UT&SvLCzo&$AMN>=60u2y;hSi3@?q_`nuDg6C4^Lb`SJ*jM#MXU^ za}gqNvg2G9Dg3W4Jbiy{u_zZrLWz@yx?;VRP4rODK1>cCyV#kUKw+=Y8e9s9s03l^ zTsBp#6t;ro=A=eA_R2i{ev{s@*sRwaXY&AiTxEJ?E-mLPUbFe$?%Ms)nda8UC6c8e z2B9l*U&@mz%;q$t2!NKz%aLCVW{p1N{8fr`JhKPV8i(f`V=Qv?n=xQBjzGE`p6qCs zslXajqJf_`HT=0h^k&s{IKcN;_&jVsr;oH?-@gcR*I#jWJR?7fAu?t!LUd7E9XoZ# z;fEjh;Wf#2B>@mBmES%~mfC*K^b^jxd^tLuyA5w7L8jVn$u%CEa>?H{J-*bH=W%_; z_(Teead>NVVRF2p78*yZc;fY56zYYQZ5h{cCJ|9uAc~=dM8VAlJZVPbEHfPJ2d8{! z*_3Zc5Vsr6Ptj;WmY1^^MOIHynR{a~@w`{!fK$IU4xqT6=}%LAY;;e?iQMi!dyidf zmEE)=_6bLtH$9?TrmB7#v^e9bywt;6Ge9rAJ^_i$!1OCeIFa|4^{W|jOLHObO*rd+ z{i#WL(AtmX;ceou9%fD3p{4qr_#y`5%U7|6qUFo^ra@)tC&bxo4%|dN%a=tEB1uQN ze^_7hvDu#1sjw(|+YL!#-{=W>(?Z2fgN10PKAx!3-1SmMaR;Ex#1}Gc6<Y%ly^#RA zJ$S@UIQZsP4S5N*3|_q$Qc4`?u=HDekc?zjh6LbzdW_m=$fVdCiDczcArp>YeeS2m zKl=H3j1uX6>L|LZi6>UI)99;WKt&_>v;4%W$q-Fd%y_6@ktS-XTYGm}#5)(bZHou$ z@i3#?(Xjx(dj^Em-iK9(auSnI4S&AY{*v1_SS)#xaMY)N>N-Hps^YdfLt-2~w7XxH zir$7`P--x!X}5jVHY{RGMkDv;K;uE7(ud-%j{JLv)*_8djOK*7@@%N$Ybp*1lM9Rw z`sD|$KLS-mX5Yvl>u5|+ebuOJy^=!d_EN)LHV6uR2+%lapi2aog^^F6JQ89(zE^=s zh@Q{A8trAGm86>~Mmi+}<g*y7$oGki$0B(LYp0JdCVE9b>PneQm8&I>6<)aNk=3dM z=&ZZH@e6~*wKpgBHS=gcl7z}2+h(a5TjSL9h-PqPnTs_@>m?VE2_;9qJ)F?t+eD;> z9F^bR9W(Y=oPFuJO!_<&6BDO=e7>FJMkRWN&L<?|w3v{Bya<<ZkhrC)F#YL1D1{cR zy?FYA;!xzaOB!iHZGm;QrK|`gsj>X-I_;}o^foc)Xe3jxtp}^j&{FuLd0TZZvWqu^ z*EFIrKh8}d9IF2v8wY=(*~JTXkPwPjgj(16YX}Q{yOZ#qs$sxoG_dz<eW`uzJ3irR z9-%|46RNhWx5%HE%zuzs>G_~!XYzPrlJnTbu^0_<rVkU!RCg^8Cn=nj<XW|+8WJ_) z^OYMT8XT#kUnE)bNBu7rRAD3&6FWn8`N$r)5|`MCO$%grVVpFoWJvY=Ma)4kOt05L zeioy0MP-M2%}ce>F^a)~YlQ4hbo^+et?~~rlczNq8ckKN7+(2}p6a1nK9+l<-GEy= zseY4;v5~y4I;z>caM`k@BDLSNLUDhim|I&e<<~2=K`yald+%{<PYTm0)hG%@mD0<U zW|nVEmtx!o8Pbj-3G^VDh#d;gx(S6%lnVL4^ra_aPkG3AGsr~R-MOYQt6l;2H64V- zn4vpQ4%xqiluu`TUZ}rCS0+`<B2QB1xSVtR>MaL(uIZz6m+Q!zGPK{OcFOpk^qesm zo<8=<uiL~lgMvN(hH(8uBi@-!A#C~z6pC@vkmzdJUL4~_NO#nH7=;&}TY-kFxLVQn z&7=&S@iAndt<1a1Rg-!QycQ2W*h2O7QwlE5m{Em%Rcbd$)bh`sNSr*{hT+UaXeFeq z88_rG&?s4d243>QHm@y)z{;YG(_U4?YrGVCUq(=VX~e(iWiEEyDs5)<*7GcrIww6s zQfHQMt5WDWp+nkmN-n$dxEVfG!>iBmG>hVt^*)JW-)S9a;LmNll+x*cFT?kBp2h0> zjdwFChkoQyyv65h+)|^vRX0J*ESc{!5eJ6HQ@m7YA&zkggVDcmbe+`5m+Xl+&Ha!K zI6Y8!1R4YNy&Q!PK}dTqWcq5sFP{WNg==s`^6{zmoRhY?YG`c9pi@8LJe%?;!3l_! z+UAXA#(WwXowu4)-4<e)7K!r3!0{>r8`$%a?sq5`O{)HniSWwdwk6Wx@XGwd5hbQT z*%x76#+cKdm{Rh;AgRCijLD?ZPh2eh@^!-4xs&6OdNxI4_ChRt!^*b)uGT28kEQer z#PSyBqcQ{W47Cx$CdFF{Ebf%w$Hxq+%PDB8AGVZ+GKBJsPN<SExGS}*W{i9Ku?D#M z<kec%&Io@K5E@evx!tvDY_vWHN@IB`sH$==^QG?DP4+~Prc47#-K;3C@Xrlkgislj zv=vustKaWuaa8voQ``Sm+t`yJ8_Trq*Qz8u%WFH;8FBi&?fOi_W_%~6v2>;mP14c5 z`>Yhx0p;rLVYz|z&T}>&xJ^qH_rH{Ii&lvg;as%`l1Ocyq%CD48(2!e{0Q$H-y6eK zZ`1Ojvn{oB)p)t6%)0JppX=w&^H?oU1H*H^1~oL{;}sMSv`*%|-R4_3R}v5_l_4t> zHFxS^I0j{tSe~H0&GmCVj6m$hxJmj#jjTn_*rQgdm7XF39LSwrWWsvmNCIp1rrK__ z)|4-~Iw3GaTgp64VXJQSo2FVb7i-}A-DSk=Natu0xuQOCziehyw^Ur`Z4u%7m(%Js zmKzv1^_xxFdoJGr+tuoF>M>4ocl;#nt}mQ~HNIqNIyI$a>#<H%QJ&z^x+41g`mz|x zD5^AcmGk^UVa!V2TT5=TN5u7alRUw^rN?5c+7Ai|)ojT&r&z^G30K>I?7X?AX)q|i zS;<pIj_cKeCGY4a7HjH!;*Vm$kUg9=(2sFci0f6r`#r*^dEC&|XuJW@V(@kwqChdY zU=FGeORtpfxd^%U*pz~G@weDz$m}H+8uCpvMj~?Vob7;2YX)<Cerx)8!D5KWz=tO_ z{T7K4Na0}GhSeHs@8=u8>mMWBoT4S$Tvy{n#bz#szCN^A)=Ug_RyAJDds{6Yu3<wM zE5mkYcG$H_FDo}DmYLKqjo->|jV>LMZZ;J}m2_^#e_b`EJ-h_HK9vs-FqVtn?pT5p z#G3qvl2MbTU6XKKg6{htkQORsRohdgJrKM1K5HrK#?{d+-asq?@4;SX=~iqsYU#Qx zBw8-Z*vedS?Xa`glba-7!w(-}?E~}&*=QXZzi!?ELcCc(Kh49QTClxEd(5U&D*1{6 z+qZbpm2%7Sho$@9YVs9~k0#wOS#+>*%hdc?l<}@~+u!P_D;;}V3plsAf#T__-j$yy zs<F?r@be(Lia?PfK!z)k$5g?#*9}0poRc@JGzw+SmH?F^EPnN2TLFlrON|v-MCZwn z)WHv~W%}_`<Dj<Rop)!50bi(|=CLod{%p)Q3!Bu^lTuZ>>33UGJKuc2`3fRwxq++4 zyyL0*!S-p-5hAA1lpFRr$)NIA;uM!7{7B(=^hKv~KIpLUy{%s2HCyLgr+oIae(g8x zDc7!wM!61tX7Z(7=Kj_JvAMB(bekfS$lA>8c7!u{gm=&N5H6JlnG7x2qWzc$_tWgD z6ZRYVrFJgFi>k5?c`VgI?J{mCfVs(*+FE3@_v6<f?N!XGu>zC2@Bx~S?||hTd#mgN zKR&KZ&uMv!pkP)9W@?LWS(sJ;f@i6wwAeGvutFh-lxt6nYDZdZ+xXh{G4L<T^i3<> zCD%d4Pm`KzXhS~dQ)=fHQ#THkW_ZN7{P4?W@N8>fC_7>@1};oSa*0dYrgP{r;Q~e{ zpLf>s5!!WI>pda#^)4S?O2+bAf0e(yCu!19@EDRsrU<SyPl|WS*Z+Jf{1uD8=)n3R z1(u|Vw+LrGKSgUbsaAP%m4n!$*X5quNoka2e-n+)2(C9|e%>Os6u>G|Y?mX_YOr1k zn4|!Ad4}@XhHl%AjFhwJyeH_Uo62q^qvwCd|D3f#9%k%LuL7X37irow&1h&0AMf&+ zK3F|KE_~D0ND>e=O*JLuiD=FK?&(tPDrbv{L#w0HY1o*ku_|i0cNzRg%Xs&lI|K(- zZ)FX&l!x^cDBGMb85DH?dh&UKmbPAEKWUsLSdQefoN48bh+ob%z1nua&OP`+CS&c< zOh!QxN%;YDUBL}c!^0(=>k4xVDFea@6NnoBrUG-ghl%ba;?crg=fH%Duv+U5+y$2| z=i)YPU)(Va2Nx&+{I-5&kBYba{R7qZUl7>QCb3m+dyAfr>(FVHh_feGbN4dbJ`3^} zDII<DL{Shn-4CIW2tP?iJz5v6hG2@L^HW~31M7smN>9B$>Bn5ogO7AJ;QX}DWiOg0 z34!>BE;NWmD!*UMQc7|Pd?@$L;nGA`K%#%rJN&?lMMLde^t_e^6I1G-##`U=%dh_B zvi64Gfx1n)&|c8J<E#q?yE4oq1|DA42Z>|JoRC``g5Z*f!`o%1!rD;nhs&5YY!u<L zMfR3`C+g6YS{<O7)P)!wWbJm8TR!e4yIW>8c3~SpA|~hitw{Gn^4eHlC#B$^>e?{N zVj>Yzj(ShmyiV=LLE~>N6r$A(McZ4g+q2qFFH_5i8W^Txt1J+0e5l0hML;k#7e>ww ztc&TSc4jvSf8!)OS4N=nuT(Np=L)|HO)JExoSAHv<*3_02N<q&iZ5vgq?C1;uTCPE zYsNk8H1wP>8HL24qvl36x9rXS&L!a>Yq8!HvncDXrPAyQnpNA-ZRd(j!-9At_qn_& z049|Yt!PPESLx{I{cOsJFl>1~_vag>a1r)pkRQre1NxYvrIz#Yt3<_mvkf6-?#zZm zvUU)!v>RAV7-BI_v|kra4GQLIQiJorU%s<BSI{c6fk4Bt>rMCV(P1qzBTu5$WC*wN z$ux&(Fz30wq6G~s2r~}kx=>NY;!_HR*T*sC_(Xc2ceNGJ%ONY)I)SW%_MO)X$IJ)P z<$|x@1yrqL!zqm$QO?XOAuuH98FTE7Uq;&zLCo7PFhQK;IYT$$+#%Da`Op$%xHrn0 z8|fGN2c3sydh9jTx?5%5-;**ZL&ryedPT{ilQ~U0P?yyWVpf{9lWRcOXhY}^2hKhZ zsJ!Nd+39viX)nCi_tY;s)H!imU^rD~1yA}#O4GOG71~W^3d#;1_k%o88&z@97~ey> zu?Dv0LHk6nluiPwL`^eExn73Lsg^u*dy#GXHKJx2Fp{uSc6=W!#9*ofMrLForc(pr z@Gz&rN8L76QA{%r!Q0<2vtHuL8U*aVm>>&*lBuz{Nu)7ByUdg$0uL~I-r_HtR`<6K zcPof1O2ccl5*o{?2DGQw1}0=NsPngt-U1<myUJ`Gx~k!63>MwaoYMKJh~lf&r_cKY z`gwc@6x7~M9(aR^*U{~DOB)NzN%pi%&$?>%%2^rz!+)dhRTBldc=DuqE{J&&K=b`! zJ<-bjJg68ew(KRBo6b3Gt6mrjrH{Qpd+X&%Gdm$@-?>n<e7=+SQ}q$0d$Fx~bK1cQ zUh}!Un#Wh&x5KiuLjB-~96jri@|V_TvY--ho+w0dwUdm;FdL4KHm-oeJlb=_Z$x!b z0)RZziXliBZ*lknTtG{49vBgWedPMl61O)^G)C7%Fbp85yW~;-vhw2Aa~ZD0Kq2ic zss-Qy-soq1qrdE<uTpvevcD6jb{#1*Mbw;qnHwhQ^K{0#CaNKfwY0_-CNcidHj?3` z%vfE@g|r&YY+9(HIKER1DLk}UtYJ_SMyo4Fypz~goO|bjuq(Lumx6tTY5mKpC5lzj zWD%}TSwek0zDKWmbCj1V2Kz%!FH^&NB}0RMmDw71m}i=nuT%kx8K>XZUL`cmEZcsN zZ+`Bq1;HrL@`_qWpu|w&y8V-KmPWpU>Aa5Nrej7fBeP}aA)modeT$cM-ndnGgyknu zC%#sl9;$DBsJ=6zbjG0i#>(^OFja8W8K)t%PJ-O_dTWN!ydBzO(I!vXDVX~(?j@q? z6Tg+xdE?e%qTqPe*QiDg><ijY8vt{@(ig~*1j`B+_SGYZ^7C;;W~hkvOXGPnmvdr? z{6whk8V}7@&(Jpc$s4LI8&})CMv;Oi@8U4uuCG2Imw_%R<0OC=1r<K=2`N0}ax<?n zw&fY8zX07-*?078>$FD*5ica>t8@?XV-=OZopiIRkv_NBZQwt2c|<djejyri;6!Mp z`q)|{xa5%B`i%dx&k&mP0l5NHd#A{$jHh3iX<sKJ-{3uqQ$A;^lu>*7`IAWKuXC(p zo8(n}q8C1td9*6@2SbPU<svt!ij|EM4o$BG%h!J7)^kODa~hL{L@n>q74O+oZ0pyo zbB2T2Hrk$dV7&-w;YzxzgvWNRgt`*kd^_U}2ZSgsj7{QJSonx&(>+Pcv9U3Yq4NVQ zPCX~1!H&!UUyaGkR`@Rb?$j&&XY^yz-@W%_U(ea3dzI3e@v${ppQPoAC#Bs`;(;Ve z>ljUFiv0|a)NZoN95ma#6_fEVvxOwfR7(3WWYy9f1w(F@oMa+-8`b1-!XwsAWg@R~ zM|_!+Dz?Ui1Taa~<>L=07}i@w8u@#tWrcOx<TaZtkI0Ck&Uedq05TIb$czw~6{-ul z?xN+4wbF7K<I>ZIY@tSMDle^b&tlOeH_I$tYOpF#C3Ois_GmvRsBPsRLu;xA>7;WX zkR3Tkhkyt!oTBVHx>%&?G+ep_nMO(v=-hlh)6s+;msZjNMk$ay9`h9;j6H9zBh`L< zi91@e$=MFz-+NWg`!*8GDqIhbFA7U8=?(uq3*h)P&}LzTA20l@-DfV)8P9*=_r+h4 z=PIXI<%UG$X8wcj2yX81k3knusI{w2oVfD`9#Xmb83|`-D4$@OI}sAH@~9VoUT{3` z#Ys-S^iP-4lvWEXV4EVkC!pW3$<4uI0~b3lZs&MTn|x3fP#m)|6V!PWdXI5)H{Yfc z`5KpU#H+m_Ch?0dax0;z%;drelOxLrH(RpcF6mWh90h&~2L)^P`os7M6kasoz&jI) zF*WoC&MGtM^BGOrSg9vB=SRwYSBD?IYz*Gzqk4o<7}{m#aZAo`yo(Vmnwm`5_;UKK zCE&x8bQO|U7(xb+K1LpG(t|B%SsKf}j@o=U)o|j7gY%VNC(G$;?$si!?@FVavYEl= zOCh%>VZKrowf2gG&?K~mVykFcUD;dUWYaWETNaNJ>D1lL&1&L`U$Z}6mT7?&roO6N zDH)N1EJA=JnfD@aNsFC2#?5`az9ZF7>)ZG6`tm=r@Ynib18og>JWlPttY)%m^H8!d zwaQk_mF~dLb*hgnGM!(?N;RAEEa$HpM;a*hQqX*S3u$(zzOw6|s>FO6@X`WrZ*u;Y z676Wsd{PfkG~CLl3B~*&0(@`~a-sYOSHu1g0g|V30f&@#Ow$N5g5PpASVAN*?2A9H z_4tn~{XaYtO{n=xvs(xLypjci3|R_~{V9p;y9B=SznA!X)&JXxe=B2s@@f7%PQOj> z{}sco{0bN?*1}Z#PiNpQKeqQOG~9)SVV_d|eO~`@7pYule-NoPIw><s|4$zR!7BA& zgl5vTz{jmWK9T1HF)?qi73Dv@GymKCTL^ve-xvN`uK!l)-?8Msqwy~X`QOp_?`Ztz zDt~vJ{AUmU*@NMod!2l7JE=yS5g7twz6CNsAKI;Vx@65FJ}>N_>-++$>B>8lr5n>} zQzOsWRFNKOCMhQ?q~&bi@=_+C`2Y*>gXZ~ZB_a@%T<I~v0ZOUeI+hTkdIJ%8d-fw@ zY{%kC(CvjFeVC#)1BE+H<~l~MiK-$1cbXZCfco&IBeeb{Ul@=0Kzhp!F@5ru4?sQ~ zEa<JpDMaUkXlNLGk(TN(ZG9dVx5@YBFhFKz>Fpt8H_yaYzF3vJHpZt-P;vz1F%i94 zfL2<3X=~!Ys~OO|-oi!)<T=tSLDka1>OT7#fi;Z|9n-t_+VRT)?gT(b-gld@K-lnc zqJPoa@Z_-M8ACv%JyVAe92$Xi6Oef~YIRaCkX+N(SYF-iRr4sr@Tp*F5l~gyO@UH< z#_7?;NSo6z4x{Zo8O+si+5i+iW>fFD4CMztFC%}U(~AeQVEzl#;V)vI!b&;4m3(AT zU=e)cf=JCmIt;t9Dd;wf^;~gWZk9-fKJmPM%bnR8@{45qrm0cPJDTV+LYERU_sPEE z9u-Pk5aCpA9IT!gLq@xd<%ksb)?q7vvAO}h(2Z_PMqcY<Q=@F#E|}u0wvkGLMG1|v zJ7T8$K0KO93CNHcLo$(<Lgg+9_S^vwekh2<17{b2AG?Miafo<)<^&=D+dwpg0O~#4 zklmTI5+_aO)<C6UumxiiRDg`J#jKlz8+)yTeBfLnwD~U(ZsS8!G+i!v%Ri^A7Al&- zR(KVIoO+rJY1b7GNGbm?a(!!qd2oRmkkbuh3XQ}d;5z0o2_$V{`6(>)94}3kGy>3d zr4=BuDCj{Kswp*a<>A^M1VLfP+T!Sp5C9Tgy#s~>mKY@CmggbIheo!Ty*R+*3|e-4 z7}g_ENq(n!AGYavW;||c#u4yGweNs6&K`NS4q~Z35q7#fHeu8i@#mLL=qQ17Uln8I zW^Ro;1$*76%}$0Brgamj3HCx{NsZphCnSK}Nru5;a?L;h<>5MDufk9fWjnKd#*Way z{a0Qbz5Ii%Odsip?ErP`CL(Oq=S=YZfsC*^M20BaJo(53g-@gi@r+MfC<A71Fq(vj zW*#W{0I(%d2D}=fcn;2$d1ldgs%?SWk&q7{dX2?aoN#ErLVbCslf8ix>@Rw*z0gIg z$^9X)Qi;&`?j?zxhIgdM#malAc69(m)bSj~EA*KK2P0G3Fe5jz>_d=)=)?g1^~~9< zPiU)yDnM48(5gk6RFvx*>#03SFf{sf5i#*+0F_XY5vhPt3p)Sfh1I}3OdJ!Y&~Zcn zj9hFbf>w(6+<7u<+izVkr=!5kjScV<;6y$wk+;^s>vo<aZRC^^08bu#b{`PR`E2W* zE^L1WMm-PORKv%UOm4tXBn681ee64W(+$2!6;g=>?rHY~9(2ArUh{w;YNG$@6L4gB za6*s?yD>0pbB`MK#~(1t$d+SkU~96`Iu9igz8A0XJt@lO2I7^7ZF${BC8U9!u8>LI zU(iDED3Zn#u_8A|XR&O=encpMAobN&-v?i%nbeB2yY&6_Da9n1fR)zma02lPs`L>s z*(G1{LHZGWrQyw?RSPvl%jqre2k6g@Z;bwzkd;~|1HHFVF>p=OLNI46$Q-Ej3|se| z&v@W!mQx@Pyk=4tq=JlsxOyaHPo2g4*sY)3v`7bu{DHkx#=LhwoY|QOvK3p2FrO<h zpl2f}qHzXXYU<9V;91VkeyYlLJ!JVPR@7X>4aCNlQN5#ZoZppi4lX2F=l3QxdLc&6 zuFeW*)>7`XH#?RQQVEK=SBSU81GQ>~trjW=0?f7p#&mQ$CaA#6JBC++LwjQbL`-;? z?9W%1JCNoD`YIV@H)+mbp=)8xnwb;PxhNh~juIC@0A@8hZq;<D6QHy&{@yJn4RdMa zEq&Ow*Xoe*f9BB8f31B~9T_6!V^102vi;;%OMISU3Zn3#cR2Y?BY+SMIHqM3D}vpz zD}D!ZO9hb=F-h3mCUyb_D+YQyy}zbAwbhd$Tq-ibE1KPnUUp;hL+>zRd?0z(M_bPq zO|;0$Rjkzrb*;MYtoj&DTWr>exj1@cHQLx|9KMR~yNBqi9ifdy^u!3#t}JN)`Y!PY z2Yx{tMj|o=Yzo?Xlom>tRmI62fh$y^{i+9@F$OUcdy!RO^n%$gzQebJb}sp(5_Il! z_?O08@m|%)$O;&RaA}$2o5IdIw32;L3~u4NA_vV3#V1@UckfR?kb}QmFuQs>pq5!A zd!*CZXP=L5z8F_hdZH=DP#ozm*s=P#rEAE+@}z&k-lT0{ttGdC5S%*3o{#<ttu$wh z00vkSS0_UrZB3qid|2{V<(`>iS@=gDuF!+NcX|_fl3hNe1JzHb0(XS^f4LcU^-pm7 z&xhFEk*{8x^5@IEX1v+gtzkXC4!tUJ2fqd!KerFDFbd`8m2hB&#%2*($uQq~=QL%J zacu~68PCI)CtZAy`BpXl?6ws|Co|g1VW_Viin2?ug9>)TX}Eu|n({{05krAHEUnJH zrWmjHr7%^-Fenz9a!RcF5v0`z1u%y{0Ruz>xupJgB;>;tO;bJ1BVhA!2oKf;6mgK# zO;>oHr#n7Jz>fI+3AGtJGK{3+i3lB+^5o-nkinKV=KqxyXoCO*MI2Lw%Lg3=!31{) zIEA;!?4PDPKfk`-F-B(FQoFjhAJ4;m-|j`GeQabRyw{*z9SdP@DCUY<6o@6#8t=kb z5*v*5Uq<zN+vF7~tFdLYuUw~7lCw908HG7Ef3!)TG*$fZQ53GoM2;M_4Q(nD|Dk7y zIKk66joWS(dQ@WIf8`q|5t>pT8ck;U|B79U(2jL<c;5|VMS_kXE@z|ZzHA5+cT|9} z4FsPSB!BYKO9y)09Q2#>-4_nSbPxG3^73ELgc3kNav>T<km+S{tGo@BUC9bgtU4^k z53be79Og>QXh%Z`mSur<9v-)@ZynH~vq3y=Ak~dTISQ)i$`{n}lMB>?V}=(<w1K8Z zmQ(P>#&VZ$`8%?8962g15iIq-da7r~b?!vy-@2ooImQ9|e<p3GBM%1WMZBGO9*K-3 zxWShZ8V-F~^dSjyz%@=mW7-Yy6SEp&jI2A({*ZW2+}~cR)H_C`roiV2oU6DGJH1*# zsn`bauXo(`qK@DpJ2}rBgrAVxF7hgh2(d(_@)&#G$Aui#xh)(GTqv1{il8{S5A|ca z9U4LtIYWOB`H(;8gG@QxnK}lni*oE*HMjl=0=h&I<mjp}mIYB>C#!i*Yi_STz7Oxh zuFebgeWUXP<zL-l2zm!&@Ak90u*+=ys_IowWXu$GUHX|jo86cMO3Iy%vW!n(D0$p^ zuI+r^27Io4L2$?ax&o2k!G7*0v*x|CrV%BcZt>>_V-Uf3oHx&~Ap7vYKSq9NO9Dc` zy6;o!PNw~@k3tQ==m+LLIR5K*$oJ%<C_#K^UX^<6k1MJF-#-(~Hb^>@rvK-aiohNl z24!#jIWQtO1)RozFY(uoLYLQnI{_#ApTlF#``>Z;*K+*-)v)i}D|(syZ|9VIBtqO; zA9|saJ8)`p(a;JQo(pQsM5L#?b7Tb>tRi0znY3@wMUa;jIexpT_^6T4XiT!#hs>*F zt^jf9bG#3cJ~@zM9x8N8OV7T6@c;Wg%IOf#ujK<hMG}S&KkuCad9E6gA-REStK^m` zl7xbwKpBR>-P4B#@8VLko$%1Wxx>a3`F|Tq?1kbP2Mv)u8E=wfz1;es3jOD%G{#Iw z(Ysp*8Jc?o>cT1SR%fJ(H}%Xf$Ul-Gv~UiB_Rf=+1kpYuNRG6Y`%OcMFy*ozarBkH zC?hn@7|Vo;C*udao6a7)s14?F=9&Cf>gJD^LgDfJlu%{)q*7dA=W?sWi?P!Ykj%Fa z>5Lgs(Mn0SM|k=WkdZJYb$LP1?*Qn+pw${igerp|E9HMR!4Vgz6f{i~n~~;FM8Qww zcp=1vR1%U3-x2f%Nv9CFp{xOfA$R)Vfpg*-_c2?&YUZJ<BaTp`cdC8OCJDN!dhS@| zSmNw`9B6tXer(6>)FYva0C`08{qWv%_}U`p=?+B8+9JK&lnW^amF9dllJ*zv#BMhq zq#<9hVrxm%J^}q^`V5lW#9hAw#p#^LPltL=j54C0WIAW2j5*Z}sy%u%bRwc5_X_L) zjaYb@7gEL|{gem094~y{hZqz+#LkYhzmS>WQ;?=-OyJ8FJcR;5D_K5=bdO7_#MwO_ zhP2gKJ_%>Di@+%lozcSNBuMyDV*!b3L<S1JH;_;As@$)}p^J1tB|-L?TrS16FOv|H zvVce626D=yAQ?fC0G#4PvL?c9)V?ib!1e#C4?nU%8B^$E8}uWeR)$TJJy>n=D3HgO z8yPhoSYjVr_p?0k;PvWlB_S9II$$yfVr@um$?-?oP^*YKQU+vSdV&ZthD_na1)OU+ zUtLrz4R>x3Xk!f3gM7WgP&W~&uYAZ>htcR_C>Lfp*Lvz?!({>U=_+t{KO(oUcHmqd zeHq<fMOm<SToX-x4L~6{1kG_fj7&SctMup)fHaf$d}>Ev)_1fA_eV&r%MM(@M$~@m zSx&S=r%fy#cZV_2`EI>lhyy>P3)%8X3vWhj31B(hIt+Ty8Jbhy>3c>B1A+wkL~<tg z1}KwLk~DPz0Mq-vF@wcUoChGp;7okx2(k%%m!bRlq(xms(<-EU4VkP}VJ(s~{B(MU zU*|O*VON*&sf|-MQj3JH6+x)-JbTH*5M4~Zg(1Nrr5c_g!po=jy%LfXBa*+8+oE0+ z9`J}Apr4H4Qj2*<XfQHQ4CD74RH#QzMFSsTALP~!NCpRgQtmZ2DZ;e<;sz3qe#wyq zR$(k;Op#i42VB(6C&2l@YAW1uhq;An$TTp?@d%R*(IQjgJp|BFzv~0WFA1VdB?1#a z$gc<!v`;}8sM!Jirns?)^LLO*^<aUjkkqUAUw;hFq-(?Vugtv&G0>kYE-*QM2Vg|d ztl5UF6L*&b)}OpfYy?*jr$M$*vF&9+)eyiW#*2W4=73eIAg&7It$b@1DSU<7#}M2j z7v{^}4-QNH^aV+Ry~%=QR&+7vMz_OWBjf3Fp$it7(U4^z&t(+c+UrNhorKiey>`g7 zOH{!Q48=(gNBQT~3m~mVdikbxkh53v=v+p{<c`6pvwIOdL9#h57JQwL8s<_U-R9k^ z%}9qeh;PztyJM>DGvRJ8FC4XChvNK!r4Kx6o)VEH=e~N(!qu=~_ZF5>`^?YXlu8Y! z4r^ZWHH7i3=S9ufU&P;JIKspc_MGqa)%PC*j$AsT_Wr^lCV!U?E+f0~wed&3(C0EP zso!)G-p~DQYOcVo+hV~kJMu)2TC0b6la%{HWUp6j>mJ<9sJ>;eVnYA}Nd^Im)wJGj z|BxD53^JGQbRd1LTA!$E1zdO*@2BR0>XnGq-Iy8LDW$`Xo^6;_*j{8hF#3LVxnS{C zJ4^+2Aia-85Qgt;WJ<<rX5L#+DZVQTNOmQy)yc)w{#pVd>Z1(^IU)$~mb1-Kx~wU` z03x;C%PaTMZQF={OQ{@hj>2#<t4$!HcZT6>sDN*yV)yfH1|{8DG8b2;Tu%Q$iZB7s zvdqO7Rf~=rFtyIBw|jXvoK}kTQtz8)LQZE%bv9d#)h<#n?)HA`^59G3Aq=1+^3N(e zk_DUhWEhq>xos`hB_j>)xV3qX<pL1Zqx1`G21Qxjw&7nd@FY$3wt}p=CMsq+cUEEz zsV%lg$qe$X^uoU*9rl4;&rjHY#Y1pXMS$X@#rX2k*CNXW!nL;{KIXhOn?j!$$hYpF zm3q>Gj@?-m_RE!LaxiW#$gKQDC^c<Ijai4Fow`*He-3%}t%}N);LzjS9S>n3N!i%J zo2(BjcjGn)Gs%b$Z{$4dbqAQ$At1W~LlwyT$dOqlrmx3OH6!rR6u3Fv6-z1f!=Ag( zRhY%CB4mI0Q1g3D!L0B|aVTildZZAO{j@}OHL|SjvkS{ptj8GNORQ)Nt^_|?BjoZN z@y$uC9^>b5Yl4rK*}&<vI)Y=E4{w60p>mtzz01Q;n^Ct86OgB!Ca*pCeql?TRpvbF zfgCB@(bvyM35PJ{(8Fk=B!@>P0#KxX^0xIWW1wUl4*CU(NIj3lMLDn0ifK3GUyJaO zI2f?)CgrsyVY>ix$df=<Yt3}I#RbqcFNreXLvSTSgv{;n#`SaX;4hAyhno*O4tFS0 zl}|A^FZ#8@jPaa`Q+2~(qt>PS0woat`jz(Awa9|r8kdWC2=c$E1wc;e6f{d2dd#vQ zf6!AxHroDTxqwB=Ng09V#@RL^;k~uUO575f5HLlip%D5-)Y7yw^tU37L81S0wQQ$G z<rDfoa>6#}$pZW%2)GVgo%7{d5YN}Dw%4?bA#*XUxGznP-dve`*&4`TZIENu{KI4c zDHki{_9EGeDg9`PAJUfJC_AwSo<x))K(V28LS{j4Weyt*7@JIx6Z9CWBDk6v+GcAz z14fQEIKokxU+iAo`FDo?FZSLus>w787gg+_q5>i|M5$6mKmjRYL0XU&N(4keI+5Ok zj3Pr3gb<J}LI8!(2|Xx1p-Jy#D4`b#1V|uozc@|joO{<gKh9n2tdl<@lk%1Kd-vYY ze)h9>o~|&RvmDEqqjm6GnvTl?Gg}HHJT_m^8e`8ZOhegCJX5uvhmTtUhZj1%MVr$z zm1c9^yo|Oq5{zNc06*a8Fn}qkbDHd{ji;!c5ir-))#a+SZTuc=b-2;PQM_5`b`@=k z4;%aUbtGHfm;JOQD?JE|c<#`k>0zHM>?UOT$OG<<DIe%xh8k6Slm5KV4;oLaZd1BS z<@_|>)5;RvU712lbKNO1yG7MM#vb~Zez``wde7VyK)YVjWJ;i^?}0END!e=5={XOy z5maq?>UVIN?0oe25nF^_0R2|#K!Dy8`U?D*Q^xA#D`4WkBab)jmqGZFJ)f=mG~sJx z{aXeaM7?Rd_Yi|!e{NFaKDkE3sypr1MNGG9+1+IafkD<X4r5hbtJ?slS>Lcd2Dq7h z0SNYi6;A)Jduz}wM2%%i#UB9mL3RarUQu8G^yJ%aVDNhJpGITNcJ5WHXL0M!5{>?K z-~5y^33=wfWM`VN*Gc=y5W|h0j=v1(0mCHZ>E0Bdf+6>Le84g=jn>3>XJw27?_?7u z)*-h!GEqvKU3?z@zV-smLM)DYQnVj3=h8Nc{IwUrYy=@6i$8z~uceEmGrO1SY5L<i zfEF~_A#O<x&}92(IelpNiekW1`bwYU`@EA^XhV0C0o+dxs@(CqbA21|uf)Q;)4&E; zFV4hk`^1`#@X@^T)4XHnEH}<0MSsMu%4=d6cr(z;f?h2BF~A5JYa+9>Gch-Li^igN z0}9ofegGAjg0>$l+;X8!s>uXKn>wx*&>qjX>o0(L0-#B)z_rpJL#1n2n$nH~%?c!P zCc8p}vW5SmIcEY57c|T?>d5^lAL*o?O<AY;uA@0+FEd3^j}Ikdij}_a792cUfAk4Z zL+jqeY*t%tRsk*`Sss{Ft;v`8qSh54bk!#h2{$oqJC0Vk4X$hh%Pal1UuD4Gf6u{; zBa9i4rm~&O`$TFGg*gr7o7EMD(SD;Ob9JJ6a;5!7Uzjo*2AH$d0_)hK6)fU`h7r-~ zxi$G8br(UwKLTO;eRzc=U{SYoeh|$zP2rcifk8cDJ`$}j2LK<wJ_Xlohb=NbKQ0N( zg&!=2QN3Ta=Dzd|-xpvMu)0*RLMuariA|msT`OH0F4d$>lz6T%odTh(dEQ;84No-! z9730-&yQ)CN(XoN8sA+_q-7+28c&Z4(MD=mMZ7TtqK|)=!sQT!-3Ie8=}D4B6#!_S znzEdWI9Uah^fCZ?>?x%uQ=SyF+VuMxFb~6RV=4cHI8=qe4m{rcj|{+ae%#p|2aNWL z82dT=C5t<Hb3kb$iFz*Lqan>yn>;lRL1}M)7Y(2dE-uOxq|)ZGOF0ZlRBu+VB+0$C zxsznm`&sYsqQ90`1h6IrK)e{Rz4(w80BPU+!LkAVgbCQVE|rD<rP=;pKYq?Kei-rg z*Mpq;9RLya;7PHcj|`mRgW(tc?vY&kpUy`+v0~0kEnf8bp#Pq;4Y<T<cS~AO`S}{3 z9SRdK=)E2G%Zote@2nV3qEzsQ`TcV{r>>k5d>S2$YBc-rIg4IuWt1BO^5?(r{Kp>w z2K&?0?IlnC_nh&-Iop<S|6Q$m#&ed>vYpQTe0jhTzX?5!wsF*#`p@lO0e|6VfxNBz z?>V0X=cF$=eaU~>(DYqScPw<r@~a1>`*M2tq=!#BV);2x(m~??9LCdeIUSe(CoQ3e zPkQ*IhfjL={Qn_*PLCN0{yhue2i>7a=UeC$7oAo7mC2;DigY5E&Yl0t@zA+*dbxsL zA^HzUrB{gPMLBvM^yj>VR;i{_Ty%<yPI3JlD(C_Wy1;_2Y57Ibv!8)pA);4^=*q2M z10`K7N7pCP^@+a<G3g}=x(b&reEyHvnqH!ymni5Z3VMm+CzgROBcjWQ=rW?841(5m zNbeh>w`%=Qn(62z3VMm+SLTJ@kVx+VrT2jT%Dm9SC%x18f7**q51;h#Ne`btryBGM zKfS_Fukh0={6A~q=(AbqvsvilaDK%J^a(`t2}JavPrs(w{|j@rzU%?h`6c%4+jq2X zr?Rn8X?`E>2w-4^44}A0iQoP^sH+2bdB)A{zZ0D3pQf+LFAb0MeL}aLpMfjgpZwq8 zH8q<xmHwUuK#vpj*!h!~hK>;E2!W0e{^ob-)<Cxgx;4<P0f5N#Oo^T;(KDsL=_|T5 z(5-=P4RmXuGthMYgwCJP`IA58A9PeeM+J0LKt~01R6s`sbW}h`1$0zEuc!atP*10O zd%Cx$dwaUKr+a(4xBqoo6P*#EGa__GgwBZ284)^0rekC}My6wAI!2~rWI9Iv8xhg1 zfo=_SYoJ>LO=M2z3g}z`oh$g;7tpPNZVhy6pj!i-IioXYbmr`TA9KcL`w8^-EC9N{ zrTbgDzoq+Iy1)J3=WppgmhNNeK9=rd={}b3WB)&^!F^+1UsSZC{mM4nMQ2(Mg<n&7 z=*-Ifis5inROE-342)N=8XhEFJGU<~i(lm<|D$kK`QM__UcB&l(k|^sg?a_W4<*mV z4F*?gjzV)k3s&ioTs4<^hV`Jm!uzAe-F)n<&mFw7Z~sxneGI2w?)&@CrMP{naT~rK za~J-8<)=5@)b=6ON5{^eKm6aXoqDP0!~0S@Lk@3D`JcTxddB*bZp&GbC$nRolP5OY zWmn3aCL0@4Y`Qbb$!jxBDLiU1x}R;Qls*`rv9_^s8m;t*P&jb-_={@~!(Lod4tLE) zq2nQTsFElBxyHkmHFK=LUS7b4>IWOIDO_(k$Ia(ue94}*a-<%eWmM^yGrtM$!CC23 z&6<jg36gyVbq>{}KF48)Qa8WtjW*Mly8l^#Q(4jhh5VK#K1-4wGf5s5H_RK$kZQ4g zRXP&;7%s&ry0{b#Tfj9&fDaJ*fd7Pt{f4>s%MzZ-vSV=2##Iq<g3E6%IKI%vahGeQ zJuG+YH}1#wgF{pA1yx*@^R6EjmGM|k)O~&XsN$6)0S%$-O_06mwuBj8P~ti0v5T_q zIX<Lb{iGf4(sr4JvRBOV1K6g!zn<x(qTgwL6@hERV*MsQD4(6xCJ5;vTG)NS>_{nj zI-wFqrIe`hF)*CsSJ>}x$#-MWYV=yfoioGEjCKQ#l}jVzL7dB!%@s~wrxX8uqLNo1 z7i&*r`BGeCpEw66$a*+tV24Bss2jtT)th6(CphKruxJ8TlO-RpI@we?46PZJqs*om z4j=Q`Sey%2*!3*!1w-^|w0e&I_t2=LtU1x0eZh9Q<&C2*9dd?B=)va~JZ4jaD!#rt zV%6ZXfB(^*m@iXU_$aVuGrQyt|L2kx5po;9>$l|xiZL;<|FkD*e|`pixPlC`H%Rmt z^IBHeBWq>^6f(Ok7Bp3K`0uX8-3B(eBK7pvX3gFbY_k&I;BKAcMj1<XAF?U%;|r|| z`e*n5dE9{cGmrFet%=gzNp5|H%fRin6x+85m%8Gq%O1vgCe?|c9!Fc-kZvLSOnbqx z3Gu+{mq-GRGsEWr%d3VycE)2ZnY}473hK-YOoSkE-Fm*S=cO3t?(V5T!f?DobZlR; zn7<J<wJP8DeKN185Wa6GwW@gIHMx79nGi^zx;osdTJKUD?bcLC3N~@;)f<(bNvvBP z<hc6lrr&>5H&)RE8OH1Eov<uuaq`Z0#nKM>ZQWADp}w=AXKNbTJcR>0hosV;4~8sh z7Le`w3K#@M&<YLHqWK(>E#OD~z#c$v)l1l=v)-IZ@rU_dk8c?N&t`t9=yr5U7uWnI z4BR|BU{?-Gk|{S2mu_4tn+)%@;V1#z2OHP-?{&<TEuu9e!Vt7jOdd*|l51|Bakb3c zyv4{V3lV5s;i{4|RnT<Vh0||6a@Yg!XI$zydS>m^v0wH^u>wPqv)kO~>|7kbMfA*U zT10q6Mc?q&22rb~hu!6@qbOBv;Rto*{idmM-@>*%NXp8h{(`<d0~ABf*q**sGuQXT zrOlJA>r21^bIX<p|D8!N!kUl@%9EXXcW)$1D=m@rw#xFY8nQ-W?j4HIlQJ=Z3r=pT zFSqqGZ}B1xHSet6@7r9Cvr1MGxGVn8V)KV9s%g6?^*OIpZ$6JYdHau-23%=JHP%an zo0${DuZ6d+nHCHO6Mt7#zYfNf4Er7<)tT|6`VvaY#GqXz1VqYI2!b8Q55|yseX(nG zHpEZ|Xu%TGf@Y=2pRaA!nfayeJY23}y?OI_>x)}sp5`MONuSYU-V5b`3zJ!t>VI&i z?2q4=CLB_X9A;(ZM2F7IS8n3{@uN;nm*V)7-jgyFsMt#$BTjL*nI0LY^-4?=<+5Xr z)WD9u*qfg*VeO7O#B|oeZ^iZPtA92-f%}^+vbETz``N1xVC-3l{rJX^UEWp~?|vdR zpFo=2$wv;mA0t!vBgl+FoQRv!umK|32{zEBf$q(`!#Lv%jz#FphpyEjGa#dVm$6y{ zQ-K)MOir{fERhrK3U?q0MAVii;;@EZOihs~jgZ8Y(r>QW!$a3N7$JVCP&c;gcqm+S zcpiSy?iF^OnqSo=)Xpr}KG7!1Jvxux$jYhZ6<zm>`}MR5-J3>usLKzhB=1*YJQ1t- z2Qt>gIWyV&GZUM*A7$OC?#&4o<4X6osG({fkN&PG)P)rwo`5fl{1Wa0Hq<^Kw5R8@ z^W0?^AfP6bmhdo*d5OWI;AD**UsFq0hxXbSKK0ULAF=us4dn4BbE&@BO*{FRXKN5h z4h*3`Fckzd@-y@1$FCG|qAAqdrr?2-l^bhYW@^lS3K6wt+tDezrFq+VE2WqjXQPI% zQW@tSVE2-IiP(Y@94z0Lp#H;eIThr`qPX{JnM8}z2Vy@y<QueMi%A3v+6=RtWO%Ep zILy^=>@nJ^yb<-{C#P3_%%b|EADIxFgcgbGey#M7Pm&{+$*-mVEZ$-|DkWwtrh-WT zGDqp2%0#kv1)--txl(P2-Ce_7rAjz(#jXU|W-f4@%k;8e4M|R6bHruRK0;x)Y#4C2 z<^)BIb2<-;=W_Xs_%YM1TJ{k({U?#-?QC1Uc`yQIvwEc*2;JolrK3{%QWIR?P92=? zNLc|QVTn4#sx2OIjOAZ6amrfbikg5+-HBTk&dun0nS~s<R=vd;TF6}rd5M{!U=+wx zaHpEwnqBYB7Sr8AV^YESq`2Eo9~0z!+%v8ZZ^Q*RvwZ8EqWtmevkQN4B`v!1I!DsE ztNkM*iyL_Vy$Zc%A>)dkx0hu5G~y+&bHXFdk=I7AXXkX`Ozt&RAlA<^AX&a`8fS*g zR+1<Sc}>s?WX@MMkfM3iimTF@lRu*%AYRBba3EVv_bC19^1`W{Vow!E1uP|Os+KDj zCg7<S?*=>yu^&{vHC})&w%?u%tAZwRde3rsbUt8jXsB6z3oWZVDcj2apbU4=Rd@0I z4Rh68J=}r4tZ^32JDqc^7s_eQk~H5dcx}7&W{2w@b;W<ut~Yz;;>@#?lraS$klr|% zbo8}$Mj78dMq#|$^2lHf_wiqxrQ!#+00V(*hk`&SZj{a>`^2QbQ;`+FEj&8UO#P&j z*$7{TIF|FM<DRr^GzM|P&iOB6wwJda4RY>&56V@JxEy!q>^G?c!=DQPpb*=m4<~Hf zP}S5nE!ob9lvXLtx5o0J<jT8J{5A|TeZZfKSYE9k&k8?s+4G8t3XfOKE-BBrBU$d| z#}v<rW1AD9qK*AETMeQ(_Z1#7{k>KZbtCVL$U}#5!cZV?H}hMjb9YA$QYj?Cd`VQ% zoR3{o(|k0S>3-frZ!oX4-_32`epY|9w9m+{9Dr!o7GNbY@t3a+u3)xv;a{~baaB*p z+f|VIP4TF*iIBO#(AHG|m{=U&wk0I5^!N`3uNv>|QXy|uykTu+-kG4J37z;IaRQ^L zVIjB4A*5cUOtPW6{2sZZbnbGtgZM7>>k-Z}04wlK=fX1|MqFM3n|KknU^quDq%qI% zV*$qo@lO`A^(#xI_;z0_*A=j-pIhmWZFY&b34BPHXY1pG(1~;<%#l||nB8fi?F@oV zA1j}IwS+{TNr|Xoe|7joi2jJJpq1xUBXC;TzxH6sdrHu9-TjFIZb~A6L)_xgghk0a zl@A(<JT0~5zJSkAA(GNmR6mxEd8-+UgA^!|so$8J1ava$;SAKWu4Cd9AexGa8+qgX zHcKc0mP?WOV)hQrx>JXQG%$%cqeM$4f+2<*AE7f~L4fD^PUncM>47#2z7ZSx^dG_O zLWiu!pI}m&D7v4aRP@Y1tT~CVH?!E8sQS5OLP^11lgk%Qv2@lOyZzoCJ{Mb*kHp4y z3#e0e?JlHEJqjQp@_ZW#*_ZqmAb4m0y>0xY^Y(IO-c98EivOOE$1ZuMi4p!dYsk4r zOSzm$r0mPwxw&VF;4eu4(ppC7n>dL}otF`~?|QHd{K#g6KZpjTjRZ{8mjN8=_%&>= zNnyA1lJ{(CoZX@HI~X7uP2PT6t*wpdRvGtdm!7GA0wfRvs_V1XLVAz}kZP(@QgUR1 zV+Hnan5HI_5xl(G#L1Z=Mi3;Nxfy%P5G$n{0x1~Hv5;La-Q!ar2X6zA5UzEcvedn) zg*O(+M`mFO?!a;ylt)G<ugkqtatBfkRovw!y}}Od^;yIIB0;3Xtn1bIu#`|Wu8_3+ zJN^GS;tvK41=`bdnJMkj-N9VbwAhirX=O&1;##j$ae{F$Wn%j;^yk4y*@ld9Ky>aj zb|18K3}gd~-QY0AaR``8%)#xem--__ZyLqxrZCRfLbYKE(A+}UWrakW*~|b5GPuvT zlAQ=oB-}Lhs?Epd6Jg%cL_TbOg_5aT&{C(n`1Wbe49Hmio|IQ9#+SNa>S2VuGP9S3 zcC`!2g-`6bW_L#v<@?rD1-3DFQ=MaCG%?s4c;XxCt@4---|RZgTO6>&K*GW{6pc*w zp8q5zVCh?acDIgm_Yc?O9wM1&Q!fHynR9PTIj4TmxkcE;vaMdN^TV-d&<)~tGWNTT zqM$YnAsY;)v*{{d_Of`7G&Z)|=|N*8M2rX@9sDBh5PydkY>w9jWjenXGy4t5yhbOm zzL?hYaGq_zZ@4on?Bowyr@bXwie5))J7jT}K9e!Px<9&+y8W@{t+qYEnIf+e=M99x z23%bVb<wK4Fop*c<p(~1{5}G$p-$YmqW_BYL?!ssjaeYCY(>2F{3oS*EX5!^Wby&I zJ1sTxqAn-+tpc_);+DqzqH?0fcl)TUs<V7vXVI?Nb(?<Vvr?u|1m1J<?d{4i<`fuZ zZWW?m-KKZhFXyD?`P(l($gWhaJ*%tHZfu@=G<3;*;OkOhi^ysTHe6JmDk{+4MJ=}7 z#Lr8aIxyD3_Jp5Yhb|PYY?JgV_CD*g)V4_t3pxHakCqpxt?g4#7#+$eE9#J!6@l)N z{qk><&q6UN&&?Ft=HH3N=E<qdIi>!gV2TNJAz!z(Ig6H>9ja|qc1mSx5gd)^-oz(@ zGi`kN`!`rn7UXshjQNE5g~vm=>A_KG%vLr;es^oq^8q$H4-=0xXkp!z_<(SwIND`L zt<)<*M8QTj$N}pDUZd&acu=;}a?ReZXKh?WECNedsOK&+OyAgUgq85LbmjQsF@C}Z zTBf*nqW<I-6j;=n5CT4DtCoVkL0<9$lB)@cQ9c5eP~593m{nX+n~2U_s#<Gb-C*W0 z?Z5uOpA?3!oy&4VVY;MB&=smBsZk>#b1po?sY^I-sbYs7kBuZlX`|C=GfEI92TO!6 z)nRF<#Lm8`O*AtfW97c?#)Us1Cz_gVk)juS{JmYy(NfmW_>}FDnF--&3g~^c45|vj z&;(O^y+k<wPwb8kRpb*U-8z7fX?O%bsv!fHkFOFi!EmChXqa^UnJ@KA5u3<-4qC~Y zqbd=*y70YDnbWU$yDJvMI@9ZKlwof<C735IIFmWU2r(f8&~uxHctc5k9I%0@E!y8L zOWST+X4y><YjrWnEu}tOc$`39AYt1KKG{SzQ`00;E26sA8pyzh1b@6D+P7)B_1C+i z4gH&@lP?p;G7_|VX27ZjEQx*_gH3Z9W#BAp9=N?Cb~l3cy32H{Q_c41N@4Ul?H=oF zEqT7QF^tl~?2O`}d#d{en?1jOJu<qrI+<i&GrLq;BR7K{c69Nlbf*x5H8tSGyMm$X zWB^S?Axx<S5^FUZC3%*FPd$NX*5Gn!fh-_&`!^iUN4j?6j;zKijo}}D3|<9tV?V)| z*%Sbr8ontnxugd7#P)nn1X6U~7{P{k=u1dpiC*@_+&>MJVhk9l&61!+x@+&eguu1+ zq{u>TypMnyiArSSx7F{OkAy(0CD(G;iE`f&HH}EXTrVkXePu3W71vgu|6|`<HVVv^ zC$^Etj}Jp`gEqEx+1{121DKG%_>IRZ48oMt&p}X>WVv9^+zB{wIN>?dOq7=Tu9`?W zH<~RCJ*%7sdrXM3TOtwcCNfcs&PFkL$1=dfuR-D(xf|sR8o9hI-f20U;gnh7r%`JQ zIu*Pz;_q`*K1LgRCuFnOuXmzP4HiE(4*cE)d5(>`h%)$g-)pI)zrFa=yN#;7-R**L zFt)2+_YH^?W8{7>3dYKZ)lCFnn?;q$$NSoJRD;U97`Zi^ndTy<URQ+$uIgV7q}nyG z&8uTzx^_fS>G^Dh)6ABHY&G$9^HGY(WQ2F9P+$u9u7Mw!A{v;vcwQxZIcUCH?wi%C z6H+h4J>&9DnE9+3vv(!?5`;A-Ut$h8KQnACdM2)93bB|<KPbKR$E(Twy_DrhwB%eR zu|X7e1D@_6TF(2RLR5R-`|j$PXNu73dwf39L%FGX2JtVj-%yEYITgdH3$ISLNDY-F zOKKFhEfsfzK$#0XBrQQdiD{db4+2@m&!&@omYoV&qNa*bb%|)Wty;X_5g`){<{Chz zbeQ+a9MS-(iaT8EHW)aoA1tLC4@sq>EM&Qdmjj2=e9~9K8rQzr==Ey?TW|{+yI6q* zCp3QMq|PdXAopA5t`Az3=-_k&UbejrU*x9(X^+bSsx<k5rLP~~`c>uZ1_^oVz!U%C zZ`~-x(3)s{7JurjDkUJP=V*d@eBK?i0p1X>y!vK~feBga?&Atpp?F=SH0t<cL-MeF zk;Waad_6@YFLTw%$KhkAF1voa3*;}!Oz???$#7W_splNLMy6A00nYWOl`R~OJccFQ zu_g+e2V=%{YpRH!mnh@eoVA6a@Rm7;N(&XIDcOaZu^k)n^d(d6hdj~(XVbU3=4AR# zrqtl9IVhhpljnhqg~ENh;I6fX>k{gtc$^SZf;!8o42Z9^??TOSo%sFA0>!OgtaH%@ z2|~sgR@WihG`6#;EG(<tsv-heIaNfmS+!Sh!gJHrxt6ZaCr1xB@Vr}F*j!^fkKS#$ zHB-tLohK2h17jLi<!Fd=emIVnxuK@ryyfgBR7#9f(%T`8N><*5jln@O0!28@Rq>dF z^mnT*%_l>agHZ$T3W<BWD|-`q5-`lelXhM9cpsE+)WmfaE_1)=LL({n`jPOcen$4u zpvS47YS=TL>Uh0<;O$71UX7`3O?=znZk%KKNlG^ga>EY*PM|xh^{qvzQYv1tz&VDq zg%z|x1L(&Bwr;-}OD`P&aMFC}&`Ko`aAdT&CTBW56h#djG34h8@nZi{D6#>d_~`dH z8-jS<DArxi0gt@?ZF7zP$zs!d7sMXbF2-kGqf#hTQ*w{Y7SnzIF7Bd64k?oOwX0QM zURZz3F0UGJ9YeQPWHk~y<Tgi+Gi7)9QuBN3he|uH4D-k5Cv;mVt6nJK`6L<wzj_y% z=@%Gwyw{eXG}E4>p}W%uIs7=qYdWq~u25d(7*b|Et(*6`5iD0v3M0p)@uj%yLH4cI zxZ`B)=d(>^dG9(^Pw)NiG@7>Jx8k$Pc%(O5KFdCLmk?4)X^TA$H-~8(suk^A&wC*f z8$I15kmD^<^}TjWBR}um9KbU?%ppD+%y8h;Zv2#pkTl(#etF<zvvzY>_*lDbF9J2t zHKhwtNLPfb3np;qB~ovZ=ik*%3)xS;obW@U2!g%+8?TiT^U8E#8VvU`Z!HYHF$W1s zG2URz*%=y}6_g-R9YsM9%woMDX)am^6zs5>PSRww^Usd7CtD*-W*0dMJar}p+@kWF z9z!&j;C2Kv?ZNai)#B8yktj+et3KelrR{N`UX-4B#wn0(DCg$r3alS{w9-CjHVp+O zI3pH|hWKS~485@cO`;n^l@eJ)QlAyRbDj$LhdOFFHEYqbrT6HKvn2VZm07~6>tho_ z5i7$Hmb8#*Feu?AE;cI*`D#vR)xQj%3lf%Fr`};~QJMR2?Dw&J!JK0sU@vTuucIZW zErx5+DF%r<>?WREFs4kDh;w1n2hITWTNOUVxq8NJGmoZA9%UDxRX4gFYpC99dCt!9 zU+kQbGJs?9Z+v*OWW-4MJI;pbC9kx2y)CG)81aQS8;Ct_RretZ-X%PDF`%I?+k?^q zk?Hp0pb#B{hnE1}Y%fgiwD4r#X=H`y5F>KwY5ZawTyDRHJ=JG9NH~QgnbXmAPoWhG zTs^FX<&8!RfwV@p0k?PU?aU}NWxwlz3DjClX+hO`p#lsRc@_Xo{q{j?vYhNRw`n^i z$LH$3LTz1@{b_8H-n_+U)ZFFMeY;ipAk|l7FRG|bp)_EeJNfg)cVGh@lR;GAAEH5G z<9-+Kb{wHt^3y4H&`^WCtM+-y@@p2*chR*s4aSTRmw9t8+@TiJ4w&_UN03-XHyM!; z4;7r4L%_Peu#Hb#BhI$s$1`Rm=wRJn7rHnwsoQ2pzc;#Z)#nB!+GSgNAlF%;Xk7IH zHA%Q<&a&~c&!U#KJMo`_LO|5f;w`@9X7^A9tl)!>ren3!!yCUpX&FUm+U!~=X5T5M z)>};3>G0#HkO;)bds6H>z5L=~0{4<+N*cnC$2z?Ge%ln%g>s*B<#%us&-9iI_;%1( zP`%jo3CPk_J9rOm;(jT8@f}?0;ZgxYDk`m4HUWz9%K6qAvtMHZncs;%AWV(X*(<SZ zOze{Qs^?{%kGq~-52-7THNhPefLSm#u^zmhjZi?(D`#643)fqjyF2&D7m?D0&v+-; z;`p!yGHuK&5yaP^<Gv5f-d{GcduDb~1NOdYUNdB~yZ$cjj5O&Uqr@IJKZ^I;>$@GH z=ttO$;IP>1c&?y}XJX-ZG{hH+Q)smf0Nk57#qa7=fG=X-1c7mh{<~zSL{`WUV@x(P z$9^vcFc_0KUs!gh!H9>PDNLTcmmT)tK6ldn=<Ko@KUJ@&p(}^dRzl=D`C3K2hMo<~ zw$FUjb8i?pz=(JbJhIi!BG?=<wlQlxsS~U<NQk_uKN+YHqZ?vXKK%HEV_<%kg{<$O zty+{ELTSig`3|h|`fBT&jsk&RrllYg)!D$KKgG%rMPF|8vHp5|O1hD6xi8!S7WD*4 zhS=I8)V0UN+NS`9O`s$nnO((;H^P1hF64w3mQ)Q#iyPeqJhcFudq@K=a=(E9&yFjc z%M@aG*>Anm;Mm7oYciy}j3-=VI2sdKPYCAZD}5BIM#iK*y!qxc+w~~(rT)*HF7tU{ zs6j%`Uc9T_g9wruq|6=?6@A>OoGVjUwy@N!P$QRI$aBr|x3OroW2w6~YPup;OJaB; zxqnDJve==P_y&Wv9mJh94JYXAIUEY5tjH2JTOI{l3i5J$bZgvdQ!OnVI-Id>2@SPm z?J7k~l$r`!=7fLim)cikp1yq^-83)Q!%SEhS}wORxIJ|Ww^_QvBXGZVC-8~g_S4jM z{e9EdCpD*fI0Y0!oyKi1V%Ideu0I6J5lH3MNO1hyr2M6gF#a+9@NIH>S@c&E2r7L> zee$LJc|nV``yug5=wT7?GfD?sG*fuoxit0s2;&@-XLvmCT&LgVO9FcfUul6aB`;d5 zrfvmx{s7hC-{M?=>KW_Rz#w>Sv5K)#g{Z7RVs@3oA#D!SZ{PWP!4GZD#)w+6Fp`~t zw6B5OA75+@JQsHM-Qp^!={+1%NyF5z6c-^j^{KRiT=@50IO=%O=7(c%2?&R?3C(tr zZx(S`aukCU?#5c%qQ%xnZ;l_ZgG7SD9#=#gpjGcRo<M$CjgTmT0Tx~W;&eGH+7L1Z zYq61xpa9%d^gXoSU1g;0)#twI?jm?ftYGy!PwdfQD?cr-z&bnlg$y$E9@3&<+nn&W z##iG`$<?RJBjp91Y8(nBdMdlB%qY(;EDuoatJX_c4LZ&-QEZHxg7}1`<f1@qg)eT* zUAH3E<>$f^QxpWMFHiUe2C%+w0PWNT$BE`$rs&&Vs6Cd8@)UevQuemTVz#^=t&aKz zrwZ<QMP3pV_)Lm);1v+FWKkD9Ubm#ho5xx+;*fQ?REc7Ub>Av1!4(XwsB<le@<iZ8 z1kSWjTPdPmsvLsghXOh{2jua;(Z+*Q)}5b^X$TS4!A}(29lA1a?;R<n@gELnE`Xvm zl5vCe+Lml+ag5L&ssd`?J1>bBP0Pm;7fQ>-@^T65Z$>+y4m+lwTXL>+mJC7*D3Ygd z@eyA4BGi(j6{hXaWL51>DYLK(1E8!`Uz(|<AAG<^2X!kz8}}#%&D`u5oEW~(*q=%= zM}EnzTs{x=Cu%~X62HT?pll_r!<4IDFlt2%-0!@@*T^EyesH5jORU=G4q_w6$32Ut zCR4X(tL47bW+^^Ha4f(k+0_^e)=aENI=6uAyMiUXMTFXj5HGUP{%duG8SlHPx*WZt zfp4Sjj7}q0qQNRQ*)J&>X`M^&9SNl2#TMp;9yg;3Y?Np$uPHIQY07B8^wOl@`m<Cw zVhY&eefRxb1NN9z0-IoVGBM$_jhzmJcWmRydm((k$Ngbh%appsFl$-U*<v;=zBv^# zf7%~L<Il2*wN(u!u|+3(Ct~`VRMB;NBE$H?;tkzv)j*!N?MEKp#Jt-@XlI-%{|PfZ zyrLxlUui01Yb9)33fp6F+=0~FgjIyNOq9Mth%bZ(2X7%43fW^2t9ewt6M$_Hx5vK^ z>1S+hnAUo$zkwDhL05$2qs>=&`LLUoDqJ7*gq^*w344k23h@Od9G4g>NY?JM4vmby zH6*ywkIdf#Ybm=d=wx-Peteva<82_X!YV*0aaobTzJ=J$I<UNeh<f+1q0Vz_<l};2 zv*IN+kedxLE%-A?@W#FJU9^yZeR^w`-@C$Ag+c6)-shFg-r8wC?0UD*$Whg}UVT$J zu*_#oBbyAVZFqY+hT=3ncO$C*a-zMNja_77auJ|y&?|;$AGUjtEN_&`H*7#@&M$*j z8Fj><joRPexCZWuF47`c-zzC6%=-hph}8(lw96%OA&dOPAzRm%XFEq~fh<NSZw&ET zn`T3vTi=t{583a`^_OO2cHp24xry8E*(c79jnn57vH<ddR(JZl{<cO%9U8)=b8f4y zwE$EEJEd@&>(x8T>?3uxh(msq6!2j~-&q=!LC~pOEPINQ5)W4eupE3rFeZJU7|%<E zyFKn)Eu0kC1z$sLBSRBH<fU^QGre<ayAUPhoCDfEvl$$Y*0Cvhi$JX8?P~1;OmD)Y zH_}WAz73Ek=g{J3p_ab*E}=Xuam^r+P#_+|r|%2#_gtvIo~?!fi(_XCqb+uAFC$g7 zTmW)UI_VsD+E<ycjYF{=`i3?BXWR8Nq9t_1KFMn=E_B*losr#>oobq44C?UW7u7HK zj@U$~dAHk>^d*Oc8;V&YV~yS8fErZ)M8SiO8U?UTLm2qG9u6_|#>jciP`B_B#=e&W zc4Wt0_vT`0_L+{Hu;J}olzE_N=ub(?k$|(*-%EN=v+#k!91sH*nrvI+-;He0gs+-K z;x@y4JK|@-9H{AcAM0WaTF!k2U5q+YXK}qIkPrkCyzhFacrdfGv%^MYQBxbXkbsu8 zCRc&Pv}H*dCJr%0cg(gMBREq|+dzz?`xTyni919_$llIw!sxkU>j(pI0JwHGZRvoF zHbhlm5Vl&%H+OsG0P`I&c|)a+fO#U{_>kiV)NODLRti7#e5u4i-~G}Cexfb*qQYW} zo&f>jNZO{Bx`q}NA}O#MH|=-fH<%ZgEg{2Gc4|>WSFQY_+RmY_d@C3d|B|zPWfDgj zuw_Nz%4a`@PB@EW?A@-{*7nZU%opuiUlijzOPbvYP8{O1Ok2L2Dl%frf)msQRTG#H zYV%eF*%(|e-yo*zET6-x=+mw)GG-d)I+9OP9kSRR5%xg_TeSLl@LnIqspP0W5F#VM zxUFSEtf~9>oE^;jTpwQL;@^(?R2Fx@S^q91f>SD07b!Jff_;8lrMPN36Pf8&KlNxl z6%LgMMs%-c8}*tIHNpwE@Dqdu3vQieYweGk1DpgM!<s5iG)yo>8lJk)nWk)Yd|h?? z<1OdwP`EYR5`%sgcR|utH)9MTckefxIT_K<yxorxA_=!zksgg-tNZ%=j=`qEVF&-U z1blr<8FHi(%qN=*@$&?9G?NoU4>PWP&_J(lZd*BBe`=;+yb3lMm&mMw!4z@|cS{1> zG^0=wYPayjxuNAuf9B$bT8e|8HV4x-wdl5KMoO+{uX)q&fZU<8fO=lwxy9?jFz&sL z(wevWz0HSz$Ac8?3wJ7NZ>y;hd0L=(fv9TU7VyitA^cb@cM;fWJbnH*CXp91ZJ$qN zqAnPzJO>HJ`63e=Zhc%CMrm)8M*BQ!VwQ*TyXft4X8-qE_vf3pY%7bUdYDR8(f7>X zfrSV0l1Vb2TjN1Ze9w`i%t@*<f_<(#<w^3fV6Y(nTDj_mA?b~IAUNtXHLxt!#2Y&b zs4@aAc$oirIQ(&ne0xiA$4hJ{o^z^@_yW(0NQ@HM=C?_6WtJ|0<l^~GUDji1nf>=w zkGP1}W*0h7L*g5FuxJT|yas`qXz}Uw$u<i<tajp(ojwPm#Fsq9p9j=2&=B1eX`S!0 z%{rG#P!aD9VQUCe)!PmN`Q~|Sn?NB+7T&K9dX$WdFUl9y#Bp&Ddz7=Cri7}`;pe|o z^D#<<K=GW!361CPl^OYqEYtPC8SlTZK?bI%lbn0M8)mN*>O7s3Id8D3=x!;Lx3bV| zH5lCqKB*Bsm9E%h(QCoqUJDyUzXo((iCm{HILs4bkFSIW&SFT{<GG-+{DDi0y zVKUilra{sKRwk*cJiYzm{0<;h4xJstCnBaDYk+JfhCF)^`OPLf>A2H^%T%OtBV(hO zrYB0J94aI^0y;CTS($tP!u2*`lxB<x*4=tB_Do|UxY_pj^?I8I_$40^{9XcE46}nQ zS@cwGRYk5v3XiEOr@v(k7&jpByK2Ww@uBla{weof*Z2SrtBxN%joNs(TJs_bJYO=_ z{3*v*AyF+kWk$`E617&!+Wd#T^&YoMIIFnA?rOx%Vb@~qsjbO~j(Iq<q{_o{R^+;^ zw^BpRQ4_aT%qsAl8WmK|-{Nl@!ceXpw_iLJQrjN246PTnBe&K7HNBhgiyT{eoCsdX zmCO{F_fDjiMoY?6g7_Dir(RM6PY;ATe^&O{5f;Yw`xVHzX~(2At-RvgSmR%mH5P<o zcFf4%WugHNU2Sb8lUEQ{E+%v>L)-Dwiat<CwYTUajsTS;rz-Ff>q>*%-C3U*Xn6s# zoCxve@$=hrNJs+b1VZEdU?nFPG-~`S-ti{dF#b+sqS56R4*lmQ^Y-O$J=|z`5Tf9s zojhOzJ<CoR(k|W{^Jx-1UV?jUjzWvP0d*__^<qx{`Y!ZXAbH2mPhqF`j7&g|@ZStJ z1b8YJ@}LGgfR4!ps0CY%O=Dja4_Sajoq<}>y-IsRkm@b!B^4Kd4w>(B*Tv7ta=q#) zPmKOu#!wwy^RF7?L)SqW?8%V5YpU72&Kiu=;6eg+#*S!h8O0kF^IUvqXGlA>R5wGc zYZy(d1TF(qD^@+Gj%YOgffHhs`gA$$X8Vi6rSTT9w98qj!*@TsA_8J%rvHp}(phf4 z;S=%AK?FCHxK)x{O!t`=(hhGUwd=cjlm}UEWD<IDc5}Qs&c)sYnt&vypuj+_|H9qj zkgE;hi-`Bf?MlhJ%-Waw?ZsKg%p>HUkpa;eXA5uLCR^YAcF@{4W<OHP)Rvc?RaX;{ zzb!GPqe~-$ron+!kTF$aHNDsH#Du*xPAdOel?^hKx6sTB+vnmAs0f?<qAqGY*LdT5 zLn4!tbRE7PY0M#uc>=fAaF3e5IT#fwI!cT)wTB^qYSqYjyPU`ZxP=pG%1$qKueuXl z1=Q8$!4=lX_mpdV*IEmioPc_9!{jRtOK?0!4a0EH>FsiH#oT~-M4Rh9q4XC$F~WoI z#rYltFS^<ECZdDlPlzJzNRPX2jBE1_zt8b4jN|6<>=Mw_o>jHke#N<?XlTwitXHAz z;DoTf$5YPMjR$-pgydS&Y9rLWh1||!nb={|kck9^Sjt^^en8FN)HTOTKC|Y~?%YcX zgj-sBXog0D&G)!l4<c`7>bzgyRQ1O%T1Ir$-y6;-d8RH{IOVEz`KK&AN|Emv-gm9- zrrWHtch^C20Z<?-w7pivi!D<niY#R&j<?v_je$7M0R_q`_2}q>@%GlDm0<o6*KUo~ z1Sbut^+rvF4J5x9A5Xjd_99Ea9qB#PYih<euK{9G2^1#hH08+_YT?7_oH^QwvrQ;o zxuNIW4e!Q865#ud?SjZdxk$?q=lJ_`L522}1Kg?#S4*n@`0(mvNCZL;15}=GfR_z? zEIbF$uCq<ZXu*mU+o1VQ<-=B%E%#LqNt4U~PPO&f2hLeJ@AcoiOQ?4@&ZWC{rE3UA zrsq6Xpd!E1>Hs&D&p>>XOi6<6LmwGgMS4EvJmK%p6&7xvqR#ZsJZN&x3A1=4@DW}y zmHw?OVhhL-<y<JS_HDCSrcQ`Rb!mZGiNV;S4HZMav?k}Fhd<;3aPUG6Z~l<23n2cK z(eRzpjjfr#7Jhv9Cb<v)P2asGEx&-Y4RwN<n1&~_yFeUD5Qi4}VozK8+x-SkS4sWA zCU10?*A#v~$r;IZVOpX*`Vx=E?cp@|8Bajj*y5@&ToQhJ;&;!`<mm75=^+t~FsE;x zW+1_X7Tf$-8@-g{SqVgaFNuQQzH_~q4jSjvisdxDdQbXH>!w7P0rKNVXN+Gw`Jo6o z<gwfx2Ed!;QV>==bET#c$}!M?7R2$qPcCY|7wR<;;fB-yU6`Z#;%d^=Bd*NulcdrH zbOpr=sDAWbBc#=cYncbK>V|aB1#2rUaP$`0LEe@Es@KhJ{N5Op*X-ZQmpg-SRMxGB z@xBX9MQDgM;?0=r;K2@Gf(9dBg8($X$a$KN5W5P}_*}d*03gthb)}!q#K4^=03A^n z8vx}<n($)^?aV>WQC&N?10_GfKi*R#6LJCJ%vPEoDdYW(^7$ZG;(SwHA~q_ov;$yM z)xyFHdM&8=btgmDfDOdTfa#Wl3&B$*0yO~@wZRP}N?m5!Pys_~gs^Z7e(c;}#jBJP z@}jll$1>)R<gkKpd4vT@*4rpTp2TCA0FP@uIuW0I`)Y@OIdM8f^9*5zR2S19QG`uJ zVoR-RC)UBHJEXKHEQZzNh;ED8l(Op}{ArF<v`!{#Cgq{>#&5-zplmT$zg|842F_7s zj_4B+_`WISj%+*_d%d1)3FL^lZL@`Jr*9r#C!+Q7KC($_hwn;CfAYv&iDrF5S+@R- zppZ~us{H~81Es7?-WSG9l9F$t9>3Fu2%e;}o$}tTwR(K9_}L>y3qPW?fQf`w|4m00 zck=TqcRuAbo)hROF-HGf>a=!SY`cYDEGCfvMz;BUe|lJ-({yS#URPj&siY>oMi3BQ zDpxT!#3I=R%bx&=n~bE0+MJN3XPKp%c{S8Zm0S{Nlx^yOcsKD=+&FV^0wG>#^|91^ zdQmu1V|I$2sO2R%8N?)DM`+z_u_JOLUyhAU58GY|3>dSwc@PYae@&u@vq@QzU6XWc z2t}?fP%o0X9(0_P)|CirCcCp1Gu~4;SW?j-x)M|}ujFoJzx@tNWD}1UTF}TG8v~xL zsfk(j9Q-&6b|bS_IQ})6k%OvO4G7go_aC|VE;4l|iYt?IaiXl@ETq1lqRArb-<6Gm zh{u1%<|A&Jl7)p7@XB6oh8)I_+opr*5^zg#p$KD$H^1qsHs<$G-XhFbRa35Ogy`tU zL#lj%s!_hE*{EkNrj2>qvzl+*MBTW_fchiGFwVk(IBZKCp5GRy=3`4%s=JB{>yL9T zR3s-ws|_`#AB#n!AMO7mZ|d=|q@#5hoIxuaGgPG1xQs{^6)>=Oi}mkHCPd~rslgrO z5^NYb1o^_XFACNS))h+N``>4vUJhNo`f7pT`j`9PQ!TwGm}zkZ65=xmW~{jh@Z!mZ z<=mDi*4;oE)SFK5BdXswRaSxDd^0gT<*J*8)XsTj2aPT~I^Wn9zDSr|DR12L?QqxL z4*pj?i#%l=!fUZAw9~+D8WtO`_B(G9rNDSWB@V{@*=xgG%S=F~*soDu`?mF9fqZ<} zAo?`v+NY`pf$9+uG*puOO+aR-Hl5RTNIsPl`P?Vtn~bHsp0mYcFywuY0yff_&mkeS zH&u@zDiy&QmC2bd^GSu%K?9rlhd9SGk;yuv6Y00ID*99pknD=uq*_KUw3veM%LxBH z;oNuLLeNhHQlG(2ug8rZYL8#fRN*(yg(xn|02HjN1S~w4rguO)oZ#%>;D3XF6pK3j z7^0QBWRrSh_Q_IClV>$4=IwE(eh+2bet-L{CMo?i4Wql6-Bwd-saX-2j&fdXV6lq% zZ;OG}iSIr(E=FUgBcc;~UmzUs@suHIkx@(E2Dl%DET<+j4SGe&p`E22zV_n91wMz8 z9kj6W3!ASRJ*Y_+C9EIsT&P8#j?Kn5|65nGIVi5mVcFAnLO^!P;#}9FUH$1!f6BNW zi(G@D0p`j3%gWw+f#mA++XnXJ;+@o;VoKf4xSN3~zo<HM3r%VNtR<7cOvvozoMz6e zqbPZqc^k;La%QkY<c0nD6;Qik{+^fm*l|qVbwsmWvFZV$4f!6KJl|h1rrnBD3E-0D z`d5SS7tfL<6p$lXKcs2~Vv#^m-nAXu=5Jfn;?Ge@CBtrqt8aC5q`la>VR-DpB!9N7 zzK=7aXe%Fv)k?&wt??*(wFVMdp-wSPo5C8fE<RLMufviqEOc!F%XV@>->@OBN<tw= zG&<8YRL%Ak601o`N0efWWD)p@sClNzT8bXniC46Z)<+;PI#F9el_024w!ETn5K~kV z&fH0bTzA_pp%+!ixZvZ+$bpDV1^=5||0O%rO;)s@?f)Ek>6n8`0gHiSIU!~M7F@G3 z#Or=NEx+3D76%4uLnL$&w=LPWOiLB^?UQm)xqnv|bUkk?|3nB}5R5?tGqG=(8kCmY z_*91M^k1%#v)fSTmJ>BgB#=@#;pSeUUYAi}D=iz5{;#Xh&&kT(+g`{1hdu(YV-^mk zQPxRcu3kgBe|`gEWGubGHuO4O1=+xMy{^N`lB)8#flPJ|6wX6*AUY=0w1@_VWHY~# z7<P0M)K0ssZ|TG^E4pT2Bam>9JnmLN1Rmm#ca@hAr@<T;1l$S4@owV;xaru=zsb{I z!eKV9Z@%tRzF6_r+fTpj+gvQ_XzZl%JSX;W*5<cAsU=ukHwIxi!>^_74PDPe5m3g{ z5D3c8EMeHll)nZ=95NcCIKT$vDSLx!f>I8IOGJn4Ds)VLBo=^X>G5opv{Jkjw?qHd zKk~r8wJe4VhQqNrp8*x50L*6>=xS)x0+JP|Z^#?86n4<U7_Kykd<R{X(&{M*bU`Lu zT+?{7!_AH>s_?5kkI519=hyKPj_AtCA^*_TBTA$UDnvVKiSRCsaB>V^d*XLf{*L*K zJ=D_9QPXC@``4Falx^HOTQ_M6{_LF518SQDLj#z01x@%@AIGu_XhcVmBmNFrJ)1)# zK-RkqG#c1d*8dM}FxO96m-u1ldpf%TalNf?anXbX*ND$b^=L+dqpjh0u6+TAxxeqM zb<moB<kDVub&dc@3iz&a`mLuPHvhIy{Md&96+G|UC{cKvh;c=kOS^Q_+l$iT;eEq9 z!;Y10a>V9qFmz6S^=j-&P?0#H{5y@YoS@06pxG-e>ve%RQp?u3Gh)+{GOk3(B&-4d z7CXl*$F2eztm$pboqWU^?QTnZ|NI2Sa_2=k4u~!<Qi%vnys2LVY6s{lnTHYKDIeYm z9KP!HH;ek0kwgb8wj;!O{fCNemH~l`3#b21!2?>y)-4ms$rN0A{R^K!Uf9{#=;YH| zHdloG%e(6+C=`)UOQ{7MkP%r-PKdLQeOlDR+rS^_3E2HEgYtcUIY!iDua=@jHlqS9 zH)vme1tCtfXe&O(m18+Zd>UYo+jIp3!&viZooMx^|CI^<-G+1;XhX^p0MuZ?rMIep z&a1c>?78p#HM>8=$d(gb3^s6FG11IBzN&cr86l7yS}lT>19bjVNMsN(w2d<c=rAeW zk?^vaGxPcBe`9uO{dYhn(yfsqFV}rP>9ad!F0*oZw9;zg9~}C3XP~0nG1l~GEx3jG zbeEc_+w-0N_)pkTfB~7NkQS`8EY}>g1x!VrNIyo0%<G<_iaH|olExaN5_<c#70ert z1EkWXWq1|(4Imi2ybQDnS+zv%|N5V;_V^mZA)vPB$1J!0u5uzoFcE$4{X)+t`0$Im z6Qh8(ZQ<~V^A??`oyVWffMg(S*5>yi3wCzod@>=19mkvpSSX|=wo(pgqFO+sYfyx` z4Ftr5AD^449ygIp)29Ph-jI3E&^O|;+{iJ$#6QqOwt}Ix|GA2N0q4)~8wsSLLCJRp zjbUU0D6{OfBN732zF?mp@T2(<=>4jl%2{|36^}S){8}|Z?8io+c?i*}lZ2o-!m=q4 zj1+Pdg#C@YX|J^e&6#c2?hruC3s5`1)GB}$0isd2g2N3UTfAldpp~Y~4+&w*0Hm|& z3-}uUY>BJCbUFYW&<Cl5s@gaMou)93$sdgpV?HZ}x-kkC&8JEM=vE2z6E+H37dNOh z2DlaKsHv%Ss>TY9cRm<OmugLs!k$<Q>UR(}&8f=A<lmt}ad+-<C8RIBvnnA6I;h%d zzQ<T#qdYH+*9J0~Z;R<<g4_TNVDR=Iub4;DRp3git0mECL6^O=Ab@S3=?;GVj|Swg zO*(Yi8m`?KPX!2wdcMl4UCM&tFptnBPM|aJ^8R5^86LJ>Uea$;>CL9Y@(;E6*H&kJ z@IgqGBU=l)%<1{Z{vSRWx)Lj5Y^wq|QN=^YByQbm;!AY^5Du+TZSwsBW8p)IUk?)C ze40O7yAj&6LtZoAJ_`lV%Y|ExPpaCU_2n2A1UyZedR^O?`QRMG+`)9uIY3~#(+U1! zR`;eHXll7J7N5>`L$&T74a8rBo8siDtoPa(PN%+U{}1K>-2P)fhNRD?Jd#TP>BanW zWj;iYbNttme;ilx3eX>W_}iy5|A<xp7CQo__J8!ZzG<r_O`g?s-|T>my^Y(;@PT%E z-ZG!NSXwVE{D9Sxwx5fA7=&{NNp{Nw@RIn5!;rafsa>AN==b(l^M!bpvzl(39bWBn z3P2ak_ByhN1HV87b@BsS|MF35-xd7GcK51Zo3$^#^~A!R{LAqVFZ{5P4|k7B2uA>w zhf6h{A=^N2^XNSnlNT*%{(oXP{$xeCowF=yXyB5<i<Jtk_D39PtJJU3=I8LCHOOe% zLpsIsZ42X)r!ld5iEy0x(2uRfs`TM0vh|vaiK`W;s;|uylmB-aGk}$0G@+s*<)qxm z(l@)b=tP7D%)VW1c|Eva45NScW)~0<Pu^FY-0paDc^th^Vs>O+(m0T|EdsQU0#3Bt ztnt<Igg|NZ)fDj`*7Hv3kNUgY<p;Kx5~H%kvA5bin~fz1qKMPsKf01HC4NzPg&99N zTdH#;Q*rZ0$hyKApnI>zi0hRSFQ-!1kEMP4veDpeNw0gvLqUtj&AJJIbOY#A4*v1# zUuafQgux5QHMv|K7N=kO6B;3SJ`{k>9ZM4Jn}LfDl_CMV@lxE&Mx(doe}CyrK$U77 zUcI_a*Z{-->7vt*YkvHY;k=!tb*T|(QdX+=^MeF$;P%4R70pqNPyWK5SArEm$guEg zSlQv0Ir#Y>^`3xmMMb`0n?+Qy&ZW;xe_lc_*JIRbwM=#D*&k-c&lixW8n<nw&{2>@ z;C?Gadr{zEjB;9T<Cv;!<EARhLwVX-+B5W?#~0ZV7=IYj`2gT#t!ry|`_~)Y4{6_7 z3Q?479JB4|%ync;qU9Ek4>N>STfY4L=#O{b(LTQ&+E`k*+{HtCg{1M}yg5{IJ`0K~ zrg3_Tq+?<m;1uZ}25x<jK^h@`Gp60<f93FJN{cVK$BNJ#OBR%&_nc3*ma?Bth8t}K zKWn}$D5bQ1I!!y(wNuTXqHSI`{(0p;7WnVL3tafOtb>nn67xxEqyK#e_>hDI(8hnl zZ`=CLuO9J-vw5oubn2&#Ne}%GecixG@3ANfbbl+2X#6ikr=OB$Q1pcb8c*rl=l>`p z35a>)=y&?9g5~%9j&+XNR;nZNYfc^uIZfjIfd4zmFzyn=>oQh&$g#0Y+Oe@v+OaE& zV>yrvE%z7o>=!n|aHII=0MBs$!@$ZDu5azh>qj=HYYTvKu;vl2$BQnF17mkDAFueU zTc7_xz{#{|j!yAr)R>#a;lVF4m^iWhM}gj(twc7#P5#m#NWOjK1r`CT-P=@?&92`( zLfG!Yj=J(QI8fVpzRr*JND9Q4omBsT>fkW<IRf*XmHE?!T?teS(!!;`xKf^UW}s#z z+%ZBPV(INM+}bpfRJ0pcp4+5Y3NjvH_s(VaPfpsb*zS#njLL?}=Qd?DI@sQCbYp_Q z0etSE)7JG)K?T(xAZolc2M^p>&@}*j)r}Ivd&j>DQ%B;Bl`UoDvdSw2(_@59r(-HC z<0w=bS`gM~gXv~}pO))$_diEA_n5A?8@=XYYL75sK3($s3G;F5d(>0Sn@NJjh?Y^J zQc+ai!-KYR%Tqq>=Z5XpA3qlU^F}`0J@z5m=Gz73q6@FSNOtdy!xWZs6W{hq1@E?1 zkN)2NYUFGr!}Em83zd46;mtUFwT}L#zu?6sm>dc0K@B@n)J1C=Ej*U)H&f{1awwqd zXu6zL*b>qS`PbZ&57*8BKK%5Gj9=~rhP7Bx*R@z7*U4CQr;ZN)ZewxM$x`Ev6C)l+ zV56umOV5lX4GoRu?E{-`d&Ux9<)l}7)$ja?b+Z&0!j?!Ln%1ZPx^R<YFSy;S$2f+1 z&5qrX+O+ieGT<_LpeW`4>Fvtnp=|s8L)xemQA1K$CtH??5h_czu|{^q*q7`(sR!A~ zzC~m=gfW(}^pJhumoX+G+YBiThH>t}`=0ka=RKeE$N79te>JZAzV7?FuHW@rzt{Kf ze|f95M=j*$Y%p@OKWq`86aijvCG!t6p93tL{t3lL!D+xGRb@dlE<e5fd4kU=A6h?z zdAM3wl75itQ%ipqpTy99>|}|Yp=jG~t5HLoc*E+dly)Z6R)m!i3>@R;%IW?Kkk6)Y zF7whfBd%)u{(e+dUFm$V+s*4n_iLTP^S|cLNeV5trpp^~WVPOJhe?mS$%)m&vTPkz z=A{fp1QQR(nxm&DTIq$i=4n1v4X#X;j-PSZ;%CGB>@ZeL%3~CRb*c~szbir=SEclX z_)Hk&a^Xz&m6O@+5)RKt@+o5d;`=m=hLif-wG#VW1$qOxNW7sPS67Di#>MZ{`juUj z#-nBO8;CAR0`DScqt@!*#8EtTv58xkcfE-dU2RNcWe72eiuz!T@Kr3{(N|cS&lQkI zr+AAIIz(V0ADC-n`QBpMl{qcHQ=c!Pe>grxm%FHASa;tov|81#-m_wOu{X#JgM<Cn zl!H%uJ(mL|6=xK(+&)D`&3-vb@vf*K(-UdlS3NqU#tiSe`;s|xVLmRiAr6Uz44T#R zhopiCQq(;p%JTv}txE(x0>91s?ZntKMFIpr-=eICym#Nx3zr9^ZGD1R4zbRy3J_+R z{RAqu1Q9S9P7JNebIDO7rb((=$37Q-*H1`m=a6mmC?4y`C04+(yP3HFMBE0J4}b7y zI4hKxcF<imoVU&K|I8|A>QU{8#lJjN@n~@8*Zf~Zl;H0f=W8mpoed}}L(RmI2V2(B zGX`}zXK+_Qg)He}hxIU;X%^wkSK}&8T(*?&i`5%Re#M7L(}4%>P#k)a{_xwS<)&hy zQYPFb)ZItE-E5V<4kP^_w<N4c-M?^LII-7B7fM7ytbd9L-I0*%sy{_s$N7uzwy7AF zzK(x~+MF6;83{-#(JFZVt3PsWnDtJ}SF)?kXV~}vtb9ZFd|QH*W#6TkKLYQZx4HoF z{-Qg*h$>42i`#edRud+KshNhgmm+S5v~`)&|8)QKPI@QjH%@5#ccdSAW=SoYp@MPc z7%r9TM|<Ly`a?jJ@5XFZpT#ZrMdH2KdCb}6c`o-E%oaSQs5Fqb$%H<f=4erS)qZgb zlK)sf1twtL)QCo*btT5Fs}#Ucq<rbkUABDWifpE@L)=Z<8fG2I7Qf2%O8}fY@1n!n zB1!U>h{yJTWA0sZ_AG^bhjPttjywm`_82paY>IWP{<6zo)a|lL<qev|M9$*DA;*;6 z0%m4-!7of-gXo3;eTxK%D3H_VE^RQ|p&b-KwHdJ%w=*NSV(BoqmZ==C7uq3jZOO40 z)zfzCGq1!QgZ3e1aWe-QWmdvl%4#|XtV;`TP|tU(XEPxBvQSYlBl1m5Ii{dmAhY}- z(SK~kQQe)V<_$UQ0SIa`0coR5c4Pq4HPSb5Y+;&VwYqdSSiz>+@@xAHjd)}Pa(l-? z13~>_@)yiso3jKimVZI&mWz><O!rLL4W|l&GW1<NIRvEw{cHB|p%EOy=Ev(s2nPpO z8+^p8fH>Eoh0(|8D5>=8W+J~CO5%K=JW*k8`Ye@C$|A$~#`u5Pe&0q}2lTFl$2;uC zduXN`F+8N@@c<=0>*KfJ%UHFL&t$KDyhyRO_@rrW&ut|k-f*l27{4AQ@^dhI7XHl? zbO{3QLL-Q&fMs~qpXacqF!*2<g;hPU{j2yY2<_*L=joX+CN@37uZCue9eL`2<37<C z@4WblUEFIxY|b$CtBuc4_-|~K{dw1DJ7=pQAN3}qY5oSW-uLXSVbFfTpa{E?0$lfR zY@~g=hypr72Pec19OQjRLZKWo>dd<w$IRfK-JPdgD%7x@=I1E&+kD#qMw<?`>MN>| ziKEyDbB_p9&9u|ZvAu6jcQe+G)<wOuUwC@E;ss%}lHE68pm9*JyX>kt>uk^M5OLKh zKf+)(p!@c<lM?3zfanfsRAFH*{V43}<TpVsxdH39v{4{Nn{j#P&AiEJ2#vJ6TF-%p zujFRU68CbcYK9pzmoi4kV<X-mom29M9Z!<cq1WE2wcvNEOL_9!jw_Q572ghYdWZe? zO*gpU-x-`XJ_7-`Q80i+6JE9U7{>+F9`*!aJ10J+<2Lr!K{J!qTaifl&WjN`6zL%Q z>IWNf-QR5wo1BG8%^?S(^uaRX=cWZ$Lp>$Yd5e1>r~HfJ6Mkm6N%89aWl!SmnNaJB z%B@8Zt$EiX%{`<rGNfODqg|zKJ4W>6uzujmWf^c>^JM(pGX9-1$U+>}a*`#b+V@4t z&Hz~aFf5))n#J2wKJ_Yl2<?uu@4<e(*j$|Jr)0ru-qfp2O;*19UY>n48#>u*8Fl&< zM4L>3n#T!3D8i(W)>>6T2AAqiMq0hAJSUU_<TScis&^Qsw!Ul8#3H;b^uTxXby=Hz zk1QvZh&A?0WnmVtW|S3(ln(n{sZ?LNG&ixcfwh!gxD<+8wlauViH)ONsae`Xgz8!* zbh_Pc>2R*_a`yW4YPY*b;m%g~udg^EZ3x=WqGSx~@KYM}+n`+Ws<ve;eUeJ_g)0J4 z7eqb!5GrkX>#m1Ah6bip{IQg!Xe*Sfa?Ufoe1GR$AI~)!fF!6V`oruv+X6<7)0jcJ zWh&cpEj;$46}+CB;qk&t!)14U>7TdNp_GfzI{q0(D+5_woFw~<khkI|S6Ym>k!~p7 zN3Xsur!w1Kz1t<i1fUXVb7z}<$u>_`iJ1?t{CSk10b=oxFNYSL_<U)G<xu~pnUb@= zc!N})4?e8S1hPeaqCblzr#PMUMP|1cgkI=neA{SWCRoVXrm2^&O?s7~*xC<FjC0@W z#2MaOuppqp)s~@2%?mC@r2PF3rJc>kTsO&?osv&Y3hnm~O`CJ!SB3Oavcsi&Yov9o zb~jL4Gt<`1ngzp1Zg8tZ>h3FOkjlpjCc#WjCyCpg9)4*)dr2ujlkOlJiS0N`ItC&C zn?VWd8{z_%a%HiLDIHzUo8@v4m4B9Md<v%8Q)q8nm95c~p5l-1S2dF8&JbJf*V9Aj z3*C^0CF>Sj*=?mzTM^@Q2n?&&(cv6p4|YLlX_W3LiFs46_aF07#qkBuhdZmAX^P7E z`9JDCwksXRXVPE`D4Qt^RQmhyG$vMaF*(qr+dS@t0;rmAGuZ;nxB0edV1KHat)&^s zpcot+9l3Hy`cqlR{^zN)@>~kbR`xCxT62!{6+4Ec92XPk!ziS~Zhlhw15arJJUSq@ z_^D2Pyr5dY4<Aoyj*(SbGSpIVoJ-=RSf`Q1R$qw^o1Edtwc1_B2k;odYVYw_MqU(_ z8_(<0e&x9aB~g~Ee_5+&80?@wo78VDiGpY<t#2$VI!q-N;{8K>eU0U<ebvp+b{~@V zZP$39=*%#;2z)D@ZT&tV37AS7(#5?JoyW%sjKC>6(_9sbeb#j0yw`^OgRkboX=eBf zN|5<GXS}xD-8{Q3m10D0r?OjhtAB)^Iny#KUJ*CIR}mLC?~bc-(m_N~3&?6LT&uI$ z!;o?fn$ShiU}GAm>VDt)7(2W~|3qtC#a@zaZp2Kz(iOAJlE>g{>jJ4U2`##mFpl`y zY>g?s;L<zUEOY~5E$74YExbAB7KsX7wnyHW#vs`9hXrrO90Ff>wSB^vx~lDN%LHli zpy&rStKWu$>cnLx(<g%WVLZ9&ZxIz$vyhC>n={QD{@Q(?ISVv<cA$<XG;ULOv`dJ# z<|Y=TiXgpKdC%e(`&5&vKQk^68CMZQ3pq07m%CKo7mC`1>neCufag%q*uX_+2*`Rn zBTBeTVVoOZqTr4wl%(3Mp2aB!ywdM{xt81^niRh*4RTgb+}JizY5YqSJed5z3^i!q zBxOdlBqWv}2s0iFTGrfUQ=(>9!_B6eor6P8=Z#;vDOwJ^{FzN6T_#07O)?A70w!EP z3j&Jexr9@guYkDy1%za95SGZ2xsqOMIw7PiM`)roF)UeQ@VvHlap<Rx*rB2vNM_pe z9(~d39JY^#m#ySv+WKJ04cY{(Y1MNnakC#y3l8bKF_3#xMeJN=c(GDX`7uu~69l|! zU^G$DO%TbYVQu0Rfyb}<4>6y_W#vGE>(k|Kw=OlM@(1EV!<zB7&T@=9+I3MYA5gxJ zG~D!loLA0+fnf-ol&eG?+G99d>l1aA(Oi<2XY>31MB=KqHpQ7pv@@-?f{)WItp|qi zI3udjm_wq`Z*<8#Z_r5R)xjOJf(FReD<K;uVsGXiSRP6PFB|cV9O}~~D%5t-d*0QL zB@nGWo;7Bx+17|ixLJp<*;=+~m;^oq>mTfdcZwxWGq!ENAH1*qsBL@6Q6ppjqD{fs zkhY@&fDGz35L&WKcOgrDfLPP1vUBR6T)PW>+^Uo;jdgo2x-XboW~ZP=v#xrZ2j;$1 zt6`3QNn7om6bo%jUmd-#1X)dB#TT(qfD8JQJtI*Lt(i4_u}<#T1Ca_gdI#IB;o;^8 zy!D?`3KC{M#G2^M<H+ll`rSaBNldQNM?=-~^AHCJ{Kc$II1pSKFn}(9?Tv`gNnB~( zJ0n5>KhqAmd(Gjt)*{yE0YBGUx4Nh(oc;~m7*67@<p=MlR`Kp*r+B8+o2X{=H)DNZ ze-RyW)-t<AoVSO()*?@{T9hiEzlThE3@`7xhZGw-$gVos$x47h)jD7FEYZ4MKS>iG zlZYFRZcgBRFFpf#Av^vgd#+B|?d{vW<^+k{F8lQbo!>apa}yKepQWcO_iy(eV9G*R z7SA9MI-Z>m3hgXZB9QRjFwRKt@UrM;eXkrJpYK@*74y$l$Xm9M#8!Q9c_Mc_Pmdb3 zrGid|vN&e}Hk?OkjoZwpbBQVMmYENpbmo4~lgCevvgBD9a`xtu!^Y>tKn}tkUgF00 zPPloHyq~TWXyC5e$15-Vg!aRgxn9X0Vk(7tjgVg&s$SXYr1R?=R-#NN1cB$URFMdJ zYPHN3l1oz0_Z5qU{Qrysmj&C#yX(9??It1`_^8(p6MdF7c_K8!+y1*de(OCdpwA_A z**bRCGbItLM%yifB)YC}^-O&56DZme<3{O}o>;{y4nSm(``wx-=>7mB8|WL~*hSi? zFE^92;?GByk1?Bq-i;Q8zXZx7lhmKoXSGV|qhbQkA2L1Cwx0787ayh)S(D8F(37?r z$0MC}W-2*JAHONSa5)_{*=N&}e+`iAPe}T+6q#RAo?LPR%$!9wNpw|_kRWAXS*ix| zeeH8wBDXM}tsI0FzK({yE^pYfhmT>i#_rRfI?-_~fzYbr)YNW8)3*-A*Z81e>90nW zhtMOZyRX%DDqEO$niQ`p>5T^&x<-vTl>jLQow4#kr}3I;Dn$1D*;54jgxP-Kn^p|@ z?*@W<gnt_g0E~&hiskZ!pS1uYU;Nh_38thk#(qdQ1A?Kza3I$Y5;{Y8IT{)Ua?zYf zmzuwf;+?Xz6JXMNa@EvMtx<J!k2&5DQvMGsLY6(|HCWkJ2gM}XfV%Koj6bS2G4F@U zSWq`3O6`sTo8sDAxk<Ef<LIHX;^eP3zlL?uJ~8%s-(6>2e1edFz;96`l#X_(I*xf1 zW~F1>*5lLNP=0Un^^!iOA^}#$x8tZYi3qPkMMAsZry8G|^R}|IQRw(1P?Aa>alu*o z`esVAt1%+?=e0P;cgp+TUGP4!A(U$W7Dm@6;C3`(S}_7&JGlc6ih%f&^g}@CxOEwE z(iskAPR5fcu?f(8BkK*|BABggKF$sZs@`-jeGT#3^B3efw6DDBqMV~DsJNDwLTf4K zIAdmCZ9M+0;`=Y7CHS|Gv2-0C0htHdiUwJ}AnRF<?j#GCb$IM|-bc@B9(>03t<?nj zz(x^PyDtgFh1KS*yVojM=TK5JNNQ;*uw(`7l}3L|CwGt&-;_1JHAKeXyhtr4IiRe) znNbImHImvY?RdStx#MkNE<)6eec9&0b&(`roDk~oU3$;VM<_+JEyG-KG#X~7W?{U7 z%`&(CdQuBz3IW2swp!gnzEJ~of`nyR(bKylRfR6DhQA&`x2b!U`PQq3NiUM%`|XR3 zxDS~G3!VMwGwYdEzc_dXQ3F&zb{_da4%uUOyBX`c^JbP8id61NVhZE!brY=h912@< z#C|T)KD;Y+x=>4NTUVRDL5%*wek$Ug{qUM?TU!9%!=y4etm*kIWFu6t_Sbxv0C|il z#Rp%X090LP<*2m|cWhbw{9LkX8ZEbn*J`~S&1DG@S07kj6z6p@QUP|teK;_<G0h_8 zmWdB(9tNZ{3yDh9N1?1tmf$(_{vD>AUWCxuz$+PNQLFN#ujHWnhPdH=-gfGh{2CS? z`}BmxJP<7k)cwP}HR#$kwc1ub1Ja}t6Q6LSQ>*ZFol)h*?!ARQ*Ogg+2+lKK>}?}) zZqr9u$alG$oBhUAhhD?#(`w6gI5k**R8qh8lbKt9#QZCdcBRESBOk}HBLB(48rY8< z`1gZ%uMCEl_<3rEgKykOS-1aH2@A?CsB@mDHLA(w=F$2Pu6cbtJHx9^hK`Dw^A_rP z`=xz-&ch)qmHo+iX%40tseoTw&|-8?rzt^fZ_PC6G%*4X*GLdrLnLbMUT@p(I=oo4 zZ}jQRiK&L2f3>Fx+trb)!!^`beDkZe%!GeWxYTshJ)N08V|&BKTy1_Ui1h(WW=SYv zDz+iEx2|lVnU7C6sn0DS)hWQn0BgJ%OGM+iFyni6ah=6@8QtjpSRYYuwtWlpMcFv} z(Q5f|2DY3^9&z}{^^qa5IFmp%Mm3k(3!@e=-j-INW(ToPSf1XT%qg`$Yl5o{B=)}{ zylhpk&f{Neob0dj>b{ovvCZy^$?EUZgrJ+5Aj)2-+hD`e?4I12q%FpWnW&dj*y)5} z(gy+MbWUcW3h_$C==(xmZgf4t7+nu?PN<>bCz}{pXi8F~&JjowFt)Fc*$P|oIG3il zM?<oo5@mDmmC8YcWzHdNDXp=!k0k=uKCMn040>LZuy2X~j!;HS4NR*J67CxA!mK4~ zq1Zw*NscHayVU*lXV*U1Si*cW%Q5+}Ec~$P&cp#^F=8>Iyumx*?UXwg_@q=<@G!97 zqNvJ|hL1b;HB{k!V5eJl0*`li@p<G-M{qkgS7;LD!k_1x_$H9ur{f%>qOfaouJQTe zV9CbI88X>wscF|ibflRb6I9R)dJZ9EuQ)C^8}Nbj8shsUsq(8G*7iw$w7BZ~AAH~@ zO^a8`()ntIP-<Sdx;GFRo(>%loRm)SgdyH6_wd3d!ZJc0u=J+&AGnQ4W2%M^H4vJ4 zSKF$rEE^l+#{ADAwjf+|jqvN45mysQVHJQ&UDutDS+KNbAkA$OryUx^q7$-|Mvxj{ z_e-pV?j>|x{WE{*H~(b{kWxTq8@`*gCst6!{_?~^M#{a$D@VFDIU{wXXPSi99_O>g z(73VkrOrozJT&UN@&<&87DMR$7fZuAnLd_}iGcd$?IE}vlbS>R4>5^VCClV&qA`P( z&PN7D&V^9{F6>g{_N?`39`h1Kt)xC@F4dZzATA3%@1@nNyqNw>wLNU7;0y2D<3|fj zvrOZ4D@N87Uz&rNJ*Z4TX$IS>^eiUI{uu60+qBGIPpREPm91P_t}ZtEu?0fA%z=Rg z<%3IrfuV>e%{i*u7JfadJp4fg=tLeE)1q-MQrq!T6NIE&-W%VrXScf9BHbBjBr>=> z&j!DxhpSLqRqSGITmCZ-3vIw)$mN^ot|CaC-aifEOM2eG!oS0{h2HFL){pbn6*Y3+ z$L{T1+vt3JmVj=BU!Bxj9-rnZzSv!VUQ6e3kbt?V7f|Tgu+(&Q?xy*Tb?@&DXd$Ox z(`@|RW&<$(X8q3Im!g<H=QB?X<tIpjM?a(!!%(-U{5QV8sh)GS@8@n!=&G5*^X59n z2fmKTIdol`?UQ!j1;7*2O+OkmFQ+Fl*3Ye_9+Z{9GZkp}m9$CHYYkT6uv9ZUHBRl7 znmAr~eR%|a^nh7=1{ET5qC(uxi^w^0OZoUMjy7zd6ufk)Rztt+VRYPpTFabDoO%DZ zFVTIyK-MBCkz&JIrGl8vCr?hl>$7NuCfqG{1I-3kU5Ka_b#t(BA7+zl87YvOMCe_; zF>Hq~&J}<NRTO831UQ?U!Te9-I!7u3USQs)66-VuI}LN{E`;hl;ERk}%lI(^IKrbf zF5LOat4A8Lb+b_4%j7w}C`_ky$5^ADg9h+CKt_3{;5lgCt5aue1T7MqYZ&n*<7mC* zjRs?)D(Xl|Z;4Upbj@1~{{&~s(mVPU%_1st;Kh)lL1^(4$LG?9l7sx{27#b-Dde3z zCKi6ODbl;hI6@KVOlNA>uUZ-J#8r*hQ!8vu)}x-&M^uv5Z}OeMtM;S+5QTZtoot3< zujkk^do6Pj01FjTxEB2#iFMS@4f8zn|8>`mP|WN8FFUPT(?M~LeF)#Zp-FNXkSz9A zDnMB&(hF;K|JZbr|3m|X&GsL4b1KLyD%R^;BVVO;TrSgo1C)_C3hvM!yGB6Tctnf> zDKJVn_;`ev_}hRGT9n+dspZIAga0xy#|dZP&Um3MzW^(ie?FNE^a1(=-U}ZOxF_!3 zX3LEK_{+cau%!yry#27O`u`0&4Ac)HWRE>#=6T)elV$j4pFpD@5Nv2+2_a*D2a*97 z4M542z<jYphkw(kj-}FHL3tgaPX3^JOi4za`T&PXrvbv?>D98PwYEs+#XQxt9~OP- z%K-Q3b&IkkH!Ur##Qm3LmDEYEH0%JVDP7U0b3l#v6lHgkKz~uy#A-=1hv8KNlVog> zowKgN=U#u{lL<xunIq;k#i{9^vn?2ahIvjDZ6{p)yAMJ}4<hxXD34|+OM)sjTbgMy zU_lD*G3OCo1f?lBeY7YQ=juOCJ<}U^7xde6_4w=bwHkoDXOhgb%%@lJmCX81vf{3e zUMLG@y(=Ep;9$!f4hN8~V|!p)pa>8mv*QB17!p}A0pMlr;nIcmZNfloG}5^|T<fy< z{B|Wkw}&EABX6x^r3<-FkVXnr&%s&>VsG=vBGn=HEhk#aS>cA$pFRN5ECx4ODIAt; zxEp$e;tbqYl?h|jA^{B0B5OE+pLhB!tM4~{6i$eYDy##z$)+Ni?icyJ&GH~GIa4~Y zznJ#V4EX{Wvjh5pXgq-4?tJ*WM|gvg!w5~gv5ocdJ}zno(G6hF97F;u66^z@WapY# z@;-yL&+HygSRLTSYbS|Skr$Uc^@|??vk1_x2#Q(u-G~>I8_pq%Nnn(MOjimp&b9!^ zrWn<+cx{nCrExIBtWW-0#x2fQ04-{*UZusym|3qQc@2DX{;6P26U!d+o1-%)fqP^L z^k2WoNB#-m*63V8TdgND6?QuduKYh;36QQnwwt(i<5<)KU!Gg~Q|jR2Uq`pf8~|lx zttLWnBKhaL^B0&!d8Jma{>!z=cK_40c2%P~rMbDMnNB*+J_o=xQGCdW>;fV4;G==B zkb5_p86l4`%ZieSug+XWR|^!w!Z;O<_7O{xjFwj1>TrHE`I`U`ek$)v<V(qTNbA>W zvh)C;E=tuDpmuPA;6A@yovh3sj<)*;%8Zrb06u_?5b)m!cc{j=cPAwr+nEAEKn2`y zq|gRLPv%zSYdcCq*FiZbIvD^gFCuQRET}Nu2cUWs=UgQz_t05QSM-f)$sfxI7ifIY zxaP^eBuHMgHga0WX^nvo0PsoN3k3LUQ2?KIE16^8VRLD4gL%LKQ&-!U0cn@6$*mbY z3sXJo|C9`582EV3s(!pifFpv$=!%~j-C7!KA=6f`C7VS#0mRP5WKM#Rmya#UuOM<K zFmEAN*=DD0{bs^d)#IUQCx4+SHcF@t22K`yC1c~sj0#Ri&{p81AZY;WecFB7M1#+! zf!%^7iwSx*FAJ#dZr6=?!wt)~f_7Ce0Wdj0wKO}yfjzff`p-QVf3E^03><zCBXez& zzbT?Bh8eMMI@orY?}$~Ddv`2o670ahFF8`H0wp{N(Lj-06@%q0S4W;lUE~Tfcce^i zJ7Gv)4t%8O3pmJ%`n{Cfzk`y!094=d=jsnp|E5u08BpRjikWps`5K_(|J2f#mp)TD z--YQLx`YHhZYQ^ooFMu%c>G6b$O2*vAoRvoGd>!TZ?jRpm9Lp+;!-w+jF>uFSD6#Y z-Pw9KR}j#(j4=QJ^xB9a@93-+IskH~m)rgG5>3$2n-wqw+C(&r7Ubyc7hEG>-9TrG zgO5X=9GUr%L^5754>Mk3-(A2$Za&Md9$8+^<mURtKp~0zym^i<6KU6IL&+~A!Ak}) z0+HHEYW4{Zwcrv(WG#~q5B@rqNA>zH`KpTZ+P1#^r3pO8*768r$R<JS4%wb6xUwh1 zOzssyLrKQ2{_l`U&@<=eFMftV6tqx6t9LY!F)}h{_>EHp=+i=#_!R(fQzg_KOAU<v z4@wXK-z*kyw7km+JOlWB@zJd^O$*P(M{Cm`6+~netk}uYFVA_54!tqwQ}t8};Di_e z^1wd6>r3^^(nsxVF}mQKKHO#d7ZOeb{*hw>Q%4K);+$2)=yL=6Y5*aD0l=#oc$-36 z@}z~_&Jxry<2yMs9TP4y{HtR@X>*P><##s3<$IL_=~s}y$cv--0OFoR1li@huhFsd zFA!Uz`Z=;BS)HPYT$c{lk#PuloOkgmm^`j5v&-B78UBsAYidqS8Qv%#t3DbBuT9!x zW!p+W0KOr#$IX0dbh;wGEEfPAN468rE=IiY?LQlKq&2(%pfR>~oO5GGs^s?<NpZ!K z0``8ijD`~Et*FC>xyK=H5tF>@SVv%t^Z*c{PRi4o%5BGo2%=@eZQaQrmuIL89%-&7 z!cyCHO!>TCs@xB_7kUm{KDhMv6R|r|e#>@kgg48v0Qhssib|xB;JC&Ty`HxHu8m-v zCLS-yf(K_ck(Y2vK8rn91zq&%Z+Q2kH3R0e=%dNy6Yc*u_b8rRa{nbP_uoDiL$;z% z0V~4Ea0T?Yd&o_%Wp;u63YOMl|98OlKy6^ZYF>$@04^l!_LKKG{_p?gk^9n3oot8d TSJa~a0RAb;L1jxHo4)xE6;A%> diff --git a/demos/angular2/src/app/app.module.ts b/demos/angular2/src/app/app.module.ts deleted file mode 100644 index d7bb39a..0000000 --- a/demos/angular2/src/app/app.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; - -import { SheetJSComponent } from './sheetjs.component'; - - -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-root', - template: `<sheetjs></sheetjs>` -}) -export class AppComponent { - title = 'test'; -} - -@NgModule({ - declarations: [ - SheetJSComponent, - AppComponent - ], - imports: [ - BrowserModule - ], - providers: [], - bootstrap: [AppComponent] -}) -export class AppModule { } diff --git a/demos/angular2/src/app/sheetjs.component.ts b/demos/angular2/src/app/sheetjs.component.ts deleted file mode 100644 index 0015d61..0000000 --- a/demos/angular2/src/app/sheetjs.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -/* vim: set ts=2: */ -import { Component } from '@angular/core'; - -import { WorkBook, WorkSheet, WritingOptions, read, writeFileXLSX as writeFile, utils, version, set_cptable } from 'xlsx'; -//import * as cpexcel from 'xlsx/dist/cpexcel.full.mjs'; -//set_cptable(cpexcel); - -type AOA = any[][]; - -@Component({ - selector: 'sheetjs', - template: ` - <pre><b>Version: {{ver}}</b></pre> - <input type="file" (change)="onFileChange($event)" multiple="false" /> - <table class="sjs-table"> - <tr *ngFor="let row of data"> - <td *ngFor="let val of row"> - {{val}} - </td> - </tr> - </table> - <button (click)="export()">Export!</button> - ` -}) - -export class SheetJSComponent { - data: AOA = [ [1, 2], [3, 4] ]; - wopts: WritingOptions = { bookType: 'xlsx', type: 'array' }; - fileName: string = 'SheetJS.xlsx'; - ver: string = version; - - onFileChange(evt: any) { - /* wire up file reader */ - const target: DataTransfer = <DataTransfer>(evt.target); - if (target.files.length !== 1) throw new Error('Cannot use multiple files'); - const reader: FileReader = new FileReader(); - reader.onload = (e: any) => { - /* read workbook */ - const ab: ArrayBuffer = e.target.result; - const wb: WorkBook = read(ab); - - /* grab first sheet */ - const wsname: string = wb.SheetNames[0]; - const ws: WorkSheet = wb.Sheets[wsname]; - - /* save data */ - this.data = <AOA>(utils.sheet_to_json(ws, {header: 1})); - }; - reader.readAsArrayBuffer(target.files[0]); - } - - export(): void { - /* generate worksheet */ - const ws: WorkSheet = utils.aoa_to_sheet(this.data); - - /* generate workbook and add the worksheet */ - const wb: WorkBook = utils.book_new(); - utils.book_append_sheet(wb, ws, 'Sheet1'); - - /* save to file */ - writeFile(wb, this.fileName); - } -} diff --git a/demos/angular2/src/environments/environment.prod.ts b/demos/angular2/src/environments/environment.prod.ts deleted file mode 100644 index 3612073..0000000 --- a/demos/angular2/src/environments/environment.prod.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: true -}; diff --git a/demos/angular2/src/environments/environment.ts b/demos/angular2/src/environments/environment.ts deleted file mode 100644 index ffe8aed..0000000 --- a/demos/angular2/src/environments/environment.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: false -}; diff --git a/demos/angular2/src/index.html b/demos/angular2/src/index.html deleted file mode 100644 index bc806fb..0000000 --- a/demos/angular2/src/index.html +++ /dev/null @@ -1,30 +0,0 @@ -<!DOCTYPE html> -<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com --> -<!-- vim: set ts=2: --> -<html lang="en"> -<head> - <title>SheetJS + Angular 2+</title> - <base href="/"> - - <meta name="viewport" content="width=device-width, initial-scale=1"> -</head> -<body> -<pre> -<b><a href="http://sheetjs.com">SheetJS + Angular 2+ demo</a></b> - -The core library can be used as-is in angular applications. -The <a href="https://github.com/sheetjs/js-xlsx">Community Edition README</a> details some common use cases. -We also have some <a href="http://sheetjs.com/demos/">more public demos</a> - -This demo shows `SheetJSComponent` which provides: -- File input button with an event handler to parse the workbook -- `data` property: array of arrays -- Simple angular table which binds to the `data` property -- `export` function that exports the `data` property to a new file. - -<a href="https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls">Sample Spreadsheet</a> -</pre> - -<app-root></app-root> -</body> -</html> diff --git a/demos/angular2/src/main.ts b/demos/angular2/src/main.ts deleted file mode 100644 index d3da1bb..0000000 --- a/demos/angular2/src/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; -platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/demos/angular2/src/styles.css b/demos/angular2/src/styles.css deleted file mode 100644 index 90d4ee0..0000000 --- a/demos/angular2/src/styles.css +++ /dev/null @@ -1 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ diff --git a/demos/angular2/src/tsconfig.app.json b/demos/angular2/src/tsconfig.app.json deleted file mode 100644 index 321e0d7..0000000 --- a/demos/angular2/src/tsconfig.app.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/app", - "module": "es2015", - "baseUrl": "", - "types": [] - } -} diff --git a/demos/angular2/tsconfig.json b/demos/angular2/tsconfig.json deleted file mode 100644 index a35a8ee..0000000 --- a/demos/angular2/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "outDir": "./dist/out-tsc", - "baseUrl": "src", - "sourceMap": true, - "declaration": false, - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2016", - "dom" - ] - } -} diff --git a/demos/angular2/versions/angular.json-ng10 b/demos/angular2/versions/angular.json-ng10 deleted file mode 100644 index 9e7228c..0000000 --- a/demos/angular2/versions/angular.json-ng10 +++ /dev/null @@ -1,125 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "sheetjs": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/sheetjs", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "aot": true, - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "6kb", - "maximumError": "10kb" - } - ] - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "sheetjs:build" - }, - "configurations": { - "production": { - "browserTarget": "sheetjs:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "sheetjs:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "sheetjs:serve" - }, - "configurations": { - "production": { - "devServerTarget": "sheetjs:serve:production" - } - } - } - } - } - }, - "defaultProject": "sheetjs" -} diff --git a/demos/angular2/versions/angular.json-ng11 b/demos/angular2/versions/angular.json-ng11 deleted file mode 100644 index a1563c1..0000000 --- a/demos/angular2/versions/angular.json-ng11 +++ /dev/null @@ -1,124 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "sheetjs": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/sheetjs", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "aot": true, - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "6kb", - "maximumError": "10kb" - } - ] - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "sheetjs:build" - }, - "configurations": { - "production": { - "browserTarget": "sheetjs:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "sheetjs:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "sheetjs:serve" - }, - "configurations": { - "production": { - "devServerTarget": "sheetjs:serve:production" - } - } - } - } - } - }, - "defaultProject": "sheetjs" -} diff --git a/demos/angular2/versions/angular.json-ng12 b/demos/angular2/versions/angular.json-ng12 deleted file mode 100644 index 69d02ea..0000000 --- a/demos/angular2/versions/angular.json-ng12 +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "sheetjs": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": { - "@schematics/angular:application": { - "strict": true - } - }, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/sheetjs", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "2mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" - } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "sheetjs:build:production" - }, - "development": { - "browserTarget": "sheetjs:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "sheetjs:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - } - } - } - } - }, - "defaultProject": "sheetjs" -} diff --git a/demos/angular2/versions/angular.json-ng13 b/demos/angular2/versions/angular.json-ng13 deleted file mode 100644 index 69d02ea..0000000 --- a/demos/angular2/versions/angular.json-ng13 +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "sheetjs": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": { - "@schematics/angular:application": { - "strict": true - } - }, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/sheetjs", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "2mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" - } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "sheetjs:build:production" - }, - "development": { - "browserTarget": "sheetjs:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "sheetjs:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - } - } - } - } - }, - "defaultProject": "sheetjs" -} diff --git a/demos/angular2/versions/angular.json-ng6 b/demos/angular2/versions/angular.json-ng6 deleted file mode 100644 index 30e40df..0000000 --- a/demos/angular2/versions/angular.json-ng6 +++ /dev/null @@ -1,127 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "sheetjs": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/sheetjs", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "sheetjs:build" - }, - "configurations": { - "production": { - "browserTarget": "sheetjs:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "sheetjs:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.spec.json", - "karmaConfig": "src/karma.conf.js", - "styles": [ - "src/styles.css" - ], - "scripts": [], - "assets": [ - "src/favicon.ico", - "src/assets" - ] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "src/tsconfig.app.json", - "src/tsconfig.spec.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - } - } - }, - "sheetjs-e2e": { - "root": "e2e/", - "projectType": "application", - "architect": { - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "sheetjs:serve" - }, - "configurations": { - "production": { - "devServerTarget": "sheetjs:serve:production" - } - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": "e2e/tsconfig.e2e.json", - "exclude": [ - "**/node_modules/**" - ] - } - } - } - } - }, - "defaultProject": "sheetjs" -} diff --git a/demos/angular2/versions/angular.json-ng7 b/demos/angular2/versions/angular.json-ng7 deleted file mode 100644 index be12951..0000000 --- a/demos/angular2/versions/angular.json-ng7 +++ /dev/null @@ -1,136 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "sheetjs": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/sheetjs", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [], - "es5BrowserSupport": true - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - } - ] - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "sheetjs:build" - }, - "configurations": { - "production": { - "browserTarget": "sheetjs:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "sheetjs:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.spec.json", - "karmaConfig": "src/karma.conf.js", - "styles": [ - "src/styles.css" - ], - "scripts": [], - "assets": [ - "src/favicon.ico", - "src/assets" - ] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "src/tsconfig.app.json", - "src/tsconfig.spec.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - } - } - }, - "sheetjs-e2e": { - "root": "e2e/", - "projectType": "application", - "prefix": "", - "architect": { - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "sheetjs:serve" - }, - "configurations": { - "production": { - "devServerTarget": "sheetjs:serve:production" - } - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": "e2e/tsconfig.e2e.json", - "exclude": [ - "**/node_modules/**" - ] - } - } - } - } - }, - "defaultProject": "sheetjs" -} diff --git a/demos/angular2/versions/angular.json-ng8 b/demos/angular2/versions/angular.json-ng8 deleted file mode 100644 index de608c0..0000000 --- a/demos/angular2/versions/angular.json-ng8 +++ /dev/null @@ -1,126 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "sheetjs": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/sheetjs", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "aot": false, - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "6kb", - "maximumError": "10kb" - } - ] - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "sheetjs:build" - }, - "configurations": { - "production": { - "browserTarget": "sheetjs:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "sheetjs:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "sheetjs:serve" - }, - "configurations": { - "production": { - "devServerTarget": "sheetjs:serve:production" - } - } - } - } - } - }, - "defaultProject": "sheetjs" -} diff --git a/demos/angular2/versions/angular.json-ng9 b/demos/angular2/versions/angular.json-ng9 deleted file mode 100644 index 9e7228c..0000000 --- a/demos/angular2/versions/angular.json-ng9 +++ /dev/null @@ -1,125 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "sheetjs": { - "root": "", - "sourceRoot": "src", - "projectType": "application", - "prefix": "app", - "schematics": {}, - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/sheetjs", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "aot": true, - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "6kb", - "maximumError": "10kb" - } - ] - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "sheetjs:build" - }, - "configurations": { - "production": { - "browserTarget": "sheetjs:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "sheetjs:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "sheetjs:serve" - }, - "configurations": { - "production": { - "devServerTarget": "sheetjs:serve:production" - } - } - } - } - } - }, - "defaultProject": "sheetjs" -} diff --git a/demos/angular2/versions/package.json-ng10 b/demos/angular2/versions/package.json-ng10 deleted file mode 100644 index fff68df..0000000 --- a/demos/angular2/versions/package.json-ng10 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular10", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "~10.2.4", - "@angular/common": "~10.2.4", - "@angular/compiler": "~10.2.4", - - "@angular/core": "~10.2.4", - "@angular/forms": "~10.2.4", - - "@angular/platform-browser": "~10.2.4", - "@angular/platform-browser-dynamic": "~10.2.4", - - "@angular/router": "~10.2.4", - - - "rxjs": "~6.6.0", - "tslib": "^2.0.0", - "zone.js": "~0.10.2" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.1002.3", - "@angular/cli": "~10.2.3", - "@angular/compiler-cli": "~10.2.4", - - "@types/node": "^12.11.1", - "ts-node": "~8.3.0", - "tslint": "~6.1.0", - "typescript": "~4.0.2" - } -} diff --git a/demos/angular2/versions/package.json-ng11 b/demos/angular2/versions/package.json-ng11 deleted file mode 100644 index 9bb80a8..0000000 --- a/demos/angular2/versions/package.json-ng11 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular11", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "~11.2.14", - "@angular/common": "~11.2.14", - "@angular/compiler": "~11.2.14", - - "@angular/core": "~11.2.14", - "@angular/forms": "~11.2.14", - - "@angular/platform-browser": "~11.2.14", - "@angular/platform-browser-dynamic": "~11.2.14", - - "@angular/router": "~11.2.14", - - - "rxjs": "~6.6.0", - "tslib": "^2.0.0", - "zone.js": "~0.11.3" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.1102.13", - "@angular/cli": "~11.2.14", - "@angular/compiler-cli": "~11.2.14", - - "@types/node": "^12.11.1", - "ts-node": "~8.3.0", - "tslint": "~6.1.0", - "typescript": "~4.1.5" - } -} diff --git a/demos/angular2/versions/package.json-ng12 b/demos/angular2/versions/package.json-ng12 deleted file mode 100644 index 74df6c5..0000000 --- a/demos/angular2/versions/package.json-ng12 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular12", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "~12.2.0", - "@angular/common": "~12.2.0", - "@angular/compiler": "~12.2.0", - - "@angular/core": "~12.2.0", - "@angular/forms": "~12.2.0", - - "@angular/platform-browser": "~12.2.0", - "@angular/platform-browser-dynamic": "~12.2.0", - - "@angular/router": "~12.2.0", - - - "rxjs": "~6.6.0", - "tslib": "^2.3.0", - "zone.js": "~0.11.4" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~12.2.7", - "@angular/cli": "~12.2.7", - "@angular/compiler-cli": "~12.2.0", - - "@types/node": "^12.11.1", - - - "typescript": "~4.3.5" - } -} diff --git a/demos/angular2/versions/package.json-ng13 b/demos/angular2/versions/package.json-ng13 deleted file mode 100644 index a214cef..0000000 --- a/demos/angular2/versions/package.json-ng13 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular13", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "~13.2.0", - "@angular/common": "~13.2.0", - "@angular/compiler": "~13.2.0", - - "@angular/core": "~13.2.0", - "@angular/forms": "~13.2.0", - - "@angular/platform-browser": "~13.2.0", - "@angular/platform-browser-dynamic": "~13.2.0", - - "@angular/router": "~13.2.0", - - - "rxjs": "~7.5.0", - "tslib": "^2.3.0", - "zone.js": "~0.11.4" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~13.2.1", - "@angular/cli": "~13.2.1", - "@angular/compiler-cli": "~13.2.0", - - "@types/node": "^12.11.1", - - - "typescript": "~4.5.2" - } -} diff --git a/demos/angular2/versions/package.json-ng2 b/demos/angular2/versions/package.json-ng2 deleted file mode 100644 index c6cdfe9..0000000 --- a/demos/angular2/versions/package.json-ng2 +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "angular2", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/common": "~2.4.1", - "@angular/compiler": "~2.4.1", - "@angular/compiler-cli": "^2.4.1", - "@angular/core": "~2.4.1", - "@angular/forms": "~2.4.1", - "@angular/http": "~2.4.1", - "@angular/platform-browser": "~2.4.1", - "@angular/platform-browser-dynamic": "~2.4.1", - "@angular/platform-server": "^2.4.1", - "@angular/router": "~3.4.0", - "core-js": "^2.4.1", - "reflect-metadata": "^0.1.8", - "rxjs": "^5.0.2", - "systemjs": "0.19.40", - "zone.js": "^0.7.4" - }, - "devDependencies": { - "@angular/cli": "1.1.2", - "@angular/compiler-cli": "^2.0.0", - "@angular/language-service": "^2.0.0", - "@types/node": "~6.0.60", - "ts-node": "~3.0.4", - "tslint": "~5.3.2", - "typescript": "~2.3.3" - } -} diff --git a/demos/angular2/versions/package.json-ng4 b/demos/angular2/versions/package.json-ng4 deleted file mode 100644 index 2891383..0000000 --- a/demos/angular2/versions/package.json-ng4 +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "angular4", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "^4.0.0", - "@angular/common": "^4.0.0", - "@angular/compiler": "^4.0.0", - - "@angular/core": "^4.0.0", - "@angular/forms": "^4.0.0", - "@angular/http": "^4.0.0", - "@angular/platform-browser": "^4.0.0", - "@angular/platform-browser-dynamic": "^4.0.0", - - "@angular/router": "^4.0.0", - "core-js": "^2.4.1", - - "rxjs": "^5.1.0", - - "zone.js": "^0.8.4" - }, - "devDependencies": { - "@angular/cli": "1.1.2", - "@angular/compiler-cli": "^4.0.0", - "@angular/language-service": "^4.0.0", - "@types/node": "~6.0.60", - "ts-node": "~3.0.4", - "tslint": "~5.3.2", - "typescript": "~2.3.3" - } -} diff --git a/demos/angular2/versions/package.json-ng5 b/demos/angular2/versions/package.json-ng5 deleted file mode 100644 index 1f62707..0000000 --- a/demos/angular2/versions/package.json-ng5 +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "angular5", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "^5.0.0", - "@angular/common": "^5.0.0", - "@angular/compiler": "^5.0.0", - - "@angular/core": "^5.0.0", - "@angular/forms": "^5.0.0", - "@angular/http": "^5.0.0", - "@angular/platform-browser": "^5.0.0", - "@angular/platform-browser-dynamic": "^5.0.0", - - "@angular/router": "^5.0.0", - "core-js": "^2.4.1", - - "rxjs": "^5.5.2", - - "zone.js": "^0.8.14" - }, - "devDependencies": { - "@angular/cli": "^1.5.3", - "@angular/compiler-cli": "^5.0.0", - "@angular/language-service": "^5.0.0", - "@types/node": "~6.0.60", - "ts-node": "~3.2.0", - "tslint": "~5.7.0", - "typescript": "~2.4.2" - } -} diff --git a/demos/angular2/versions/package.json-ng6 b/demos/angular2/versions/package.json-ng6 deleted file mode 100644 index a6df172..0000000 --- a/demos/angular2/versions/package.json-ng6 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular6", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "^6.1.0", - "@angular/common": "^6.1.0", - "@angular/compiler": "^6.1.0", - - "@angular/core": "^6.1.0", - "@angular/forms": "^6.1.0", - "@angular/http": "^6.1.0", - "@angular/platform-browser": "^6.1.0", - "@angular/platform-browser-dynamic": "^6.1.0", - - "@angular/router": "^6.1.0", - "core-js": "^2.5.4", - - "rxjs": "~6.2.0", - - "zone.js": "~0.8.26" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.8.0", - "@angular/cli": "~6.2.9", - "@angular/compiler-cli": "^6.1.0", - "@angular/language-service": "^6.1.0", - "@types/node": "~8.9.4", - "ts-node": "~7.0.0", - "tslint": "~5.11.0", - "typescript": "~2.9.2" - } -} diff --git a/demos/angular2/versions/package.json-ng7 b/demos/angular2/versions/package.json-ng7 deleted file mode 100644 index 841dab7..0000000 --- a/demos/angular2/versions/package.json-ng7 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular7", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "~7.2.0", - "@angular/common": "~7.2.0", - "@angular/compiler": "~7.2.0", - - "@angular/core": "~7.2.0", - "@angular/forms": "~7.2.0", - - "@angular/platform-browser": "~7.2.0", - "@angular/platform-browser-dynamic": "~7.2.0", - - "@angular/router": "~7.2.0", - "core-js": "^2.5.4", - - "rxjs": "~6.3.3", - "tslib": "^1.9.0", - "zone.js": "~0.8.26" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.13.0", - "@angular/cli": "~7.3.10", - "@angular/compiler-cli": "~7.2.0", - "@angular/language-service": "~7.2.0", - "@types/node": "~8.9.4", - "ts-node": "~7.0.0", - "tslint": "~5.11.0", - "typescript": "~3.2.2" - } -} diff --git a/demos/angular2/versions/package.json-ng8 b/demos/angular2/versions/package.json-ng8 deleted file mode 100644 index 5643841..0000000 --- a/demos/angular2/versions/package.json-ng8 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular8", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "~8.2.14", - "@angular/common": "~8.2.14", - "@angular/compiler": "~8.2.14", - - "@angular/core": "~8.2.14", - "@angular/forms": "~8.2.14", - - "@angular/platform-browser": "~8.2.14", - "@angular/platform-browser-dynamic": "~8.2.14", - - "@angular/router": "~8.2.14", - - - "rxjs": "~6.4.0", - "tslib": "^1.10.0", - "zone.js": "~0.9.1" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.803.29", - "@angular/cli": "~8.3.29", - "@angular/compiler-cli": "~8.2.14", - "@angular/language-service": "~8.2.14", - "@types/node": "~8.9.4", - "ts-node": "~7.0.0", - "tslint": "~5.15.0", - "typescript": "~3.5.3" - } -} diff --git a/demos/angular2/versions/package.json-ng9 b/demos/angular2/versions/package.json-ng9 deleted file mode 100644 index fe6c382..0000000 --- a/demos/angular2/versions/package.json-ng9 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "angular9", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build" - }, - "private": true, - "dependencies": { - "@angular/animations": "~9.1.13", - "@angular/common": "~9.1.13", - "@angular/compiler": "~9.1.13", - - "@angular/core": "~9.1.13", - "@angular/forms": "~9.1.13", - - "@angular/platform-browser": "~9.1.13", - "@angular/platform-browser-dynamic": "~9.1.13", - - "@angular/router": "~9.1.13", - - - "rxjs": "~6.5.4", - "tslib": "^1.10.0", - "zone.js": "~0.10.2" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.901.15", - "@angular/cli": "~9.1.15", - "@angular/compiler-cli": "~9.1.13", - - "@types/node": "^12.11.1", - "ts-node": "~8.3.0", - "tslint": "~6.1.0", - "typescript": "~3.8.3" - } -} diff --git a/demos/angular2/versions/polyfills.ts-ng10 b/demos/angular2/versions/polyfills.ts-ng10 deleted file mode 100644 index 741c886..0000000 --- a/demos/angular2/versions/polyfills.ts-ng10 +++ /dev/null @@ -1 +0,0 @@ -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/polyfills.ts-ng11 b/demos/angular2/versions/polyfills.ts-ng11 deleted file mode 100644 index 741c886..0000000 --- a/demos/angular2/versions/polyfills.ts-ng11 +++ /dev/null @@ -1 +0,0 @@ -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/polyfills.ts-ng12 b/demos/angular2/versions/polyfills.ts-ng12 deleted file mode 100644 index aa09a9f..0000000 --- a/demos/angular2/versions/polyfills.ts-ng12 +++ /dev/null @@ -1 +0,0 @@ -import 'zone.js'; diff --git a/demos/angular2/versions/polyfills.ts-ng13 b/demos/angular2/versions/polyfills.ts-ng13 deleted file mode 100644 index aa09a9f..0000000 --- a/demos/angular2/versions/polyfills.ts-ng13 +++ /dev/null @@ -1 +0,0 @@ -import 'zone.js'; diff --git a/demos/angular2/versions/polyfills.ts-ng2 b/demos/angular2/versions/polyfills.ts-ng2 deleted file mode 100644 index 2143ebf..0000000 --- a/demos/angular2/versions/polyfills.ts-ng2 +++ /dev/null @@ -1,3 +0,0 @@ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/polyfills.ts-ng4 b/demos/angular2/versions/polyfills.ts-ng4 deleted file mode 100644 index 2143ebf..0000000 --- a/demos/angular2/versions/polyfills.ts-ng4 +++ /dev/null @@ -1,3 +0,0 @@ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/polyfills.ts-ng5 b/demos/angular2/versions/polyfills.ts-ng5 deleted file mode 100644 index 2143ebf..0000000 --- a/demos/angular2/versions/polyfills.ts-ng5 +++ /dev/null @@ -1,3 +0,0 @@ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/polyfills.ts-ng6 b/demos/angular2/versions/polyfills.ts-ng6 deleted file mode 100644 index 2143ebf..0000000 --- a/demos/angular2/versions/polyfills.ts-ng6 +++ /dev/null @@ -1,3 +0,0 @@ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/polyfills.ts-ng7 b/demos/angular2/versions/polyfills.ts-ng7 deleted file mode 100644 index 2143ebf..0000000 --- a/demos/angular2/versions/polyfills.ts-ng7 +++ /dev/null @@ -1,3 +0,0 @@ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/polyfills.ts-ng8 b/demos/angular2/versions/polyfills.ts-ng8 deleted file mode 100644 index 741c886..0000000 --- a/demos/angular2/versions/polyfills.ts-ng8 +++ /dev/null @@ -1 +0,0 @@ -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/polyfills.ts-ng9 b/demos/angular2/versions/polyfills.ts-ng9 deleted file mode 100644 index 741c886..0000000 --- a/demos/angular2/versions/polyfills.ts-ng9 +++ /dev/null @@ -1 +0,0 @@ -import 'zone.js/dist/zone'; diff --git a/demos/angular2/versions/tsconfig.app.json-ng10 b/demos/angular2/versions/tsconfig.app.json-ng10 deleted file mode 100644 index f758d98..0000000 --- a/demos/angular2/versions/tsconfig.app.json-ng10 +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ] -} diff --git a/demos/angular2/versions/tsconfig.app.json-ng11 b/demos/angular2/versions/tsconfig.app.json-ng11 deleted file mode 100644 index f758d98..0000000 --- a/demos/angular2/versions/tsconfig.app.json-ng11 +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ] -} diff --git a/demos/angular2/versions/tsconfig.app.json-ng12 b/demos/angular2/versions/tsconfig.app.json-ng12 deleted file mode 100644 index f758d98..0000000 --- a/demos/angular2/versions/tsconfig.app.json-ng12 +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ] -} diff --git a/demos/angular2/versions/tsconfig.app.json-ng13 b/demos/angular2/versions/tsconfig.app.json-ng13 deleted file mode 100644 index f758d98..0000000 --- a/demos/angular2/versions/tsconfig.app.json-ng13 +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ] -} diff --git a/demos/angular2/versions/tsconfig.app.json-ng8 b/demos/angular2/versions/tsconfig.app.json-ng8 deleted file mode 100644 index 565a11a..0000000 --- a/demos/angular2/versions/tsconfig.app.json-ng8 +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "src/test.ts", - "src/**/*.spec.ts" - ] -} diff --git a/demos/angular2/versions/tsconfig.app.json-ng9 b/demos/angular2/versions/tsconfig.app.json-ng9 deleted file mode 100644 index f758d98..0000000 --- a/demos/angular2/versions/tsconfig.app.json-ng9 +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ] -} diff --git a/demos/function/.eslintrc b/demos/function/.eslintrc deleted file mode 100644 index dbf6551..0000000 --- a/demos/function/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": { "shared-node-browser":true }, - "parserOptions": { - "ecmaVersion": 8 - }, - "plugins": [ "html", "json" ] -} diff --git a/demos/function/AzureHTTPTrigger/function.json b/demos/function/AzureHTTPTrigger/function.json deleted file mode 100644 index 7c64ea6..0000000 --- a/demos/function/AzureHTTPTrigger/function.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "authLevel": "function", - "type": "httpTrigger", - "direction": "in", - "dataType": "binary", - "name": "req" - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} diff --git a/demos/function/AzureHTTPTrigger/index.js b/demos/function/AzureHTTPTrigger/index.js deleted file mode 100644 index c39d77c..0000000 --- a/demos/function/AzureHTTPTrigger/index.js +++ /dev/null @@ -1,44 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -/* eslint-env node */ -// base64 sheetjs.xlsb | curl -F "data=@-;filename=test.xlsb" http://localhost:7262/api/AzureHTTPTrigger - -const XLSX = require('xlsx'); -const formidable = require('formidable'); -const Readable = require('stream').Readable; -var fs = require('fs'); - -/* formidable expects the request object to be a stream */ -const streamify = (req) => { - if(typeof req.on !== 'undefined') return req; - const s = new Readable(); - s._read = ()=>{}; - s.push(new Buffer(req.body)); - s.push(null); - Object.assign(s, req); - return s; -}; - -module.exports = (context, req) => { - const form = new formidable.IncomingForm(); - form.parse(streamify(req), (err, fields, files) => { - /* grab the first file */ - var f = Object.values(files)[0]; - if(!f) { - context.res = { status: 400, body: "Must submit a file for processing!" }; - } else { - /* file is stored in a temp directory, so we can point to that and read it */ - const wb = XLSX.read(f.path, {type:"file"}); - - /* convert to specified output type -- default CSV */ - const ext = (fields.bookType || "csv").toLowerCase(); - const out = XLSX.write(wb, {type:"string", bookType:ext}); - - context.res = { - status: 200, - headers: { "Content-Disposition": `attachment; filename="download.${ext}";` }, - body: out - }; - } - context.done(); - }); -}; diff --git a/demos/function/Firebase/.gitignore b/demos/function/Firebase/.gitignore deleted file mode 100644 index f9ed3da..0000000 --- a/demos/function/Firebase/.gitignore +++ /dev/null @@ -1,65 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -firebase-debug.log* - -# Firebase cache -.firebase/ - -# Firebase config - -# Uncomment this if you'd like others to create their own Firebase project. -# For a team working on the same Firebase project(s), it is recommended to leave -# it commented so all members can deploy to the same project(s) in .firebaserc. -.firebaserc - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env diff --git a/demos/function/Firebase/firebase.json b/demos/function/Firebase/firebase.json deleted file mode 100644 index 0967ef4..0000000 --- a/demos/function/Firebase/firebase.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/demos/function/Firebase/functions/.gitignore b/demos/function/Firebase/functions/.gitignore deleted file mode 100644 index 40b878d..0000000 --- a/demos/function/Firebase/functions/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ \ No newline at end of file diff --git a/demos/function/Firebase/functions/index.js b/demos/function/Firebase/functions/index.js deleted file mode 100644 index d14b499..0000000 --- a/demos/function/Firebase/functions/index.js +++ /dev/null @@ -1,39 +0,0 @@ -const functions = require('firebase-functions'); -const Busboy = require('busboy'); -const XLSX = require('xlsx'); - -// // Create and Deploy Your First Cloud Functions -// // https://firebase.google.com/docs/functions/write-firebase-functions -// -exports.helloWorld = functions.https.onRequest((request, response) => { - response.send("Hello from Firebase!"); -}); - -exports.main = functions.https.onRequest((req, res) => { - var bb = new Busboy({ - headers: { - 'content-type': req.headers['content-type'] - } - }); - let fields = {}; - let files = {}; - bb.on('field', (fieldname, val) => { - fields[fieldname] = val; - }); - bb.on('file', (fieldname, file, filename) => { - var buffers = []; - file.on('data', (data) => { - buffers.push(data); - }); - file.on('end', () => { - files[fieldname] = [Buffer.concat(buffers), filename]; - }); - }); - bb.on('finish', () => { - let f = files[Object.keys(files)[0]]; - const wb = XLSX.read(f[0], { type: "buffer" }); - // Convert to CSV - res.send(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); - }); - bb.end(req.body) -}); diff --git a/demos/function/Firebase/functions/package.json b/demos/function/Firebase/functions/package.json deleted file mode 100644 index 53238b4..0000000 --- a/demos/function/Firebase/functions/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "functions", - "description": "Cloud Functions for Firebase", - "scripts": { - "serve": "firebase emulators:start --only functions", - "shell": "firebase functions:shell", - "start": "npm run shell", - "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log" - }, - "engines": { - "node": "8" - }, - "dependencies": { - "busboy": "^0.3.1", - "firebase-admin": "^8.6.0", - "firebase-functions": "^3.3.0", - "xlsx": "^0.16.2" - }, - "devDependencies": { - "firebase-functions-test": "^0.1.6" - }, - "private": true -} diff --git a/demos/function/LambdaProxy/index.js b/demos/function/LambdaProxy/index.js deleted file mode 100644 index 5d82404..0000000 --- a/demos/function/LambdaProxy/index.js +++ /dev/null @@ -1,40 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -/* eslint-env node */ -// base64 sheetjs.xlsb | curl -F "data=@-;filename=test.xlsb" http://localhost:3000/LambdaProxy - -'use strict'; -var XLSX = require('xlsx'); -var Busboy = require('busboy'); - -exports.handler = function(event, context, callback) { - /* set up busboy */ - var ctype = event.headers['Content-Type']||event.headers['content-type']; - var bb = new Busboy({headers:{'content-type':ctype}}); - - /* busboy is evented; accumulate the fields and files manually */ - var fields = {}, files = {}; - bb.on('error', function(err) { console.log('err', err); callback(err); }); - bb.on('field', function(fieldname, val) {fields[fieldname] = val }); - bb.on('file', function(fieldname, file, filename) { - /* concatenate the individual data buffers */ - var buffers = []; - file.on('data', function(data) { buffers.push(data); }); - file.on('end', function() { files[fieldname] = [Buffer.concat(buffers), filename]; }); - }); - - /* on the finish event, all of the fields and files are ready */ - bb.on('finish', function() { - /* grab the first file */ - var f = files[Object.keys(files)[0]]; - if(!f) callback(new Error("Must submit a file for processing!")); - - /* f[0] is a buffer, convert to string and interpret as Base64 */ - var wb = XLSX.read(f[0].toString(), {type:"base64"}); - - /* grab first worksheet and convert to CSV */ - var ws = wb.Sheets[wb.SheetNames[0]]; - callback(null, { body: XLSX.utils.sheet_to_csv(ws) }); - }); - - bb.end(event.body); -}; diff --git a/demos/function/LambdaProxy/template.yaml b/demos/function/LambdaProxy/template.yaml deleted file mode 100644 index 43ef45a..0000000 --- a/demos/function/LambdaProxy/template.yaml +++ /dev/null @@ -1,18 +0,0 @@ -AWSTemplateFormatVersion : '2010-09-09' -Transform: AWS::Serverless-2016-10-31 - -Description: Sample Lambda API Gateway Normalizer -Resources: - LambdaProxy: - Type: AWS::Serverless::Function - Properties: - Runtime: nodejs6.10 - Handler: index.handler - BinaryMediaTypes: '*/*' - Events: - Api: - Type: Api - Properties: - Path: /LambdaProxy - Method: any - BinaryMediaTypes: '*/*' diff --git a/demos/function/Makefile b/demos/function/Makefile deleted file mode 100644 index 1588319..0000000 --- a/demos/function/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -.PHONY: aws -aws: lambda-proxy - -.PHONY: lambda-proxy -lambda-proxy: - cd LambdaProxy; mkdir -p node_modules; npm install https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz busboy; sam local start-api; cd - - -.PHONY: init-azure -init-azure: - cd AzureHTTPTrigger; mkdir -p node_modules; npm install https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz formidable fs - -.PHONY: azure -azure: init-azure - func start - -.PHONY: azure-server -azure-server: - mkdir -p /tmp/azurite - azurite -l /tmp/azurite - -FILES=$(filter-out xlsx.full.min.js,$(wildcard *.js)) $(wildcard *.html) -.PHONY: lint -lint: $(FILES) - eslint $(FILES) - -.PHONY: clean -clean: - rm -f *.db *.xlsx *.csv diff --git a/demos/function/README.md b/demos/function/README.md index c5cd76a..238afbc 100644 --- a/demos/function/README.md +++ b/demos/function/README.md @@ -1,113 +1,9 @@ # "Serverless" Functions -Because the library is pure JS, the hard work of reading and writing files can -be performed in the client browser or on the server side. On the server side, -the mechanical process is essentially independent from the data parsing or -generation. As a result, it is sometimes sensible to organize applications so -that the "last mile" conversion between JSON data and spreadsheet files is -independent from the main application. +Cloud services are covered in separate demos: -The straightforward architecture would split off the JSON data conversion as a -separate microservice or application. Since it is only needed when an import or -export is requested, and since the process itself is relatively independent from -the rest of a typical service, a "Serverless" architecture makes a great fit. -Since the "function" is separate from the rest of the application, it can be -integrated into a platform built in Java or Go or Python or another language! +- [AWS](https://docs.sheetjs.com/docs/demos/aws) +- [Azure](https://docs.sheetjs.com/docs/demos/azure) -This demo discusses general architectures and provides examples for popular -commercial systems and self-hosted alternatives. The examples are merely -intended to demonstrate very basic functionality. +[](https://github.com/SheetJS/js-xlsx) - -## Simple Strategies - -#### Data Normalization - -Most programming languages and platforms can process CSV or JSON but can't use -XLS or XLSX or XLSB directly. Form data from an HTTP POST request can be parsed -and contained files can be converted to CSV or JSON. The `XLSX.stream.to_csv` -utility can stream rows to a standard HTTP response. `XLSX.utils.sheet_to_json` -can generate an array of objects that can be fed to another service. - -At the simplest level, a file on the filesystem can be converted using the bin -script that ships with the NodeJS package: - -```bash -$ xlsx /path/to/uploads/file > /tmp/new_csv_file -``` - -From a utility script, workbooks can be converted in two lines: - -```js -var workbook = XLSX.readFile("path/to/file.xlsb"); -XLSX.writeFile(workbook, "output/path/file.csv"); -``` - -#### Report Generation - -For an existing platform that already generates JSON or CSV or HTML output, the -library can process the data and generate a new file with embellishments. The -`XLSX.utils.sheet_add_json` and `XLSX.utils.sheet_add_aoa` functions can add -data rows to an existing worksheet: - -```js -var ws = XLSX.utils.aoa_to_sheet([ - ["Company Report"], - [], - ["Item", "Cost"] -]); -XLSX.utils.sheet_add_json(ws, [ - { item: "Coffee", cost: 5 }, - { item: "Cake", cost: 20 } -], { skipHeader: true, origin: -1, header: ["item", "cost"] }); -``` - - -## Deployment Targets - -The library is supported in Node versions starting from `0.8` as well as a -myriad of ES3 and ES5 compatible JS engines. All major services use Node -versions beyond major release 4, so there should be no problem directly using -the library in those environments. - -Note that most cloud providers proactively convert form data to UTF8 strings. -This is especially problematic when dealing with XLSX and XLSB files, as they -naturally contain codes that are not valid UTF8 characters. As a result, these -demos specifically handle Base64-encoded files only. To test on the command -line, use the `base64` tool to encode data before piping to `curl`: - -``` -base64 test.xlsb | curl -F "data=@-;filename=test.xlsb" http://localhost/ -``` - -#### AWS Lambda - -Through the AWS Gateway API, Lambda functions can be triggered on HTTP requests. -The `LambdaProxy` example reads files from form data and converts to CSV. - -When deploying on AWS, be sure to `npm install` locally and include the modules -in the ZIP file. - -When reading form data, be sure to include the necessary binary types on the AWS API Gateway console. -To do this, navigate to the "Binary Media Types" section in the settings tab of the console. -For reading a file, you may need to add `"multipart/form-data"`. -For downloading a file, you may need to add `"application/vnd.ms-excel"`. - -#### Azure Functions - -Azure supports many types of triggers. The `AzureHTTPTrigger` shows an example -HTTP trigger that converts the submitted file to CSV. - -When deploying on Azure, be sure to install the module from the remote console, -as described in the "Azure Functions JavaScript developer guide". - -#### Firebase Functions - -Firebase functions can be triggered via HTTP requests, similar to a REST API. -In the `Firebase` directory, the example function reads files sent through -HTTP and converts it to a CSV and sends the response in the form of a string. - -To run this demo locally, run `npm i -g firebase-tools` to install the -Firebase CLI and `npm i` to install the dependencies, then `firebase use --add` -to connect to an existing Firebase project. Run `firebase emulators:start` to -start the local server. diff --git a/demos/function/host.json b/demos/function/host.json deleted file mode 100644 index 81e35b7..0000000 --- a/demos/function/host.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": "2.0" -} \ No newline at end of file diff --git a/demos/function/local.settings.json b/demos/function/local.settings.json deleted file mode 100644 index 755966e..0000000 --- a/demos/function/local.settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "AzureWebJobsDashboard": "UseDevelopmentStorage=true" - }, - "Host": { - "LocalHttpPort": 7262, - "CORS": "*" - } -} diff --git a/demos/react/.gitignore b/demos/react/.gitignore deleted file mode 100644 index 228ee28..0000000 --- a/demos/react/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -SheetJS -.next -static/shim.js -public/shim.js diff --git a/demos/react/Makefile b/demos/react/Makefile deleted file mode 100644 index da556c3..0000000 --- a/demos/react/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -.PHONY: react -react: init ## Simple server for react and clones - python -mSimpleHTTPServer - -.PHONY: next -next: init ## next.js demo - next - -.PHONY: native -native: ## Build react-native project - bash ./native.sh - -.PHONY: ios -ios: native ## react-native ios sim - cd SheetJS; cd ios; pod install; cd -; react-native run-ios --simulator="iPhone X"; cd - - -.PHONY: android -android: native ## react-native android sim - cd SheetJS; react-native run-android; cd - - -.PHONY: init -init: ## set up node_modules and symlink - mkdir -p node_modules - cd node_modules; if [ ! -e xlsx ]; then ln -s ../../../ xlsx; fi; cd - diff --git a/demos/react/NOTES.md b/demos/react/NOTES.md deleted file mode 100644 index ca6238e..0000000 --- a/demos/react/NOTES.md +++ /dev/null @@ -1,22 +0,0 @@ -# Additional Notes - -## Java, React Native, Gradle versions - -This demo was tested and runs with React Native 0.62.2, Java 11, and Gradle -3.5.2. Running `make native` will invoke `native.sh`, which uses a fixed version -of React Native 0.62.2 to build and run the demo. - -Make sure you have the correct version of Java (11) installed, since 0.62.2 might -not work with newer versions of Java. - -## Common Issues - -``` -ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -``` - -Add `export JAVA_HOME=<directory>`, replacing `<directory>` with the location of -your Java install, to your `.bashrc` or any other shell that you are using. - - - diff --git a/demos/react/README.md b/demos/react/README.md index ffee578..428f391 100644 --- a/demos/react/README.md +++ b/demos/react/README.md @@ -1,154 +1,12 @@ # React -The `xlsx.core.min.js` and `xlsx.full.min.js` scripts are designed to be dropped -into web pages with script tags: +[The new demo](https://docs.sheetjs.com/docs/demos/react) has an updated +exposition for legacy and modern deployments alike. -```html -<script src="xlsx.full.min.js"></script> -``` +The ecosystem demos were grouped by type in the new demo site: -The library can also be imported directly from JSX code with: - -```js -import { read, utils, writeFileXLSX } from 'xlsx'; -``` - -This demo shows a simple React component transpiled in the browser using Babel -standalone library. Since there is no standard React table model, this demo -settles on the array of arrays approach. - - -Other scripts in this demo show: -- server-rendered React component (with `next.js`) -- `react-native` deployment for iOS and android -- [`react-data-grid` reading, modifying, and writing files](modify/) - -## How to run - -Run `make react` to run the browser demo for React, or run `make next` to run -the server-rendered demo using `next.js`. - -## Internal State - -The simplest state representation is an array of arrays. To avoid having the -table component depend on the library, the column labels are precomputed. The -state in this demo is shaped like the following object: - -```js -{ - cols: [{ name: "A", key: 0 }, { name: "B", key: 1 }, { name: "C", key: 2 }], - data: [ - [ "id", "name", "value" ], - [ 1, "sheetjs", 7262 ], - [ 2, "js-xlsx", 6969 ] - ] -} -``` - -`sheet_to_json` and `aoa_to_sheet` utility functions can convert between arrays -of arrays and worksheets: - -```js -/* convert from workbook to array of arrays */ -var first_worksheet = workbook.Sheets[workbook.SheetNames[0]]; -var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1}); - -/* convert from array of arrays to workbook */ -var worksheet = XLSX.utils.aoa_to_sheet(data); -var new_workbook = XLSX.utils.book_new(); -XLSX.utils.book_append_sheet(new_workbook, worksheet, "SheetJS"); -``` - -The column objects can be generated with the `encode_col` utility function: - -```js -function make_cols(refstr/*:string*/) { - var o = []; - var range = XLSX.utils.decode_range(refstr); - for(var i = 0; i <= range.e.c; ++i) { - o.push({name: XLSX.utils.encode_col(i), key:i}); - } - return o; -} -``` - -## React Native - -[The new demo](https://docs.sheetjs.com/docs/demos/mobile#react-native) uses -up-to-date file I/O and file picker libraries. - -## Server-Rendered React Components with Next.js - -The demo reads from `public/sheetjs.xlsx`. HTML output is generated using -`XLSX.utils.sheet_to_html` and inserted with `dangerouslySetInnerHTML`: - -```jsx -export default function Index({html, type}) { return ( - // ... - <div dangerouslySetInnerHTML={{ __html: html }} /> - // ... -); } -``` - -Next currently offers 3 general strategies for server-side data fetching: - -#### "Server-Side Rendering" using `getServerSideProps` - -`/getServerSideProps` reads the file on each request. The first worksheet is -converted to HTML: - -```js -export async function getServerSideProps() { - const wb = XLSX.readFile(path); - return { props: { - html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]) - }}; -} -``` - -#### "Static Site Generation" using `getStaticProps` - -`/getServerSideProps` reads the file at build time. The first worksheet is -converted to HTML: - -```js -export async function getStaticProps() { - const wb = XLSX.readFile(path); - return { props: { - html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]) - }}; -} -``` - -#### "Static Site Generation with Dynamic Routes" using `getStaticPaths` - -`/getStaticPaths` reads the file at build time and generates a list of sheets. - -`/sheets/[id]` uses `getStaticPaths` to generate a path per sheet index: - -```js -export async function getStaticPaths() { - const wb = XLSX.readFile(path); - return { - paths: wb.SheetNames.map((name, idx) => ({ params: { id: idx.toString() } })), - fallback: false - }; -} -``` - -It also uses `getStaticProps` for the actual HTML generation: - -```js -export async function getStaticProps(ctx) { - const wb = XLSX.readFile(path); - return { props: { - html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[ctx.params.id]]), - }}; -} -``` - -## Additional Notes - -Some additional notes can be found in [`NOTES.md`](NOTES.md). +- [server-rendered React components with `next.js`](https://docs.sheetjs.com/docs/demos/content#nextjs) is now part of "Content and Site Generation" +- [`react-native` deployment for iOS and android](https://docs.sheetjs.com/docs/demos/mobile#react-native) is now part of "iOS and Android Apps" +- [`react-data-grid` reading, modifying, and writing files](https://docs.sheetjs.com/docs/demo/grid#react-data-grid) is now part of "Data Grids and UI" [](https://github.com/SheetJS/js-xlsx) diff --git a/demos/react/index.html b/demos/react/index.html deleted file mode 100644 index 8487db4..0000000 --- a/demos/react/index.html +++ /dev/null @@ -1,29 +0,0 @@ -<!DOCTYPE html> -<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com --> -<!-- vim: set ts=2: --> -<html lang="en" style="height: 100%"> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -<title>SheetJS React Demo</title> -<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> -<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> -<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script> -<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script> -<script src="node_modules/xlsx/dist/shim.min.js"></script> -<script src="node_modules/xlsx/dist/xlsx.full.min.js"></script> -<style>body, #app { height: 100%; };</style> -</head> -<body> -<div class="container-fluid"> -<h1><a href="http://sheetjs.com">SheetJS React Demo</a></h1> -<br /> -<a href="https://github.com/SheetJS/js-xlsx">Source Code Repo</a><br /> -<a href="https://github.com/SheetJS/js-xlsx/issues">Issues? Something look weird? Click here and report an issue</a><br /><br /> -</div> -<div id="app" class="container-fluid"></div> -<script type="text/babel" src="sheetjs.js"></script> -<script type="text/babel"> - ReactDOM.render( <SheetJSApp />, document.getElementById('app') ); -</script> -</body> -</html> diff --git a/demos/react/modify/.gitignore b/demos/react/modify/.gitignore deleted file mode 100644 index 4d29575..0000000 --- a/demos/react/modify/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/demos/react/modify/README.md b/demos/react/modify/README.md deleted file mode 100644 index 97180e0..0000000 --- a/demos/react/modify/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# react-modify - -This demo shows import and export with the `react-data-grid` table component. - -In the project directory, you can run: - -```bash -$ npm install -$ npm start -``` diff --git a/demos/react/modify/package.json b/demos/react/modify/package.json deleted file mode 100644 index dde42e3..0000000 --- a/demos/react/modify/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "react-modify-demo", - "dependencies": { - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", - "react": "^17.0.2", - "react-data-grid": "^7.0.0-beta.11", - "react-dom": "^17.0.2", - "react-scripts": "4.0.3", - "typescript": "^4.1.2", - "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/demos/react/modify/public/index.html b/demos/react/modify/public/index.html deleted file mode 100644 index aa069f2..0000000 --- a/demos/react/modify/public/index.html +++ /dev/null @@ -1,43 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <meta name="theme-color" content="#000000" /> - <meta - name="description" - content="Web site created using create-react-app" - /> - <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> - <!-- - manifest.json provides metadata used when your web app is installed on a - user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ - --> - <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> - <!-- - Notice the use of %PUBLIC_URL% in the tags above. - It will be replaced with the URL of the `public` folder during the build. - Only files inside the `public` folder can be referenced from the HTML. - - Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will - work correctly both with client-side routing and a non-root public URL. - Learn how to configure a non-root public URL by running `npm run build`. - --> - <title>React App</title> - </head> - <body> - <noscript>You need to enable JavaScript to run this app.</noscript> - <div id="root"></div> - <!-- - This HTML file is a template. - If you open it directly in the browser, you will see an empty page. - - You can add webfonts, meta tags, or analytics to this file. - The build step will place the bundled scripts into the <body> tag. - - To begin the development, run `npm start` or `yarn start`. - To create a production bundle, use `npm run build` or `yarn build`. - --> - </body> -</html> diff --git a/demos/react/modify/src/components/App.tsx b/demos/react/modify/src/components/App.tsx deleted file mode 100644 index 7e979d5..0000000 --- a/demos/react/modify/src/components/App.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { useState, ChangeEvent } from "react"; -import DataGrid, { TextEditor } from "react-data-grid"; -import { read, utils, WorkSheet, writeFile } from "xlsx"; - -import "../styles/App.css"; - -type Row = any[]; /*{ - [index: string]: string | number; -};*/ - -type Column = { - key: string; - name: string; - editor: typeof TextEditor; -}; - -type DataSet = { - [index: string]: WorkSheet; -}; - -function getRowsCols( - data: DataSet, - sheetName: string -): { - rows: Row[]; - columns: Column[]; -} { - const rows: Row[] = utils.sheet_to_json(data[sheetName], {header:1}); - let columns: Column[] = []; - - for (let row of rows) { - const keys: string[] = Object.keys(row); - - if (keys.length > columns.length) { - columns = keys.map((key) => { - return { key, name: utils.encode_col(+key), editor: TextEditor }; - }); - } - } - - return { rows, columns }; -} - -export default function App() { - const [rows, setRows] = useState<Row[]>([]); - const [columns, setColumns] = useState<Column[]>([]); - const [workBook, setWorkBook] = useState<DataSet>({} as DataSet); - const [sheets, setSheets] = useState<string[]>([]); - const [current, setCurrent] = useState<string>(""); - - const exportTypes = ["xlsx", "xlsb", "csv", "html"]; - - function selectSheet(name: string, reset = true) { - if(reset) workBook[current] = utils.json_to_sheet(rows, { - header: columns.map((col: Column) => col.key), - skipHeader: true - }); - - const { rows: new_rows, columns: new_columns } = getRowsCols(workBook, name); - - setRows(new_rows); - setColumns(new_columns); - setCurrent(name); - } - - async function handleFile(ev: ChangeEvent<HTMLInputElement>): Promise<void> { - const file = await ev.target.files?.[0]?.arrayBuffer(); - const data = read(file); - - setWorkBook(data.Sheets); - setSheets(data.SheetNames); - } - - function saveFile(ext: string): void { - const wb = utils.book_new(); - - sheets.forEach((n) => { - utils.book_append_sheet(wb, workBook[n], n); - }); - - writeFile(wb, "sheet." + ext); - } - - return ( - <> - <input type="file" onChange={handleFile} /> - <div className="flex-cont"> - {sheets.map((sheet) => ( - <button key={sheet} onClick={(e) => selectSheet(sheet)}> - {sheet} - </button> - ))} - </div> - <div className="flex-cont"> - <b>Current Sheet: {current}</b> - </div> - <DataGrid columns={columns} rows={rows} onRowsChange={setRows} /> - <div className="flex-cont"> - {exportTypes.map((ext) => ( - <button key={ext} onClick={() => saveFile(ext)}> - export [.{ext}] - </button> - ))} - </div> - </> - ); -} diff --git a/demos/react/modify/src/index.tsx b/demos/react/modify/src/index.tsx deleted file mode 100644 index 270ed1a..0000000 --- a/demos/react/modify/src/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './components/App'; - -import './styles/index.css'; - -ReactDOM.render( - <React.StrictMode> - <App/> - </React.StrictMode>, - document.getElementById('root') -); diff --git a/demos/react/modify/src/react-app-env.d.ts b/demos/react/modify/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5..0000000 --- a/demos/react/modify/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// <reference types="react-scripts" /> diff --git a/demos/react/modify/src/styles/App.css b/demos/react/modify/src/styles/App.css deleted file mode 100644 index 28b9df9..0000000 --- a/demos/react/modify/src/styles/App.css +++ /dev/null @@ -1,14 +0,0 @@ -input { - margin: 0.5rem; -} - -.flex-cont { - display: flex; - margin: 0.5rem; - justify-content: center; -} - -.flex-cont button { - margin: 0.3rem; - padding: 0.2rem; -} diff --git a/demos/react/modify/src/styles/index.css b/demos/react/modify/src/styles/index.css deleted file mode 100644 index 1532074..0000000 --- a/demos/react/modify/src/styles/index.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} diff --git a/demos/react/modify/tsconfig.json b/demos/react/modify/tsconfig.json deleted file mode 100644 index 5136b52..0000000 --- a/demos/react/modify/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - }, - "include": [ - "src" - ] -} diff --git a/demos/react/nexthdr.js b/demos/react/nexthdr.js deleted file mode 100644 index 1ba3dc6..0000000 --- a/demos/react/nexthdr.js +++ /dev/null @@ -1,3 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -import * as XLSX from 'xlsx'; -import React from 'react'; diff --git a/demos/react/pages/getServerSideProps.js b/demos/react/pages/getServerSideProps.js deleted file mode 100644 index 55cd11f..0000000 --- a/demos/react/pages/getServerSideProps.js +++ /dev/null @@ -1,32 +0,0 @@ -import Head from 'next/head'; -import { readFile, utils } from 'xlsx'; -import { join } from 'path'; -import { cwd } from 'process'; - -export default function Index({html, type}) { return ( -<div> - <Head> - <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> - <title>SheetJS Next.JS {type} Demo</title> - <script src="/shim.js"></script> - <style jsx>{` - body, #app { height: 100%; }; - `}</style> - </Head> - <pre> -<h3>SheetJS Next.JS {type} Demo</h3> -This demo reads from /public/sheetjs.xlsx and generates HTML from the first sheet. - </pre> - <div dangerouslySetInnerHTML={{ __html: html }} /> -</div> -); } - -export async function getServerSideProps() { - const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")) - return { - props: { - type: "getStaticProps", - html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]), - }, - } -} \ No newline at end of file diff --git a/demos/react/pages/getStaticPaths.js b/demos/react/pages/getStaticPaths.js deleted file mode 100644 index 3852295..0000000 --- a/demos/react/pages/getStaticPaths.js +++ /dev/null @@ -1,38 +0,0 @@ -import Head from 'next/head'; -import Link from "next/link"; -import { readFile, utils } from 'xlsx'; -import { join } from 'path'; -import { cwd } from 'process'; - -export default function Index({snames, type}) { return ( -<div> - <Head> - <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> - <title>SheetJS Next.JS {type} Demo</title> - <script src="/shim.js"></script> - <style jsx>{` - body, #app { height: 100%; }; - `}</style> - </Head> - <pre> -<h3>SheetJS Next.JS {type} Demo</h3> -This demo reads from /public/sheetjs.xlsx. Each worksheet maps to a path:<br/><br/> -{snames.map((sname, idx) => (<> - <Link key={idx} href="/sheets/[id]" as={`/sheets/${idx}`}><a>{`Sheet index=${idx} name="${sname}"`}</a></Link> - <br/> - <br/> -</>))} - - </pre> -</div> -); } - -export async function getStaticProps() { - const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")) - return { - props: { - type: "getStaticPaths", - snames: wb.SheetNames, - }, - } -} \ No newline at end of file diff --git a/demos/react/pages/getStaticProps.js b/demos/react/pages/getStaticProps.js deleted file mode 100644 index ca2e304..0000000 --- a/demos/react/pages/getStaticProps.js +++ /dev/null @@ -1,32 +0,0 @@ -import Head from 'next/head'; -import { readFile, utils } from 'xlsx'; -import { join } from 'path'; -import { cwd } from 'process'; - -export default function Index({html, type}) { return ( -<div> - <Head> - <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> - <title>SheetJS Next.JS {type} Demo</title> - <script src="/shim.js"></script> - <style jsx>{` - body, #app { height: 100%; }; - `}</style> - </Head> - <pre> -<h3>SheetJS Next.JS {type} Demo</h3> -This demo reads from /public/sheetjs.xlsx and generates HTML from the first sheet. - </pre> - <div dangerouslySetInnerHTML={{ __html: html }} /> -</div> -); } - -export async function getStaticProps() { - const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")) - return { - props: { - type: "getStaticProps", - html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]), - }, - } -} \ No newline at end of file diff --git a/demos/react/pages/index.js b/demos/react/pages/index.js deleted file mode 100644 index c93bccd..0000000 --- a/demos/react/pages/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import Head from 'next/head'; - -export default function Index() { return ( -<div> - <Head> - <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> - <title>SheetJS Next.JS Demo</title> - <script src="/shim.js"></script> - <style jsx>{` - body, #app { height: 100%; }; - `}</style> - </Head> - <pre> -<h3>SheetJS Next.JS Demos</h3> -All demos read from /public/sheetjs.xlsx.<br/> -<br/> -- <a href="/getStaticProps">getStaticProps</a><br/> -<br/> -- <a href="/getServerSideProps">getServerSideProps</a><br/> -<br/> -- <a href="/getStaticPaths">getStaticPaths</a><br/> - </pre> -</div> -); } \ No newline at end of file diff --git a/demos/react/pages/sheets/[id].js b/demos/react/pages/sheets/[id].js deleted file mode 100644 index 530b675..0000000 --- a/demos/react/pages/sheets/[id].js +++ /dev/null @@ -1,51 +0,0 @@ -import Head from 'next/head'; -import { readFile, utils } from 'xlsx'; -import { join } from 'path'; -import { cwd } from 'process'; - -export default function Index({html, type, name}) { return ( -<div> - <Head> - <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> - <title>SheetJS Next.JS {type} Demo</title> - <script src="/shim.js"></script> - <style jsx>{` - body, #app { height: 100%; }; - `}</style> - </Head> - <pre> -<h3>SheetJS Next.JS {type} Demo</h3> -This demo reads from /public/sheetjs.xlsx.<br/> -<br/> -<b>{name}</b> - </pre> - <div dangerouslySetInnerHTML={{ __html: html }} /> -</div> -); } - -let cache = []; - -export async function getStaticProps(ctx) { - if(!cache || !cache.length) { - const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")); - cache = wb.SheetNames.map((name) => ({ name, sheet: wb.Sheets[name] })); - } - const entry = cache[ctx.params.id]; - return { - props: { - type: "getStaticPaths", - name: entry.name, - id: ctx.params.id.toString(), - html: entry.sheet ? utils.sheet_to_html(entry.sheet) : "", - }, - } -} - -export async function getStaticPaths() { - const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")); - cache = wb.SheetNames.map((name) => ({ name, sheet: wb.Sheets[name] })); - return { - paths: wb.SheetNames.map((name, idx) => ({ params: { id: idx.toString() } })), - fallback: false, - }; -} diff --git a/demos/react/public/sheetjs.xlsx b/demos/react/public/sheetjs.xlsx deleted file mode 100644 index 25496b9bf02e58b00d3610e257610798bf251d21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10328 zcmeHNWl&sM+HO3!ySqEVL$Kfk*AU#@3GR)11HoN_ySuvv4-g~-2$JB=*U9ee%rLW6 z`|JC&d%EhJ>bm!R>YjU_*N>ts6f_n97Vr!J0FVQ`tl#5UKmY*QFaQ81;2Go_F*{pl z6I*9JRd;(6r?)KcY^=$0p&{wA0g&MP|2O`N-@q7X-Li)bYv35pGbDaxTAVnNy-fFD z@(az_3*q_nW&PBk^xd;VHv*T)Y5Lr%Rj#_>>kd7IcspL*=QM9bg%7Q}Z`uYqstQz= zClD^Uy+`b{fJ)lbQMHOy&&dd5SXhCx^>Aaw%ZA<ic=s7-n)6dq+Y^BWDI>uW5+2B$ z8zy|ooM?f%#}c3ki7NJ(a#%uS((HId%-TV0@3mNHwxOEa@t*lo{Ta_ZS^O|nrt+JO zcspWVwZi9`Rxuhu-Qq=J0;jFr4M)M+m~{cx6k=XU$OfT_=%g%T>ltaM_Y}mSiV|n| zk;Diff7dizEP|EaB3`D%nv{*(C(pZCLoAEkEd2cYPAV^|!OvOtI*;g*`KPE9FBI0} zshsqo*eZ}eH^&%Yyx7(m1?)eEl?ktf@>*CM6uC6VNSBlW0S4U+aPe(ij-;Q7e*BJ2 z@HY8)G;U^hyFo0?4M8=eQR<ri1{wf(e1rlh{z*gY)z~P{z_gPCD<C3RL-ia@tesd{ ze$M~X%>QC_{KMAE;^h^3*^q$8Qg<OkmvgJJXri)iB2um7sy=?wpHS<g@~8+`J86l~ zR0#s1Bz@a`?nYKt1*7(d$S*dz$|5kZ1SlKa%7Mu@4lZyEG>%CU4rLpC=&p0;a~CO+ zGVXM)?J*3cO~pB~!|Rk1vp>YE(I#0m2;eabh=4dRQv9_C<h9leuPPwsL{$#UfmMw> z+4~8TX}$|dg?q>^L;2(mW>T<+oD9vDD!hiQ$<MC|)Ktv*EUFAM9r-ET^^C2%PQ}wY zG4H*YU#AZ#Q*dEiaZE}MQfHt0YS(f77|n3+<ALuj9r`{T5J6hJ26k3|RgyLMdpbz) zm5)#W00H0`#5-%&f9Q#uouieZot@QBNA{b}K!Cj&_}>5S(Uvf23FgeeabSDkw6{aN ztqF*~JyN9d%_V?FX>+QNJL!Vf?qz~li|{Z@4sIaTo%hSBqWg`a<33zi%M7(xYWin& z1*O_6bXUCXLV|&0^9?f@wZ;0v0b&lmjlR9Fo$ka7|5sbPRi$NBj)`jzw!BsS?E zS<X_8@Co)@+~<);1mzn!?&kaq3+ba11MJJV$zBJ>Ogvh(w%E#gIeamTNvhNwvcsJ) zM&k?LpCV&?uT3>LOcT~eR@uAF0~}7)uOaL;mStOcZZNd#HzyGCUbLmma$YyUT^k!1 z!UFU`K#Dq#zu!u|AX0#5Ju;#nbXyKdUp4%J_;(B%N4a;~0h?HIH~;_@ObsxD{>Ytt zwH4buHZ*TNi${p-i~#>niqdmb*%Fo|mT5L5jBx4t*lZj$<Rv%90zlz3)B5lhy6PWe zS65@>I_s(yxrfDcc$0!bm2&yhOxx(A4J0@1NN40T2+}p7RDyXV{8Nytk-iU2Yfe8} zcJ9=m{K~#7GMO9^NT3gJQTJm{TEM0v<I=}fVZF&4f}vJIS7Dg#tCFj=%MUUs7P0TA z#`CH*AkX+wC1*iHh$ezb;qQZ9+h#sJ^BtoJ6uQ8%AeOlUs5XU92qD0-u-Khw#K$$x z@LRz?vPksPBYjv5>b6vXoLf?&M}%R$lF)^yn~oJaB~61Jpd~M!FtW#wH+t4$#1KNv zIQ>Xd>Ju{e2<YAM%`>1YZ&BBIw#GB<LCk{~RmsDYGbz7kNi|6-Rl<=-#!Mc3MoD9_ zoddyiotgPeJVik!$q%P@i_GX#;%VL#KwbF<-C}eQ9GeKi8I;XbGCQOC;E2Ww!$w1# zFGw`-^7??}wJ(LhXGjvsLFJ6n-h*T&EEXSk!lg!)^-KBHtVI>#HxuPTj<%jHTS$=W zT!+*8;p^l&xyQhC5Qk99hwK;IehOwKFdwv$xmV*~k#en#ZRM3-k528cB?^?}WZ|#k zl0qoSmTOltZQd^D^%h-vGwW7M4Up7?M8lKt)Ci0;u-n%Q!-`_aG~~(FRBXY+WaD}4 z1}&8t;B@7cZUm+L2-Efs2K0N(*YF=I>}&h0&T(K{6I%4BnT5IsCEA(IPjzS1pAIkI zSb7iUhKicG%v%;wVQ>(Us;w$DEL+zhiNyygDp{5w>IcbN<ujR6A0ui_YSZJ18_p2E zCi{_e1kF1|EXZ*_oPWIAuBCqo`;PnE)1Lv}Wu+xi^pjU@7*L`;4MPi|X*?(NTYc%k zXS&0nGzt62lVO|fhtxUdse^~)4NNb2438X#?_N0@X9m?eI_93ota|Dq8EKs!R>GoW zb~f6P7vH!az-IHWMwJaU)anox09YgbnH&6431@Q?8xz)FGy6}IdY~n1N63TLfxGKV zc9E%BLp{h%0hh!?ZL`@|m59Zs-1?El=*Uq-xxs}o-pC*F)ogAuWc#^d9daz7J->6n zCY&4Q1ec#mqqN}Fbt1I)D>zTmb)>K3_(yYAA@pLm;!-2E>WZB@h-ky2B-2c`FmAW) zvWyV%2xjP}f|TX@a>n*H_xG0-o6i$tL1fvJ+XS{SVanIbJpo7?>`BS)uQ}fFY*X|j z*uH8*gZBb>sWen+mNK*Ezc+;#u}82zMhfAgDz4Om{f4>o7Pc`Z{zhb|ufkNG2q7@) zVxNF~u%;J!d9Vz-iy!^vZh{y#8L%J#w>;a_r@QB74yH{Vjs1B@gXjWhXFvs6=mpMr zDg_BBV%{@qjxmke7fRm}XW`IQVAW%I5@3%PP^g~7FezbIu$K9~%<td})A8$aEvu$0 zgvSE>6pfWbSEu^eevap4o=fk$D?cU?5=Nk``7w3y1=`i|@MoC7*$Lxx3I-m()p~2& z^bSe}Z~Ve^YB{G}x4S(EiLZ4dxDw!1px#wP7YgKPIl~ak{NP@lv<atk2FUhDVoW7} zJ~50Ig4R!*jso590nsL5|0T*BYa~U|gPBT9#AyyeXpG&s542wTb-mN$0ju5PVS9I@ zHZSBQ@e2><v+t6lqvUP=wM{ZtCo7W=cQ;07gR^$JLJvJL$!d3%FTGAiC$nz-V?|WL zbiJ;(8_g7Sz0UWJ*I!(8@j#QPZg{6+gxMxQ)%B`OjpGd@rX%%T)7`)qqQJcNYYcJG zpho$AfyYT&A+NKwim52*)%7h8F1oBW43B<a>)JG)zl(W7D-E#{!gjx&0p}>DX0fvg zPt8F9@A!89=<}F^jT=a8E*-kS==QZJ5ql&41Xi@P1lG*)6$v@X;gQPzqDurYKvqa~ zSLl_ETszI)tUsodN-iN6OlPDwe_$!`iuJ0P&4ib>UA#9gJiLF3<du*?G=Ak)T+fFX z6VjqG%kOVAsfEbh1}VxmTa^8ajCy$YCB$V&$B7|s)YE-1#oA9=yd$DiaOtsDPk*!p z^HoytT<g+yTO1{(m`Y>c`hN;U>o-qhB=w4D#m_zNM&(WWcH{}-JrZh1`&3X(Im0D< zmKOK@>V$v8Rw#N24O7J=J6|A?zhFt)Bu%M!<#^=6m~_QYek@s#CmQ>kdy}t0*jitn zb(*mHn61(=!&xf=@!eO(vo;-{Qx|vb@EjQhPn%iyA}7LQauaKfDEww>`D11SsBn+( zQ%EqT987T4g8rkq<8JoVT|ij)3ZYzvC3$-kZ_m}pxN0WNL5wdtJ#FSBY5qw>TQ(A^ z2<5FQ+-V*t)rn$;_5GB$?>_L`C0UQ1;isjz)=b5l+RyhVRkW=cx-ib^)HavQMVS}U z@s}hr1frU$1%?znueLiJBfHaJ+Q>1_+d535@}WD;_S(7j=9E}mLui2od_iOUzm zSW!y2E-z}1@s?yEV={G4nD6Z~Oq>LV^B##zw)X^DMXS};hFZ@XHGwGF-Ih+(_}OS* z%k`fnu;>)>@yhS(qSfL>M~G5ef6Ppcpjj>?sJzK5K?%X-jx)_2k>1jgst+?U^fuSU zt}skV8I8J&7zd&>@Z|X+e?*k3u7e~$X_SU=b$%-g6vHSfsby3cs}6?1N{o%cLT{CJ z_Jg&ia%F2GwJtdiD-MD}OqjZXeZ5duKhD_Lfa6l`QD|a+!Ds3o5MlX2!HfpqY=dT9 z27fFgaXu8T#odHwBts87<A<G0q9xwrr0VnvL^>JMt7Ry1q9cSz=Z*$Q7S)~{gfH;O zo{$sma)*BA{+QbDkOx92O;7b=z3<m|(!UtY5WtGmiy2YTS<^D02U&}0aZI}*)Ou8( zYbKTH8&i}}RE(7`Le(f}>MeAcybXi`2^XhXDuW;b1xS34yyHXyH81HNKMwTMoVvTY zwKuPz49{d$Cz`8<`<O6lo;PyR^5ChUPt-t+?m0FXj+?hFrnjE%s+TWMZrh^-oslj+ z7C%e5*UjPiD75as9RB$<5Sv6qRc}TOnL-Lz%JaEj$DmMMcfQsVcKFU2v#ZFLme>MZ z3)M-BgPJhMWGBZT+Z{Vz11wAji!B6_Br1VsF-66;F*Hu04w2VWYWFz(aHw6=Y4-as z4KKcd#x^FlMLL+}oRQygv9P4n5mrR;zvx}0F$qCJk3yk69jq3~yING?=Jw;J#DLb8 zZrUa!&=Dnm5A$J_S$4C3HcS?wmIHJUzerSkjn{QqEo%H?IVI7B)2x1815>v8z(vj# zc0+52GsSI!zjXK@`(17xi#~_TyP9DHS^poVP^caD2uQsj?sZ|(qL~?Jj29lkVc}oB zV=zRsUl;g_JlI3x{T?tnnVXn6JF)&+IDfjx^!Q=h9yT=KapDb1(s8$RcCk3LDt3-z zX$gzjmR#s7YsrS*+>+xp2Rf`U#GaE5FV3y&!P$(GVc4<+iLYFH{UAS_!dQ>Q*_ccB zGpVps9o#l<0>>6O$)beY)*thW^`!tokpNe|@-{n;f%IZwhXqb#b3ytjwNG0!74NN> zzUO-8TP6+sh^1$F{sN7|6S)^ufW^j{tCoEaVVJW+a_^RC{VA@n?>TS(VT%ogfofoA zL0Q4No1zS82#3*zNpBwSk_F#h#1+RXyxf;-_E1k#Sy^*SZqHN76fTRkm$VvvYg4=> z@{vJ~Lm!8mgX%kJ;@Xb#iM?ULW7R`c)doCH$EayJlj9BXW8$+xDrW~Q+8hDKm(Cn4 zRHnS*ub4o5-ZC}rSvA8=vy(+Z<pOC5WP=6l-|VY|IUSltSdJzH_&T&}wox+W@2L^> za*mWPU_fo<_i0NVemZI-ZXvs_L!RmfW}l%EP`cZ(raQsk;_>m<1d4xy(nE&!s&S?Y zus3Q+AY&MD1Qpj;X(<!f;#p*9&u<}j1DB5Pc;siVVI^50$=q-6NhQ|Hk<`?BL|nEX zPCZ%nv9O~J<T7w!)H@?Tk<X^G2VuF<*S$8XYR?M8qc?AuG_Dn*C1kdbDqyHScKiB$ zVw{5%cHS4pTzT?#s!irk0RKtZGDKo=+2CllTxO+#GHrDhbvj&9W{yc5+t88iDE=`i zRcuYPb##ZZ3-R>Fj*o=@dVHNIc?2+Xf-5n000R`SVjHG{SDQ<4rS`VrYzkjXoLv=) z;<R|KZ{2g1I=o{)5S81HR@YuUj?U)g)vKGjaj{aw44YCO%1epk;rFW%R~B}Yw~xd7 z%E{?)^D2=fvLU#GAGsMXYcxo31m}1bd;PjGKrrW#`z+!rNif%$^w%qVT8(O|4B=`A zUoiwH&{*IVoF+M$8#tO6t2jGa*qS;0WW3JQbU66AVWuu&9?8dA3mF(k;|JhG?f{oi zHHYSu<N2<IxxRKu*g@`XUpbI;8rzH3KG|=-{{Z}w3mN!vn|r1in6>5e00X2}B)pN+ z?1jQ_@VVG{-w%4TNx&bsv7&9uFXxZzF3d1aA44w{wzYJ^W@z3l2Ku<7re<S}5ZA@! zPC@U>qfZmQ0Pn`C8Q@0Lj_GT!-d5|oL7EFxhq5F?77@$yZ#~!M(iN(|v*c~>PKvq% zN4S4g@`%xb>Uyw}n}d}c`JdZ$cDFY9wa<LDb-Oe+G@q=x$L<q>=TouN=!(qpaSS$d z`K~j%#B5<|kHQUmLxuOp0#MS${5C4L7+Eo=ZtJUVt}~^t5zhJ|zi6BAwmf4uEcuA7 z(Te2jO&0(oF|VYil!HC`-CxTqvC}tM+YRl&XY#!mAjcYbIq{rP)VkR$ei?C<4Mxw> zhRUVtxqu;Iuer%RIlUAS(20Ux<DIbP@QJT5)$<q|OprGB#~;kNpNKt}B8{6{R_Sea z)d6!XsnZxP)1Uf^IR~lQFJF-i%jT6=tipW8c)QSM%(}-i96xmu2|ZLP7LI{2Fll)_ z#u#VXS4DgDrPp2C>I)NtODRz(ytp@_dW?*(M)2cyhL~#RGU;v@!uP)S+ZG>!xrh9n zr<#X6u@bFAn+gN$kS`}vWmr%GTVF1ez8H{3X}?tJJH^9A3~556Rj;N6L<_=P6LFUK zArl;ih(YaP%Pj+D2k$98%y!Ac6NVvXXvb#Dxv}Lgnn@)!G7;1T$SkjHk2Q)2uU0L; z3f~Ks^R8OF3T8C{@Zl@I9u8`{oP8|!0j|zT)z`!xHH@%*qFfKkgAb0}@#C=;e895l z^5JCK-)G0SA#iB2`jV8A+ZmGA;H^HyNx!2-PpdqnAObwTGk`dChF)vX;jMU3gw<DH zZD!9|=)Xo>YCHhJ+^AHiHuStm7c#xF_BF+}`*MPH9{kc`LCpBd8dZriE70XAAvh-x zyB=tE^>zHF*U?F(ubV=LHDHNaHo&B24SM*%qkRc$lntfQS3WbCM-btjF{#LYRb9Pn z)?mPJ5aW2V14)59>+b42ISOz{e`z9!`jSQo4=dRKYi!3EooOdoT9HbrEoE|^M><Zf ziMG;9P!!`;FX#3!E#n|HrVepEKLgi!fK=7JEP4k>7vAw9V#xPzQ@s3s4pV<Hzc;|8 z=YQkGe&u=T2`jc+WMC(zu`fV&L~MxBT8>>LlOVCus#YIj6)?z-0EasyUFv;FEFhyz z&!a?*1{G?z6OuJN+ihM0MGC5=mWLCQ>5|c)Z)Mr5%o^SHk4FnG4|2tk@dy&mDs)?? z@%+k@0}%=h&kd>zt7gVStbw5H>8jUvC~{p34OQth%{eD4d5E2kL=EBN6xH&ez<R&| zmReC8Y;4c_MbU=BF;mQw63hWP$A{k7OI(dN$mer1Q>e?7zB|YP;c2y1l%E<FC|!~h zBTA!?5Dp6Q7ws)na^O?9BUVI1QPrx9FPxArQDUBmw#@rt(hzf^3e3Iu3>WTOp3l`< zbp>aA_Da*o-(}%TM0ttKja%l#iPFR=Rn`~5jV*SM1Ctb5)Jays*2$W$x7KGduW1u| zaF0~cQC+0XEZDz-<Z0V|eHa%*6xjC;njCFsZQ2Y|$uS_!V>8%8yA0r*rdsk5RdLol zX@Jj+aW}tS$?09dK5PujK))BbmsG3n&I%wc2++26@p8I7&YqX=n<EN#eNA7o1sNb7 zVMrgyGVc4!RR5Z4=qIG)nH{8=r0Z(wM_4uJYScJXmFZZd#u#C1$|4i77?9*K63Ues z-YQ2VsGQ1JCnB9if)1?XMP!KmE&VC_g?&;oK16}1C?5{h33#mu47CY-tqCNSB2;IH zPxum4yinWO90FooaKhqIQ)R$Vf;_E~m}lUs*}wc&4ZW0{!nr(?%;XrO`jE6dW3?K` z6sLwV-H6<zU$yKBVpndV41)AEHT88fo34bVp^PPZs99NlY*LmU<ks%W(r)rsToYWg z{KBNgpLN>5nq{d@2caY|ZK=Us%YQ>#j$g4?f^5VJ8&>dT@B>^<JLStwXTt-e1l)|b zdq$?rqYKrc#?A33xo4f1%jDAJX>9s|Uew-SN%nYsg)CoP=x1=*h~z0r!PoVu)~sTk zElj$67Y{paD{tap#yW$J7Mc|D9YGzieGBy&9z$DWYT%Z|Xe<nt%Z+kYB!aWPI@R;8 zj&O$`v5kP+qJNVuoo(Dib~0#8sE5sk8HOop(Zc*5-%!LM=Dt<J^+<Gf6Fq&$we=gM zfpbWxGQ*w%6bOU4W<lPHi52@sNWzF^XnGTKD^153c#{#I)I(rq`BIca;L^*^ccd^f zf^l!{QS80>7}J4?uRo9KU3F6K*+{ru)plUjF!nrY^0;da!)_L@R`3twIM`E@GmJ8g zExPwW3nHpLh7^U4pxHBvyX1)znN8%&iqPPeY*r<11-i5^Vm@nBM4x<(Hr=nGu%4k7 zqbtn})^eGyuzK|?hl=ws?S|>RYYu^-Q&Y^xxb&RjMXW0AVd{)xwfsgtwa!G}92l&0 zTyugfvl*izGVV6w%M(&b#uGKT4;^(mJX|u1k=`|A$e4%cefP^a{U-zl{YM0j`xk+S z(U>eDap&MtQ5&Jn*`|{Kb3M%L61C9VAPxbQgCx8JeRxIYPZ(lQTyG+~8{X$E<;FY{ z_<!Yc0`VG30hs2-;CFxqZe1AL87Vs2**mcs*xUc)Fu2M0zlH_)i=-!X+phd<azS07 z0FR>5^XY)X>g@T9j+#{f=@m}e*=of%98~jC7iW1kA6=F+Jt(gqvxVZ~TVBkm%@~Hx zx5|x~eUO^M&wArF*4Ml7upc5t;Xj&lg2L%AKxcb?X=jP+73j}25cH<$eOP?4mNdS6 z*z-R8(dfjU5Q5NIdr${mS+{_luB9^-u7wBX5Aj{cvC7oy96bIC?HkM4PifJtLL?K& zP*&%)77SK?S4kdL^53U%LDt=xl0r$tl?mN4!yn|?zmkm0Ryogqh@VwGL{@Fup>&2& z6Q^EBune4vvV-FmFW1(tHy;Y2TJ{y}3YK~h?L%S0WQInJ#;dADmL}DZ&(F^CdKXPi z@rrth4eQo^KV#rRo7EEvE_tA$8_?53z1giC{6nESP&f;XwW#k!Z<oO51A&ffkR8=w zL4VQAzz$yvYU4g!zt?eS!{^VT{=w|pw=EEEJ2QCq4M%)JTyVZ{L6x4(OeNm%5Kg*Z zni{LKx!d9uUF7Gfn<#fGGh;+AGf6@e^1kxaI;WgKrF;Z=-ICnA{97|E9Ve`p3#K+Q znA+%nq_&Zrqsjlc{nO0+V|XdcTYX|fYsbAIjXcsFUh|<EC>5UFQB1|qOio%;wWv`y zl$A*R_{~>Odd-FC8O&R*<MFf<9u+;}Tb5hI2`ekt{6)5S|2TCEr^PtZad{sRj+h?0 zX)P!L4u4vWyTdeXl*Breqjch~9M~)v>McgT1E~mJRglM$DC->!n;hV{{2L6gQeaAC zNYVkI&2iy>;U@}@#f7lbn=AD?UWO&UH{n~nUpyB7Zg+N3e_{5d4)nsU1|~+0;RQhv z`Ixh%TY33563ao}0-^)a;CPd~P0$14&9{pcp{P_V%=UQUcjd;x1OlILaR8l1wXlNo z&hU}}zBi`;4EG?fo{6|n892!DEqG0#?n}w{65l`y?$6&!8fHSWW{|L1gM1qofoi7a zoUDaqFmTeg<6)%-L4zw*=bp&>vsf}67%Ck2yq7Qxh)Jro{$|@>FK(Qi#NG{u$M7+p zZq^taGY<6J5|4Ha#o&zlKd4@HhMT0+y16TH!<058V>7CUn?Yn&D3cMDrClW(HOUbx zh>Nq%$ekcfbmGE|J%Z``kN5)uk{O)w{{6YB-@fB->))KBQk4BOz@LwG{3iHw%>`@A z9}amu75sCn`;UTy;3W0mH@=_ZJZ*UXjsy??3+IpR&!@sqTSLDK6CwQ)e%dH{3h=ag z_B#MSI1&G6^Yt&aw5KRfs}aAWu;Khd`Aco$DZ<kP|91pZ+<zeaE8Tx8`ZRa?UGx$E zZ@2I(lX{BsG?e`vrGe;gQGNxsPf`9HMEs5c00e;RI8Q^0r_z76(|?qHOYsNk->kNx UEDV@w007d@ADFD%)Ib0IA7+oOumAu6 diff --git a/demos/react/sheetjs.js b/demos/react/sheetjs.js deleted file mode 100644 index cdb0237..0000000 --- a/demos/react/sheetjs.js +++ /dev/null @@ -1,144 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -/* Notes: - - usage: `ReactDOM.render( <SheetJSApp />, document.getElementById('app') );` - - xlsx.full.min.js is loaded in the head of the HTML page - - this script should be referenced with type="text/babel" - - babel.js in-browser transpiler should be loaded before this script -*/ -function SheetJSApp() { - const [data, setData] = React.useState([]); - const [cols, setCols] = React.useState([]); - - const handleFile = (file) => { - const reader = new FileReader(); - reader.onload = (e) => { - /* Parse data */ - const ab = e.target.result; - const wb = XLSX.read(ab, {type:'array'}); - /* Get first worksheet */ - const wsname = wb.SheetNames[0]; - const ws = wb.Sheets[wsname]; - /* Convert array of arrays */ - const data = XLSX.utils.sheet_to_json(ws, {header:1}); - /* Update state */ - setData(data); - setCols(make_cols(ws['!ref'])) - }; - reader.readAsArrayBuffer(file); - } - - const exportFile = () => { - /* convert state to workbook */ - const ws = XLSX.utils.aoa_to_sheet(data); - const wb = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, "SheetJS"); - /* generate XLSX file and send to client */ - XLSX.writeFile(wb, "sheetjs.xlsx") - }; - - return ( - <DragDropFile handleFile={handleFile}> - <div className="row"><div className="col-xs-12"> - <DataInput handleFile={handleFile} /> - </div></div> - <div className="row"><div className="col-xs-12"> - <button disabled={!data.length} className="btn btn-success" onClick={exportFile}>Export</button> - </div></div> - <div className="row"><div className="col-xs-12"> - <OutTable data={data} cols={cols} /> - </div></div> - </DragDropFile> - ); -} - -if(typeof module !== 'undefined') module.exports = SheetJSApp - -/* -------------------------------------------------------------------------- */ - -/* - Simple HTML5 file drag-and-drop wrapper - usage: <DragDropFile handleFile={handleFile}>...</DragDropFile> - handleFile(file:File):void; -*/ - -function DragDropFile({ handleFile, children }) { - const suppress = (e) => { e.stopPropagation(); e.preventDefault(); }; - const handleDrop = (e) => { e.stopPropagation(); e.preventDefault(); - const files = e.dataTransfer.files; - if(files && files[0]) handleFile(files[0]); - }; - - return ( - <div - onDrop={handleDrop} - onDragEnter={suppress} - onDragOver={suppress} - > - {children} - </div> - ); -} - -/* - Simple HTML5 file input wrapper - usage: <DataInput handleFile={callback} /> - handleFile(file:File):void; -*/ - -function DataInput({ handleFile }) { - const handleChange = (e) => { - const files = e.target.files; - if(files && files[0]) handleFile(files[0]); - }; - - return ( - <form className="form-inline"> - <div className="form-group"> - <label htmlFor="file">Drag or choose a spreadsheet file</label> - <br /> - <input - type="file" - className="form-control" - id="file" - accept={SheetJSFT} - onChange={handleChange} - /> - </div> - </form> - ) -} - -/* - Simple HTML Table - usage: <OutTable data={data} cols={cols} /> - data:Array<Array<any> >; - cols:Array<{name:string, key:number|string}>; -*/ -function OutTable({ data, cols }) { - return ( - <div className="table-responsive"> - <table className="table table-striped"> - <thead> - <tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr> - </thead> - <tbody> - {data.map((r,i) => <tr key={i}> - {cols.map(c => <td key={c.key}>{ r[c.key] }</td>)} - </tr>)} - </tbody> - </table> - </div> - ); -} - -/* list of supported file types */ -const SheetJSFT = [ - "xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm" -].map(x => `.${x}`).join(","); - -/* generate an array of column objects */ -const make_cols = refstr => { - let o = [], C = XLSX.utils.decode_range(refstr).e.c + 1; - for(var i = 0; i < C; ++i) o[i] = {name:XLSX.utils.encode_col(i), key:i} - return o; -}; diff --git a/demos/server/.gitignore b/demos/server/.gitignore deleted file mode 100644 index 0bb4e01..0000000 --- a/demos/server/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -xlsx.full.min.js -xlsx-demo diff --git a/demos/server/Makefile b/demos/server/Makefile deleted file mode 100644 index 9a340b6..0000000 --- a/demos/server/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -.PHONY: init -init: - mkdir -p node_modules - cd node_modules; if [ ! -e xlsx ]; then ln -s ../../../ xlsx; fi; cd - - -.PHONY: request -request: init ## request demo - node _request.js - -.PHONY: express -express: init ## express demo - node express.js - -.PHONY: koa -koa: init ## koa demo - node koa.js - -.PHONY: hapi -hapi: init ## hapi demo - cp ../../dist/xlsx.full.min.js . - node hapi.js - -.PHONY: nest -nest: init ## nest demo - bash -c ./nest.sh - -.PHONY: drash -drash: ## drash demo - deno run --allow-net drash.ts diff --git a/demos/server/README.md b/demos/server/README.md index 41df3fd..4f03372 100644 --- a/demos/server/README.md +++ b/demos/server/README.md @@ -1,208 +1,11 @@ # NodeJS Server Deployments -This library is 100% pure JS. This is great for compatibility but tends to lock -up long-running processes. In the web browser, Web Workers are used to offload -work from the main browser thread. In NodeJS, there are other strategies. This -demo shows a few different strategies applied to different server frameworks. +[The new demo](https://docs.sheetjs.com/docs/demos/server) has a more focused +discussion with examples for popular JS server-side frameworks. -NOTE: these examples merely demonstrate the core concepts and do not include -appropriate error checking or other production-level features. - - -### Express Setup - -The following commands are required in order to test the [Express](https://github.com/expressjs/express) demo: - -```bash -npm install express printj express-formidable https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -node express.js -``` - -### Koa Setup - -The following commands are required in order to test the [Koa](https://github.com/koajs/koa) demo: - -```bash -npm install koa printj formidable https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -node koa.js -``` - -### Hapi Setup - -**Note: Hapi demo as written only works with Hapi version 16 and below.** - -The following commands are required in order to test the [Hapi](https://github.com/hapijs/hapi) demo: - -```bash -npm install hapi@16.x printj tiny-worker https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -node hapi.js -``` - - - -### Node Buffer - -The `read` and `write` functions can handle `Buffer` data with `type:"buffer"`. -For example, the `request` library returns data in a buffer: - -```js -var XLSX = require('xlsx'), request = require('request'); -request(url, {encoding: null}, function(err, res, data) { - if(err || res.statusCode !== 200) return; - - /* data is a node Buffer that can be passed to XLSX.read */ - var workbook = XLSX.read(data, {type:'buffer'}); - - /* DO SOMETHING WITH workbook HERE */ -}); -``` - -The `readFile` / `writeFile` functions wrap `fs.{read,write}FileSync`: - -```js -/* equivalent to `var wb = XLSX.readFile("sheetjs.xlsx");` */ -var buf = fs.readFileSync("sheetjs.xlsx"); -var wb = XLSX.read(buf, {type:'buffer'}); -``` - -### Responding to Form Uploads - -Using `formidable`, files uploaded to forms are stored to temporary files that -can be read with `readFile`: - -```js -/* within the server callback function(request, response) { */ -var form = new formidable.IncomingForm(); -form.parse(req, function(err, fields, files) { - var f = files[Object.keys(files)[0]]; - var workbook = XLSX.readFile(f.path); - /* DO SOMETHING WITH workbook HERE */ -}); -``` - -The `node.js` demo shows a plain HTTP server that accepts file uploads and -converts data to requested output format. - -### Example servers - -Each example server is expected to hold an array-of-arrays in memory. They are -expected to handle: - -- `POST / ` accepts an encoded `file` and updates the internal storage -- `GET /?t=<type>` returns the internal storage in the specified type -- `POST /?f=<name>` reads the local file and updates the internal storage -- `GET /?f=<name>` writes the file to the specified name - -Testing with cURL is straightforward: - -```bash -# upload sheetjs.csv and update data -curl -X POST -F "data=@sheetjs.csv" http://localhost:7262/ -# download data in SYLK format -curl -X GET http://localhost:7262/?t=slk -# read sheetjs.csv from the server directory -curl -X POST http://localhost:7262/?f=sheetjs.csv -# write sheetjs.xlsb in the XLSB format -curl -X GET http://localhost:7262/?f=sheetjs.xlsb -``` - - -## Main-process logic with express - -The most straightforward approach is to handle the data directly in HTTP event -handlers. The `buffer` type for `XLSX.read` and `XLSX.write` work with `http` -module and with express directly. The following snippet generates a workbook -based on an array of arrays and sends it to the client: - -```js -function send_aoa_to_client(req, res, data, bookType) { - /* generate workbook */ - var ws = XLSX.utils.aoa_to_sheet(data); - var wb = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, "SheetJS"); - - /* generate buffer */ - var buf = XLSX.write(wb, {type:'buffer', bookType:bookType || "xlsx"}); - - /* send to client */ - res.status(200).send(buf); -} -``` - - -## fork with koa - -`child_process.fork` provides a light-weight and customizable way to offload -work from the main server process. This demo passes commands to a custom child -process and the child passes back buffers of data. - -The main server script is `koa.js` and the worker script is `koasub.js`. State -is maintained in the worker script. - - - -## tiny-worker with hapi - -`tiny-worker` provides a Web Worker-like interface. Binary strings and simple -objects are readily passed across the Worker divide. - -The main server script is `hapi.js` and the worker script is `worker.js`. State -is maintained in the server script. - -Note: due to an issue with hapi payload parsing, the route `POST /file` is used -to handle the case of reading from file, so the cURL test is: - -```bash -# upload sheetjs.csv and update data -curl -X POST -F "data=@sheetjs.csv" http://localhost:7262/ -# download data in SYLK format -curl -X GET http://localhost:7262/?t=slk -# read sheetjs.csv from the server directory -curl -X POST http://localhost:7262/file?f=sheetjs.csv -# write sheetjs.xlsb in the XLSB format -curl -X GET http://localhost:7262/?f=sheetjs.xlsb -``` - - - -## NestJS - -[NestJS](https://nestjs.com/) is a Node.js framework for server-side web applications. - -This demo uses SheetJS to parse a spreadsheet via a POST API endpoint. The file -arrives to the endpoint as body `form-data`, accessible using the `file` key. -After parsing the file, CSV contents of the first worksheet will be returned. -[Body parsing uses `multer`](https://docs.nestjs.com/techniques/file-upload). - -Before running the demo, the NestJS CLI tool must be installed. The instruction -is described in the NestJS ["First Steps"](https://docs.nestjs.com/first-steps): - -```bash -npm i -g @nestjs/cli -make nest -``` - -The demo can be tested using the `/sheetjs/upload-xlsx-file` endpoint: - -```bash -curl -X POST -F "file=@test.xlsx" http://localhost:3000/sheetjs/upload-xlsx-file -``` - -The included [`nest.sh`](./nest.sh) script creates and configures the project. - - -This demo creates a module and a controller. The controller handles the actual -requests (creating the endpoint) while the module is used to configure `multer`. - - - -## Deno - -[`Drash`](https://drash.land/drash/) is a Deno framework for Deno's HTTP server. - -The `drash.ts` demo responds to POST requests and responds with HTML previews. - -<https://s2c.deno.dev> is a live deployment of the service. +Cloud services are covered in separate demos: +- [AWS](https://docs.sheetjs.com/docs/demos/aws) +- [Azure](https://docs.sheetjs.com/docs/demos/azure) [](https://github.com/SheetJS/js-xlsx) diff --git a/demos/server/_cors.js b/demos/server/_cors.js deleted file mode 100644 index 22b04c2..0000000 --- a/demos/server/_cors.js +++ /dev/null @@ -1,4 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -var cors = function(req, res) { res.header('Access-Control-Allow-Origin', '*'); }; -cors.mw = function(req, res, next) { cors(req, res); next(); }; -module.exports = cors; diff --git a/demos/server/_logit.js b/demos/server/_logit.js deleted file mode 100644 index 94f1a44..0000000 --- a/demos/server/_logit.js +++ /dev/null @@ -1,7 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -var sprintf = require('printj').sprintf; -var logit = function(req, res) { - console.log(sprintf("%s %s %d", req.method, req.url, res.statusCode)); -}; -logit.mw = function(req, res, next) { logit(req, res); next(); } -module.exports = logit; diff --git a/demos/server/_request.js b/demos/server/_request.js deleted file mode 100644 index 1e3aee0..0000000 --- a/demos/server/_request.js +++ /dev/null @@ -1,9 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -var XLSX = require('xlsx'), request = require('request'); -var url = 'http://www.freddiemac.com/pmms/2017/historicalweeklydata.xls' -request(url, {encoding: null}, function(err, res, data) { - if(err || res.statusCode !== 200) return; - var wb = XLSX.read(data, {type:'buffer'}); - var ws = wb.Sheets[wb.SheetNames[0]]; - console.log(XLSX.utils.sheet_to_csv(ws, {blankrows:false})); -}); diff --git a/demos/server/drash.ts b/demos/server/drash.ts deleted file mode 100644 index 5446b66..0000000 --- a/demos/server/drash.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */ -// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts" -import { read, utils, set_cptable } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs'; -import * as cptable from 'https://cdn.sheetjs.com/xlsx-latest/package/dist/cpexcel.full.mjs'; -set_cptable(cptable); - -import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts"; - - -// Create your resource - -class HomeResource extends Drash.Resource { - public paths = ["/"]; - - public POST(request: Drash.Request, response: Drash.Response) { - const file = request.bodyParam<Drash.Types.BodyFile>("file"); - if (!file) throw new Error("File is required!"); - var wb = read(file.content, {type: "buffer"}); - return response.html( utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]])); - } - - public GET(request: Drash.Request, response: Drash.Response): void { - return response.html(`\ -<!DOCTYPE html> -<html> - <head> - <title>SheetJS Spreadsheet to HTML Conversion Service</title> - <meta charset="utf-8" /> - </head> - <body> -<pre><h3><a href="//sheetjs.com/">SheetJS</a> Spreadsheet Conversion Service</h3> -<b>API</b> - -Send a POST request to https://s2c.deno.dev/ with the file in the "file" body parameter: - -$ curl -X POST -F"file=@test.xlsx" https://s2c.deno.dev/ - -The response will be an HTML TABLE generated from the first worksheet. - -<b>Try it out!</b><form action="/" method="post" enctype="multipart/form-data"> - -<input type="file" name="file" /> - -Use the file input element to select a file, then click "Submit" - -<button type="submit">Submit</button> -</form> -</pre> - </body> -</html>`, - ); - } -} - -// Create and run your server -const server = new Drash.Server({ - hostname: "", - port: 3000, - protocol: "http", - resources: [ - HomeResource, - ], -}); - -server.run(); - -console.log(`Server running at ${server.address}.`); - diff --git a/demos/server/express.js b/demos/server/express.js deleted file mode 100644 index 2b50552..0000000 --- a/demos/server/express.js +++ /dev/null @@ -1,65 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ - -var fs = require('fs'), path = require('path'), URL = require('url'); -var express = require('express'), app = express(); -var sprintf = require('printj').sprintf; -var logit = require('./_logit'); -var cors = require('./_cors'); -var data = "a,b,c\n1,2,3".split("\n").map(function(x) { return x.split(","); }); -var XLSX = require('xlsx'); - -/* helper to generate the workbook object */ -function make_book() { - var ws = XLSX.utils.aoa_to_sheet(data); - var wb = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, "SheetJS"); - return wb; -} - -function get_data(req, res, type) { - var wb = make_book(); - /* send buffer back */ - res.status(200).send(XLSX.write(wb, {type:'buffer', bookType:type})); -} - -function get_file(req, res, file) { - var wb = make_book(); - /* write using XLSX.writeFile */ - XLSX.writeFile(wb, file); - res.status(200).send("wrote to " + file + "\n"); -} - -function load_data(file) { - var wb = XLSX.readFile(file); - /* generate array of arrays */ - data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {header:1}); - console.log(data); -} - -function post_data(req, res) { - var keys = Object.keys(req.files), k = keys[0]; - load_data(req.files[k].path); - res.status(200).send("ok\n"); -} - -function post_file(req, res, file) { - load_data(file); - res.status(200).send("ok\n"); -} -app.use(logit.mw); -app.use(cors.mw); -app.use(require('express-formidable')()); -app.get('/', function(req, res, next) { - var url = URL.parse(req.url, true); - if(url.query.t) return get_data(req, res, url.query.t); - else if(url.query.f) return get_file(req, res, url.query.f); - res.status(403).end("Forbidden"); -}); -app.post('/', function(req, res, next) { - var url = URL.parse(req.url, true); - if(url.query.f) return post_file(req, res, url.query.f); - return post_data(req, res); -}); - -var port = +process.argv[2] || +process.env.PORT || 7262; -app.listen(port, function() { console.log('Serving HTTP on port ' + port); }); diff --git a/demos/server/hapi.js b/demos/server/hapi.js deleted file mode 100644 index a9db56b..0000000 --- a/demos/server/hapi.js +++ /dev/null @@ -1,67 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -var Hapi = require('hapi'), server = new Hapi.Server(); -var logit = require('./_logit'); -var Worker = require('tiny-worker'); -var fs = require('fs'); -var data = "a,b,c\n1,2,3".split("\n").map(x => x.split(",")); - -function get_data(req, res, type) { - var work = new Worker('worker.js'); - work.onmessage = function(e) { - if(e.data.err) console.log(e.data.err); - var buf = new Buffer(e.data.data, "binary"); - return res(buf); - }; - work.postMessage({action:"write", type:type, data:data}); -} - -function get_file(req, res, file) { - var work = new Worker('worker.js'); - work.onmessage = function(e) { - fs.writeFileSync(file, e.data.data, 'binary'); - return res("wrote to " + file + "\n"); - }; - work.postMessage({action:"write", file:file, data:data}); -} - -function post_file(req, res, file) { - var work = new Worker('worker.js'); - work.onmessage = function(e) { - data = e.data.data; - return res("read from " + file + "\n"); - }; - work.postMessage({action:"read", file:file}); -} - -function post_data(req, res, type) { - var keys = Object.keys(req.payload), k = keys[0]; - post_file(req, res, req.payload[k].path); -} - -var port = 7262; -server.connection({ host:'localhost', port: port}); - -server.route({ method: 'GET', path: '/', -handler: function(req, res) { - logit(req.raw.req, req.raw.res); - if(req.query.t) get_data(req, res, req.query.t); - else if(req.query.f) get_file(req, res, req.query.f); - else res('Forbidden').code(403); -}}); -server.route({ method: 'POST', path: '/', -config:{payload:{ output: 'file', parse: true, allow: 'multipart/form-data'}}, -handler: function(req, res) { - logit(req.raw.req, req.raw.res); - if(req.query.f) return post_file(req, res, req.query.f); - return post_data(req, res); -}}); -server.route({ method: 'POST', path: '/file', -handler: function(req, res) { - logit(req.raw.req, req.raw.res); - if(req.query.f) return post_file(req, res, req.query.f); - return post_data(req, res); -}}); -server.start(function(err) { - if(err) throw err; - console.log('Serving HTTP on port ' + port); -}); diff --git a/demos/server/koa.js b/demos/server/koa.js deleted file mode 100644 index 1f1176d..0000000 --- a/demos/server/koa.js +++ /dev/null @@ -1,79 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ - -const Koa = require('koa'), app = new Koa(); -const { sprintf } = require('printj'); -const { IncomingForm } = require('formidable'); -const { fork } = require('child_process'); -const logit = require('./_logit'); -const subprocess = fork('koasub.js'); - -const get_data = async (ctx, type) => { - await new Promise((resolve, reject) => { - const cb = (data) => { - ctx.response.body = Buffer(data); - subprocess.removeListener('message', cb); - resolve(); - }; - subprocess.on('message', cb); - subprocess.send(['get data', type]); - }); -}; - -const get_file = async (ctx, file) => { - await new Promise((resolve, reject) => { - const cb = (data) => { - ctx.response.body = Buffer(data); - subprocess.removeListener('message', cb); - resolve(); - }; - subprocess.on('message', cb); - subprocess.send(['get file', file]); - }); -}; - -const load_data = async (ctx, file) => { - await new Promise((resolve, reject) => { - const cb = (data) => { - ctx.response.body = "ok\n"; - subprocess.removeListener('message', cb); - resolve(); - }; - subprocess.on('message', cb); - subprocess.send(['load data', file]); - }); -}; - -const post_data = async (ctx) => { - const keys = Object.keys(ctx.request._files), k = keys[0]; - await load_data(ctx, ctx.request._files[k].path); -}; - -app.use(async (ctx, next) => { logit(ctx.req, ctx.res); await next(); }); -app.use(async (ctx, next) => { - const form = new IncomingForm(); - await new Promise((resolve, reject) => { - form.parse(ctx.req, (err, fields, files) => { - if(err) return reject(err); - ctx.request._fields = fields; - ctx.request._files = files; - resolve(); - }); - }); - await next(); -}); -app.use(async (ctx, next) => { - if(ctx.request.method !== 'GET') await next(); - else if(ctx.request.path !== '/') await next(); - else if(ctx.request.query.t) await get_data(ctx, ctx.request.query.t); - else if(ctx.request.query.f) await get_file(ctx, ctx.request.query.f); - else ctx.throw(403, "Forbidden"); -}); -app.use(async (ctx, next) => { - if(ctx.request.method !== 'POST') await next(); - else if(ctx.request.path !== '/') await next(); - else if(ctx.request.query.f) await load_data(ctx, ctx.request.query.f); - else await post_data(ctx); -}); - -const port = +process.argv[2] || +process.env.PORT || 7262; -app.listen(port, () => { console.log('Serving HTTP on port ' + port); }); diff --git a/demos/server/koasub.js b/demos/server/koasub.js deleted file mode 100644 index 1eeb989..0000000 --- a/demos/server/koasub.js +++ /dev/null @@ -1,39 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -const XLSX = require('xlsx'); -let data = "a,b,c\n1,2,3".split("\n").map(x => x.split(",")); -process.on('message', ([m, data] = _) => { - switch(m) { - case 'load data': load_data(data); break; - case 'get data': get_data(data); break; - case 'get file': get_file(data); break; - } -}); - -function load_data(file) { - var wb = XLSX.readFile(file); - /* generate array of arrays */ - data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {header:1}); - console.log(data); - process.send("done"); -} - -/* helper to generate the workbook object */ -function make_book() { - var ws = XLSX.utils.aoa_to_sheet(data); - var wb = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, "SheetJS"); - return wb; -} - -function get_data(type) { - var wb = make_book(); - /* send buffer back */ - process.send(XLSX.write(wb, {type:'buffer', bookType:type})); -} - -function get_file(file) { - var wb = make_book(); - /* write using XLSX.writeFile */ - XLSX.writeFile(wb, file); - process.send("wrote to " + file + "\n"); -} diff --git a/demos/server/nest.sh b/demos/server/nest.sh deleted file mode 100755 index ce3f07d..0000000 --- a/demos/server/nest.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -# it is assumed that @nestjs/cli is installed globally - -if [ ! -e xlsx-demo ]; then - nest new -p npm xlsx-demo -fi - -cd xlsx-demo -npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -npm i --save-dev @types/multer - -if [ ! -e src/sheetjs/sheetjs.module.ts ]; then - nest generate module sheetjs -fi - -if [ ! -e src/sheetjs/sheetjs.controller.ts ]; then - nest generate controller sheetjs -fi - -cp ../sheetjs.module.ts src/sheetjs/ -cp ../sheetjs.controller.ts src/sheetjs/ -mkdir -p upload -npm run start diff --git a/demos/server/nodejs.js b/demos/server/nodejs.js deleted file mode 100644 index 7b54d37..0000000 --- a/demos/server/nodejs.js +++ /dev/null @@ -1,84 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ - -var http = require('http'); -var XLSX = require('xlsx'); -var formidable = require('formidable'); -var html = ""; -var PORT = 3000; - -var extmap = {}; - -var server = http.createServer(function(req, res) { - if(req.method !== 'POST') return res.end(html); - var form = new formidable.IncomingForm(); - form.parse(req, function(err, fields, files) { - var f = files[Object.keys(files)[0]]; - var wb = XLSX.readFile(f.path); - var ext = (fields.bookType || "xlsx").toLowerCase(); - res.setHeader('Content-Disposition', 'attachment; filename="download.' + (extmap[ext] || ext) + '";'); - res.end(XLSX.write(wb, {type:"buffer", bookType:ext})); - }); -}).listen(PORT); - -html = [ -'<pre>', -'<h3><a href="http://sheetjs.com/">SheetJS File Converter</a></h3>', -'Upload a file to convert the contents to another format.', -'', -'<b>Form Fields</b>:', -'- bookType: output format type (defaults to "XLSX")', -'- basename: basename for output file (defaults to "download")', -'', -'<form method="POST" enctype="multipart/form-data" action="/">', -'<input type="file" id="file" name="file"/>', -'<select name="bookType">', -[ - ["xlsb", "XLSB"], - ["xlsx", "XLSX"], - ["xlsm", "XLSM"], - ["biff8", "BIFF8 XLS"], - ["biff5", "BIFF5 XLS"], - ["biff2", "BIFF2 XLS"], - ["xlml", "SSML 2003"], - ["ods", "ODS"], - ["fods", "Flat ODS"], - ["csv", "CSV"], - ["txt", "Unicode Text"], - ["sylk", "Symbolic Link"], - ["html", "HTML"], - ["dif", "DIF"], - ["dbf", "DBF"], - ["rtf", "RTF"], - ["prn", "Lotus PRN"], - ["eth", "Ethercalc"], -].map(function(x) { return ' <option value="' + x[0] + '">' + x[1] + '</option>'; }).join("\n"), -'</select>', -'<input type="submit" value="Submit Form">', -'</form>', -'', -'<b>Form code:</b>', -'<form method="POST" enctype="multipart/form-data" action="/">', -'<input type="file" id="file" name="file"/>', -'<select name="bookType">', -'<!-- options here -->', -'</select>', -'<input type="submit" value="Submit Form">', -'</form>', -'', -'<b>fetch Code:</b>', -'var blob = new Blob("1,2,3\\n4,5,6".split("")); // original file', -'var fd = new FormData();', -'fd.set("data", blob, "foo.bar");', -'fd.set("bookType", "xlsb");', -'var res = await fetch("/", {method:"POST", body:fd});', -'var data = await res.arrayBuffer();', -'</pre>' -].join("\n"); - -extmap = { - "biff2" : "xls", - "biff5" : "xls", - "biff8" : "xls", - "xlml" : "xls" -}; -console.log('listening on port ' + PORT); diff --git a/demos/server/sheetjs.controller.ts b/demos/server/sheetjs.controller.ts deleted file mode 100644 index bad90eb..0000000 --- a/demos/server/sheetjs.controller.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Controller, Logger, Post, UploadedFile, UseInterceptors } from '@nestjs/common'; -import { FileInterceptor } from '@nestjs/platform-express'; -import { readFile, utils } from 'xlsx'; - -@Controller('sheetjs') -export class SheetjsController { - private readonly logger = new Logger(SheetjsController.name); - - @Post('upload-xlsx-file') - @UseInterceptors(FileInterceptor('file')) - async uploadXlsxFile(@UploadedFile() file: Express.Multer.File) { - // Open the uploaded XLSX file and perform SheetJS operations - const workbook = readFile(file.path); - const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; - const output = utils.sheet_to_csv(firstSheet); - this.logger.log(output); - return output; - } -} diff --git a/demos/server/sheetjs.csv b/demos/server/sheetjs.csv deleted file mode 100644 index eddbf8a..0000000 --- a/demos/server/sheetjs.csv +++ /dev/null @@ -1,19 +0,0 @@ -Text,Number,Rich,Span -This is Bold,123,This is Bold,This is Bold -This is Italic,234,This is Italic,This is Italic -This is Underline,345,This is Underline,This is Underline -This is Stricken,456,This is Stricken,This is Stricken -This is 18 px,567,This is 18 px,This is 18 px -This is superscript,678,This is superscript,This is superscript -This is subscript,789,This is subscript,This is subscript -This is red,135,This is red,This is red -This is green,246,This is green,This is green -This is Times,357,This is Times,This is Times -This is BIU,159,This is 01324576 yes,This is BIU -BG Green,255,White on Blue,Green on Black -Standard Newline,W S,"BR -New line","Pre -New line" -Height,100,px (not pt),yeah -Top Left,80,Middle Center,Bottom Right -Top Right,60,Bottom Center,Bottom Left diff --git a/demos/server/sheetjs.module.ts b/demos/server/sheetjs.module.ts deleted file mode 100644 index 4dd9a53..0000000 --- a/demos/server/sheetjs.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SheetjsController } from './sheetjs.controller'; -import { MulterModule } from '@nestjs/platform-express'; - -@Module({ - controllers: [SheetjsController], - imports: [ - MulterModule.register({ - dest: './upload', - }), - ], -}) -export class SheetjsModule {} diff --git a/demos/server/worker.js b/demos/server/worker.js deleted file mode 100644 index c8e1a63..0000000 --- a/demos/server/worker.js +++ /dev/null @@ -1,22 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -var XLSX = require('xlsx'); -var fs = require('fs'); - -onmessage = function(e) { - try { switch(e.data.action) { - case 'write': - var ws = XLSX.utils.aoa_to_sheet(e.data.data); - var wb = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, "SheetJS"); - postMessage({data: XLSX.write(wb, {type:'binary', bookType:e.data.type || e.data.file.match(/\.([^\.]*)$/)[1]})}); - break; - case 'read': - var wb; - if(e.data.file) wb = XLSX.readFile(e.data.file); - else wb = XLSX.read(e.data.data); - var ws = wb.Sheets[wb.SheetNames[0]]; - postMessage({data: XLSX.utils.sheet_to_json(ws, {header:1})}); - break; - default: throw "unknown action"; - }} catch(e) { postMessage({err:e.message || e}); } -}; diff --git a/demos/vue/Makefile b/demos/vue/Makefile deleted file mode 100644 index 0adece9..0000000 --- a/demos/vue/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -.PHONY: vue -vue: ## Simple server for vue - python -mSimpleHTTPServer || python3 -mhttp.server diff --git a/demos/vue/README.md b/demos/vue/README.md index c364a8a..6229e41 100644 --- a/demos/vue/README.md +++ b/demos/vue/README.md @@ -1,83 +1,12 @@ # VueJS -The `xlsx.core.min.js` and `xlsx.full.min.js` scripts are designed to be dropped -into web pages with script tags: +[The new demo](https://docs.sheetjs.com/docs/demos/vue) has an updated +exposition for legacy and modern deployments alike. -```html -<script src="xlsx.full.min.js"></script> -``` +The ecosystem demos were grouped by type in the new demo site: -The library can also be imported directly from single-file components with: - -```js -// full import -import * as XLSX from 'xlsx'; - -// named imports -import { read, utils, writeFileXLSX } from 'xlsx'; -``` - -This demo directly generates HTML using `sheet_to_html` and adds an element to -a pre-generated template. It also has a button for exporting as XLSX. - -Other scripts in this demo show: -- server-rendered VueJS component (with `nuxt.js`) -- `weex` deployment for iOS - -## Internal State - -The plain JS demo embeds state in the DOM. Other demos use proper state. - -The simplest state representation is an array of arrays. To avoid having the -table component depend on the library, the column labels are precomputed. The -state in this demo is shaped like the following object: - -```js -{ - cols: [{ name: "A", key: 0 }, { name: "B", key: 1 }, { name: "C", key: 2 }], - data: [ - [ "id", "name", "value" ], - [ 1, "sheetjs", 7262 ], - [ 2, "js-xlsx", 6969 ] - ] -} -``` - -`sheet_to_json` and `aoa_to_sheet` utility functions can convert between arrays -of arrays and worksheets: - -```js -/* convert from workbook to array of arrays */ -var first_worksheet = workbook.Sheets[workbook.SheetNames[0]]; -var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1}); - -/* convert from array of arrays to workbook */ -var worksheet = XLSX.utils.aoa_to_sheet(data); -var new_workbook = XLSX.utils.book_new(); -XLSX.utils.book_append_sheet(new_workbook, worksheet, "SheetJS"); -``` - -The column objects can be generated with the `encode_col` utility function: - -```js -function make_cols(refstr/*:string*/) { - var o = []; - var range = XLSX.utils.decode_range(refstr); - for(var i = 0; i <= range.e.c; ++i) { - o.push({name: XLSX.utils.encode_col(i), key:i}); - } - return o; -} -``` - -## Mobile Apps - -[The new demo](https://docs.sheetjs.com/docs/demos/mobile#quasar) uses the -Quasar Framework in a VueJS + Vite project to generate a native iOS app. - -## Nuxt Content - -[The new demo](https://docs.sheetjs.com/docs/demos/content#nuxtjs) includes a -complete example starting from `create-nuxt-app`. +- [Nuxt Content](https://docs.sheetjs.com/docs/demos/content#nuxtjs) is now part of "Content and Site Generation" +- [The new iOS app demo](https://docs.sheetjs.com/docs/demos/mobile#quasar) uses the Quasar Framework in a VueJS + Vite project to generate a native iOS app. +- [`vue3-table-lite` reading, modifying, and writing files](https://docs.sheetjs.com/docs/demo/grid#vue3-table-lite) is now part of "Data Grids and UI" [](https://github.com/SheetJS/js-xlsx) diff --git a/demos/vue/SheetJS-vue.js b/demos/vue/SheetJS-vue.js deleted file mode 100644 index a21d563..0000000 --- a/demos/vue/SheetJS-vue.js +++ /dev/null @@ -1,68 +0,0 @@ -/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -var SheetJSFT = [ - "xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm", "numbers" -].map(function(x) { return "." + x; }).join(","); - -var SJSTemplate = [ - '<div>', - '<input type="file" multiple="false" id="sheetjs-input" accept="' + SheetJSFT + '" @change="onchange" />', - '<br/>', - '<button type="button" id="export-table" style="visibility:hidden" @click="onexport">Export to XLSX</button>', - '<br/>', - '<div id="out-table"></div>', - '</div>' -].join(""); -var component_struct = { - template: SJSTemplate, - methods: { - onchange: function(evt) { - var file; - var files = evt.target.files; - - if (!files || files.length == 0) return; - - file = files[0]; - - var reader = new FileReader(); - reader.onload = function (e) { - // pre-process data - var binary = ""; - var bytes = new Uint8Array(e.target.result); - var length = bytes.byteLength; - for (var i = 0; i < length; i++) { - binary += String.fromCharCode(bytes[i]); - } - - /* read workbook */ - var wb = XLSX.read(binary, {type: 'binary'}); - - /* grab first sheet */ - var wsname = wb.SheetNames[0]; - var ws = wb.Sheets[wsname]; - - /* generate HTML */ - var HTML = XLSX.utils.sheet_to_html(ws); - - /* update table */ - document.getElementById('out-table').innerHTML = HTML; - /* show export button */ - document.getElementById('export-table').style.visibility = "visible"; - }; - - reader.readAsArrayBuffer(file); - }, - onexport: function(evt) { - /* generate workbook object from table */ - var wb = XLSX.utils.table_to_book(document.getElementById('out-table')); - /* generate file and force a download*/ - XLSX.writeFile(wb, "sheetjs.xlsx"); - } - } -}; -var app; -if(Vue.component) { - Vue.component('html-preview', component_struct); -} else { - app = Vue.createApp({}); - app.component('html-preview', component_struct); -} diff --git a/demos/vue/index2.html b/demos/vue/index2.html deleted file mode 100644 index 1870671..0000000 --- a/demos/vue/index2.html +++ /dev/null @@ -1,63 +0,0 @@ -<!DOCTYPE html> -<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com --> -<!-- vim: set ts=2: --> -<html> -<head> - <title>SheetJS + VueJS2</title> - <!-- Vue 2 --> - <script src="https://unpkg.com/vue@2.x"></script> - - <!-- Various shims --> - <script src="shim.js"></script> - - <!-- SheetJS js-xlsx library --> - <script src="xlsx.full.min.js"></script> - - <!-- SheetJS Vue components --> - <script src="SheetJS-vue.js"></script> - -<style> -.grid1 { - width: 500px; - height: 400px; -}; -</style> -</head> -<body> -<pre> -<b><a href="http://sheetjs.com">SheetJS + VueJS2 demo</a></b> - -The core library can be used as-is in Vue applications. -The <a href="https://github.com/sheetjs/js-xlsx">Community Edition README</a> details some common use cases. -We also have some <a href="http://sheetjs.com/demos/">more public demos</a> - -This demo shows a sample Vue component "html-preview" that: -- displays a file input that accepts a spreadsheet file -- draws the first worksheet of a submitted file as HTML -- presents an export button to generate XLSX files - -<a href="https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls">Sample Spreadsheet</a> -</pre> -<script type="text/javascript"> - var _gaq = _gaq || []; - _gaq.push(['_setAccount', 'UA-36810333-1']); - _gaq.push(['_trackPageview']); - - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); -</script> - -<div id="app"> - <html-preview></html-preview> -</div> - -<script lang="javascript"> -if(Vue.component) var app = new Vue({ el: '#app' }); -else app.mount('#app'); -</script> -</body> -</html> - diff --git a/demos/vue/index3.html b/demos/vue/index3.html deleted file mode 100644 index c095d70..0000000 --- a/demos/vue/index3.html +++ /dev/null @@ -1,63 +0,0 @@ -<!DOCTYPE html> -<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com --> -<!-- vim: set ts=2: --> -<html> -<head> - <title>SheetJS + VueJS3</title> - <!-- Vue 2 --> - <script src="https://unpkg.com/vue@3.x"></script> - - <!-- Various shims --> - <script src="shim.js"></script> - - <!-- SheetJS js-xlsx library --> - <script src="xlsx.full.min.js"></script> - - <!-- SheetJS Vue components --> - <script src="SheetJS-vue.js"></script> - -<style> -.grid1 { - width: 500px; - height: 400px; -}; -</style> -</head> -<body> -<pre> -<b><a href="http://sheetjs.com">SheetJS + VueJS3 demo</a></b> - -The core library can be used as-is in Vue applications. -The <a href="https://github.com/sheetjs/js-xlsx">Community Edition README</a> details some common use cases. -We also have some <a href="http://sheetjs.com/demos/">more public demos</a> - -This demo shows a sample Vue component "html-preview" that: -- displays a file input that accepts a spreadsheet file -- draws the first worksheet of a submitted file as HTML -- presents an export button to generate XLSX files - -<a href="https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls">Sample Spreadsheet</a> -</pre> -<script type="text/javascript"> - var _gaq = _gaq || []; - _gaq.push(['_setAccount', 'UA-36810333-1']); - _gaq.push(['_trackPageview']); - - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); -</script> - -<div id="app"> - <html-preview></html-preview> -</div> - -<script lang="javascript"> -if(Vue.component) var app = new Vue({ el: '#app' }); -else app.mount('#app'); -</script> -</body> -</html> - diff --git a/demos/vue/modify/.gitignore b/demos/vue/modify/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/demos/vue/modify/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/demos/vue/modify/README.md b/demos/vue/modify/README.md deleted file mode 100644 index 4d76306..0000000 --- a/demos/vue/modify/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# vue-modify - -This demo shows import an export with `vue3-table-light` table component. - -In this directory, run - -```bash -npm i -npm run dev -``` diff --git a/demos/vue/modify/index.html b/demos/vue/modify/index.html deleted file mode 100644 index 867581c..0000000 --- a/demos/vue/modify/index.html +++ /dev/null @@ -1,13 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8" /> - <link rel="icon" type="image/svg+xml" href="favicon.svg" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Vite App</title> - </head> - <body> - <div id="app"></div> - <script type="module" src="/src/main.ts"></script> - </body> -</html> diff --git a/demos/vue/modify/package.json b/demos/vue/modify/package.json deleted file mode 100644 index e39cc11..0000000 --- a/demos/vue/modify/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "vue-modify", - "private": true, - "version": "0.0.0", - "scripts": { - "dev": "vite --host", - "build": "vue-tsc --noEmit && vite build", - "preview": "vite preview" - }, - "dependencies": { - "vue": "^3.2.25", - "vue3-table-lite": "^1.1.7-1", - "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^2.2.0", - "typescript": "^4.5.4", - "vite": "^2.8.0", - "vue-tsc": "^0.29.8" - } -} diff --git a/demos/vue/modify/src/App.vue b/demos/vue/modify/src/App.vue deleted file mode 100644 index 2b1f5ec..0000000 --- a/demos/vue/modify/src/App.vue +++ /dev/null @@ -1,241 +0,0 @@ -<script setup lang="ts"> -import { ref } from "vue"; -import { read, utils, writeFile, WorkBook } from "xlsx"; - -import VueTableLite from "vue3-table-lite/ts"; - -type DataSet = { - [index: string]: WorkBook; -}; - -type Row = any[]; - -type Column = { - field: string; - label: string; - display: (row: Row) => string; -}; - -const currFileName = ref<string>(""); -const currSheet = ref<string>(""); -const sheets = ref<string[]>([]); -const workBook = ref<DataSet>({} as DataSet); -const rows = ref<Row[]>([]); -const columns = ref<Column[]>([]); - -const exportTypes: string[] = ["xlsx", "xlsb", "csv", "html"]; - -let cell = 0; - -function resetCell() { - cell = 0; -} - -function display(col: number): (row: Row) => string { - return function (row: Row) { - return `<span - style="user-select: none; display: block" - position="${Math.floor(cell++ / columns.value.length)}.${col}" - onblur="endEdit(event)" - ondblclick="startEdit(event)" - onkeydown="endEdit(event)">${row[col] ?? " "}</span>`; - }; -} - -window.startEdit = function (ev) { - ev.target.contentEditable = true; - ev.target.focus(); -}; - -window.endEdit = function (ev) { - if (ev.key === undefined || ev.key === "Enter") { - const pos = ev.target.getAttribute("position").split("."); - - ev.target.contentEditable = false; - - rows.value[pos[0]][pos[1]] = ev.target.innerText; - - workBook.value[currSheet.value] = utils.json_to_sheet(rows.value, { - header: columns.value.map((col: Column) => col.field), - skipHeader: true, - }); - } -}; - -function getRowsCols( - data: DataSet, - sheetName: string -): { - rows: Row[]; - cols: Column[]; -} { - const rows: Row[] = utils.sheet_to_json(data[sheetName], { header: 1 }); - let cols: Column[] = []; - - for (let row of rows) { - const keys: string[] = Object.keys(row); - - if (keys.length > cols.length) { - cols = keys.map((key) => { - return { - field: key, - label: utils.encode_col(+key), - display: display(key), - }; - }); - } - } - - return { rows, cols }; -} - -async function importFile(ev: ChangeEvent<HTMLInputElement>): Promise<void> { - const file = ev.target.files[0]; - const data = read(await file.arrayBuffer()); - - currFileName.value = file.name; - currSheet.value = data.SheetNames?.[0]; - sheets.value = data.SheetNames; - workBook.value = data.Sheets; - - selectSheet(currSheet.value); -} - -function exportFile(type: string): void { - const wb = utils.book_new(); - - sheets.value.forEach((sheet) => { - utils.book_append_sheet(wb, workBook.value[sheet], sheet); - }); - - writeFile(wb, `sheet.${type}`); -} - -function selectSheet(sheet: string): void { - const { rows: newRows, cols: newCols } = getRowsCols(workBook.value, sheet); - - resetCell(); - - rows.value = newRows; - columns.value = newCols; - currSheet.value = sheet; -} -</script> - -<template> - <header class="imp-exp"> - <div class="import"> - <input type="file" id="import" @change="importFile" /> - <label for="import">import</label> - </div> - <span>{{ currFileName || "vue-modify demo" }}</span> - <div class="export"> - <span>export</span> - <ul> - <li v-for="type in exportTypes" @click="exportFile(type)"> - {{ `.${type}` }} - </li> - </ul> - </div> - </header> - <div class="sheets"> - <span - v-for="sheet in sheets" - @click="selectSheet(sheet)" - :class="[currSheet === sheet ? 'selected' : '']" - > - {{ sheet }} - </span> - </div> - <vue-table-lite - :is-static-mode="true" - :page-size="50" - :columns="columns" - :rows="rows" - ></vue-table-lite> -</template> - -<style> -.imp-exp { - display: flex; - justify-content: space-between; - padding: 0.5rem; - font-family: mono; - color: #212529; -} - -.import { - font-size: medium; -} - -.import input { - position: absolute; - opacity: 0; - cursor: pointer; -} - -.import label { - background-color: white; - border: 1px solid; - padding: 0.3rem; -} - -.export: hover { - border-bottom: none; -} - -.export:hover ul { - display: block; -} - -.export span { - padding: 0.3rem; - border: 1px solid; - cursor: pointer; -} - -.export ul { - display: none; - position: absolute; - z-index: 5; - background-color: white; - list-style: none; - padding: 0.3rem; - border: 1px solid; - margin-top: 0.3rem; - border-top: none; -} - -.export ul li { - padding: 0.3rem; - text-align: center; -} - -.export ul li:hover { - background-color: lightgray; - cursor: pointer; -} - -.sheets { - display: flex; - justify-content: center; - margin: 0.3rem; - color: #212529; -} - -.sheets span { - border: 1px solid; - padding: 0.5rem; - margin: 0.3rem; -} - -.sheets span:hover:not(.selected) { - background-color: lightgray; - cursor: pointer; -} - -.selected { - background-color: #343a40; - color: white; -} -</style> diff --git a/demos/vue/modify/src/env.d.ts b/demos/vue/modify/src/env.d.ts deleted file mode 100644 index aafef95..0000000 --- a/demos/vue/modify/src/env.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// <reference types="vite/client" /> - -declare module '*.vue' { - import type { DefineComponent } from 'vue' - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types - const component: DefineComponent<{}, {}, any> - export default component -} diff --git a/demos/vue/modify/src/main.ts b/demos/vue/modify/src/main.ts deleted file mode 100644 index 684d042..0000000 --- a/demos/vue/modify/src/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { createApp } from 'vue'; -import App from './App.vue'; - -createApp(App).mount('#app'); diff --git a/demos/vue/modify/tsconfig.json b/demos/vue/modify/tsconfig.json deleted file mode 100644 index af31eb8..0000000 --- a/demos/vue/modify/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "useDefineForClassFields": true, - "module": "esnext", - "moduleResolution": "node", - "strict": true, - "jsx": "preserve", - "sourceMap": true, - "resolveJsonModule": true, - "esModuleInterop": true, - "lib": ["esnext", "dom"] - }, - "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/demos/vue/modify/tsconfig.node.json b/demos/vue/modify/tsconfig.node.json deleted file mode 100644 index e993792..0000000 --- a/demos/vue/modify/tsconfig.node.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "module": "esnext", - "moduleResolution": "node" - }, - "include": ["vite.config.ts"] -} diff --git a/demos/vue/modify/vite.config.ts b/demos/vue/modify/vite.config.ts deleted file mode 100644 index 315212d..0000000 --- a/demos/vue/modify/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [vue()] -}) diff --git a/demos/vue/shim.js b/demos/vue/shim.js deleted file mode 120000 index 7ec5819..0000000 --- a/demos/vue/shim.js +++ /dev/null @@ -1 +0,0 @@ -../../shim.js \ No newline at end of file diff --git a/demos/vue/xlsx.full.min.js b/demos/vue/xlsx.full.min.js deleted file mode 120000 index dbca48d..0000000 --- a/demos/vue/xlsx.full.min.js +++ /dev/null @@ -1 +0,0 @@ -../../dist/xlsx.full.min.js \ No newline at end of file diff --git a/demos/xspreadsheet/README.md b/demos/xspreadsheet/README.md index 2321769..5d21b9e 100644 --- a/demos/xspreadsheet/README.md +++ b/demos/xspreadsheet/README.md @@ -5,62 +5,14 @@ with other JS libraries such as data grids for previewing data. With a familiar UI, [`x-spreadsheet`](https://myliang.github.io/x-spreadsheet/) is an excellent choice for developers looking for a modern editor. -This demo is available at <https://oss.sheetjs.com/sheetjs/x-spreadsheet.html> +[The new docs](https://docs.sheetjs.com/docs/demos/grid/#x-spreadsheet) +include more detail and examples. -## Obtaining the Library +The original demo is available at <https://docs.sheetjs.com/xspreadsheet/> -The `x-data-spreadsheet` NodeJS packages include a minified script that can be -directly inserted as a script tag. The unpkg CDN also serves this script: +A hosted version of the `xlsxspread.js` script is available on the SheetJS CDN: -```html -<script src="https://unpkg.com/x-data-spreadsheet/dist/xspreadsheet.js"></script> -``` - -## Previewing Data - -The HTML document needs a container element: - -```html -<div id="gridctr"></div> -``` - -Grid initialization is a one-liner: - -```js -/* note that the browser build exposes the variable `x` */ -var grid = x_spreadsheet(document.getElementById("gridctr")); -``` - -The following function converts data from SheetJS to x-spreadsheet: - -```js -/* load data */ -grid.loadData(stox(workbook_object)); -``` - -`stox` is defined in [`xlsxspread.js`](./xlsxspread.js) - -## Editing - -`x-spreadsheet` handles the entire edit cycle. No intervention is necessary. - -## Saving Data - -`grid.getData()` returns an object that can be converted back to a worksheet: - -```js -/* build workbook from the grid data */ -var new_wb = xtos(xspr.getData()); - -/* generate download */ -XLSX.writeFile(new_wb, "SheetJS.xlsx"); -``` - -`stox` is defined in [`xlsxspread.js`](./xlsxspread.js) - -## Additional Features - -This demo barely scratches the surface. The underlying grid component includes -many additional features that work with [SheetJS Pro](https://sheetjs.com/pro). +- <https://cdn.sheetjs.com/xspreadsheet/xlsxspread.js> original script +- <https://cdn.sheetjs.com/xspreadsheet/xlsxspread.min.js> minified [](https://github.com/SheetJS/js-xlsx) diff --git a/demos/xspreadsheet/index.html b/demos/xspreadsheet/index.html deleted file mode 100644 index dd80a43..0000000 --- a/demos/xspreadsheet/index.html +++ /dev/null @@ -1,135 +0,0 @@ -<!DOCTYPE html> -<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com --> -<!-- vim: set ts=2: --> -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -<title>SheetJS + x-spreadsheet Live Demo</title> -<style> -#drop{ - border:2px dashed #bbb; - -moz-border-radius:5px; - -webkit-border-radius:5px; - border-radius:5px; - padding:25px; - text-align:center; - font:20pt bold,"Vollkorn";color:#bbb -} -#b64data{ - width:100%; -} -a { text-decoration: none } -</style> -<!-- x-spreadsheet stylesheet --> -<link rel="stylesheet" href="https://unpkg.com/x-data-spreadsheet/dist/xspreadsheet.css"/> -</head> -<body> -<pre> -<b><a href="http://sheetjs.com">SheetJS Data Preview Live Demo</a></b> - -<a href="https://github.com/myliang/x-spreadsheet">x-spreadsheet component library</a> - -<a href="https://github.com/SheetJS/sheetjs">Source Code Repo</a> -<a href="https://github.com/SheetJS/sheetjs/issues">Issues? Something look weird? Click here and report an issue</a> - -<div id="drop">Drop a spreadsheet file here to see sheet data</div> -<input type="file" name="xlfile" id="xlf" /> ... or click here to select a file -<textarea id="b64data">... or paste a base64-encoding here</textarea> -</pre> -<p><input type="submit" value="Export to XLSX!" id="xport" onclick="export_xlsx();"></p> -<div id="htmlout"></div> -<br /> -<script src="https://unpkg.com/x-data-spreadsheet/dist/xspreadsheet.js"></script> -<script src="shim.js"></script> -<script src="xlsx.full.min.js"></script> -<script src="xlsxspread.js"></script> -<script> -/*jshint browser:true */ -/* eslint-env browser */ -/* eslint no-use-before-define:0 */ -/*global Uint8Array, Uint16Array, ArrayBuffer */ -/*global XLSX */ - -var HTMLOUT = document.getElementById('htmlout'); -var xspr = x_spreadsheet(HTMLOUT); -HTMLOUT.style.height = (window.innerHeight - 400) + "px"; -HTMLOUT.style.width = (window.innerWidth - 50) + "px"; - -var process_wb = (function() { - var XPORT = document.getElementById('xport'); - - return function process_wb(wb) { - /* convert to x-spreadsheet form */ - var data = stox(wb); - - /* update x-spreadsheet */ - xspr.loadData(data); - XPORT.disabled = false; - - if(typeof console !== 'undefined') console.log("output", new Date()); - }; -})(); - -var do_file = (function() { - return function do_file(files) { - var f = files[0]; - var reader = new FileReader(); - reader.onload = function(e) { - if(typeof console !== 'undefined') console.log("onload", new Date()); - var data = e.target.result; - data = new Uint8Array(data); - process_wb(XLSX.read(data, {type: 'array'})); - }; - reader.readAsArrayBuffer(f); - }; -})(); - -(function() { - var drop = document.getElementById('drop'); - if(!drop.addEventListener) return; - - function handleDrop(e) { - e.stopPropagation(); - e.preventDefault(); - do_file(e.dataTransfer.files); - } - - function handleDragover(e) { - e.stopPropagation(); - e.preventDefault(); - e.dataTransfer.dropEffect = 'copy'; - } - - drop.addEventListener('dragenter', handleDragover, false); - drop.addEventListener('dragover', handleDragover, false); - drop.addEventListener('drop', handleDrop, false); -})(); - -(function() { - var xlf = document.getElementById('xlf'); - if(!xlf.addEventListener) return; - function handleFile(e) { do_file(e.target.files); } - xlf.addEventListener('change', handleFile, false); -})(); - -function export_xlsx() { - var new_wb = xtos(xspr.getData()); - - /* write file and trigger a download */ - XLSX.writeFile(new_wb, 'sheetjs.xlsx', {}); -} -</script> -<script type="text/javascript"> -/* eslint no-use-before-define:0 */ - var _gaq = _gaq || []; - _gaq.push(['_setAccount', 'UA-36810333-1']); - _gaq.push(['_trackPageview']); - - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); -</script> -</body> -</html> diff --git a/demos/xspreadsheet/shim.js b/demos/xspreadsheet/shim.js deleted file mode 120000 index 7ec5819..0000000 --- a/demos/xspreadsheet/shim.js +++ /dev/null @@ -1 +0,0 @@ -../../shim.js \ No newline at end of file diff --git a/demos/xspreadsheet/xlsx.full.min.js b/demos/xspreadsheet/xlsx.full.min.js deleted file mode 120000 index dbca48d..0000000 --- a/demos/xspreadsheet/xlsx.full.min.js +++ /dev/null @@ -1 +0,0 @@ -../../dist/xlsx.full.min.js \ No newline at end of file diff --git a/demos/xspreadsheet/xlsxspread.js b/demos/xspreadsheet/xlsxspread.js index d4b88f9..cced956 100644 --- a/demos/xspreadsheet/xlsxspread.js +++ b/demos/xspreadsheet/xlsxspread.js @@ -2,6 +2,7 @@ /* eslint-env browser */ /*global XLSX */ /*exported stox, xtos */ +console.log("The latest version of the xlsxspread.js script is at https://cdn.sheetjs.com/xspreadsheet/xlsxspread.js !") /** * Converts data from SheetJS to x-spreadsheet @@ -129,3 +130,4 @@ function xtos(sdata) { return out; } +