diff --git a/.github/workflows/npm-grunt.yaml b/.github/workflows/npm-grunt.yaml
new file mode 100644
index 0000000..8c6d59d
--- /dev/null
+++ b/.github/workflows/npm-grunt.yaml
@@ -0,0 +1,29 @@
+name: NodeJS with Grunt
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [18.x, 20.x, 22.x]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Build
+ run: |
+ npm install
+ grunt
+
diff --git a/.github/workflows/npm-publish.yaml b/.github/workflows/npm-publish.yaml
new file mode 100644
index 0000000..c75bc58
--- /dev/null
+++ b/.github/workflows/npm-publish.yaml
@@ -0,0 +1,38 @@
+name: CI Pipeline - NPM Publish
+
+permissions:
+ checks: write
+ contents: write
+
+on:
+ push:
+ branches:
+ - "main"
+ - "master"
+
+jobs:
+ read-version:
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.package_version.outputs.version }} # This makes it available to other jobs
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ - name: Read version from package.json
+ id: package_version
+ run: |
+ echo "::set-output name=version::$(jq -r '.version' package.json)"
+ - name: Output the version
+ run: echo "The version in package.json is ${{ steps.package_version.outputs.version }}"
+
+ publish-pipeline:
+ needs: read-version
+ name: "Publish"
+ uses: brandwatch/bw-workflow-actions/.github/workflows/node-deploy-package.yaml@production
+ with:
+ version: ${{ needs.read-version.outputs.version }}-pr.${{ github.event.pull_request.number }}
+ node-version: 20
+ linter: false
+ test: true
+ build-step: true
+ secrets: inherit
diff --git a/.gitignore b/.gitignore
index 76fc8d7..4848392 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ tmp
npm-debug.log
node_modules
+package-lock.json
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..209e3ef
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+20
diff --git a/Gruntfile.coffee b/Gruntfile.coffee
deleted file mode 100644
index 23a4619..0000000
--- a/Gruntfile.coffee
+++ /dev/null
@@ -1,43 +0,0 @@
-module.exports = (grunt) ->
- @loadNpmTasks('grunt-contrib-clean')
- @loadNpmTasks('grunt-contrib-coffee')
- @loadNpmTasks('grunt-contrib-watch')
- @loadNpmTasks('grunt-mkdir')
- @loadNpmTasks('grunt-release')
- @loadNpmTasks('grunt-vows-runner')
-
- @initConfig
- coffee:
- all:
- options:
- bare: true
- expand: true,
- cwd: 'src',
- src: ['*.coffee'],
- dest: 'lib',
- ext: '.js'
-
- clean:
- all: ['lib', 'tmp']
-
- mkdir:
- all:
- options:
- create: ['tmp']
-
- watch:
- all:
- files: ['src/**.coffee', 'test/**.coffee']
- tasks: ['test']
-
- vows:
- all:
- src: 'test/test.coffee'
- options:
- reporter: 'spec'
-
-
- @registerTask 'default', ['test']
- @registerTask 'build', ['clean', 'coffee']
- @registerTask 'package', ['build', 'release']
- @registerTask 'test', ['build', 'mkdir', 'vows']
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..2a44964
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,49 @@
+module.exports = function(grunt) {
+ // Project configuration.
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+ clean: {
+ all: ['lib', 'tmp']
+ },
+ coffee: {
+ all: {
+ expand: true,
+ cwd: 'src',
+ src: ['**/*.coffee'],
+ dest: 'lib',
+ ext: '.js'
+ }
+ },
+ mkdir: {
+ all: {
+ options: {
+ create: ['tmp']
+ }
+ }
+ },
+ shell: {
+ vows: {
+ command: 'vows test/test.coffee --spec'
+ }
+ },
+ watch: {
+ scripts: {
+ files: ['src/**/*.coffee'],
+ tasks: ['coffee'],
+ options: {
+ spawn: false
+ }
+ }
+ }
+ });
+
+ // Load the plugins.
+ grunt.loadNpmTasks('grunt-contrib-clean');
+ grunt.loadNpmTasks('grunt-contrib-coffee');
+ grunt.loadNpmTasks('grunt-mkdir');
+ grunt.loadNpmTasks('grunt-shell');
+ grunt.loadNpmTasks('grunt-contrib-watch');
+
+ // Default task(s).
+ grunt.registerTask('default', ['clean', 'coffee', 'mkdir', 'shell:vows']);
+};
diff --git a/lib/index.js b/lib/index.js
index bc2b7aa..de610e9 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,123 +1,146 @@
-var archiver, duplex, sheetStream, templates, through, utils, xlsxStream, _;
+(function() {
+ // Convert stream of Array to a xlsx file.
-through = require('through');
+ // * Usage
-archiver = require('archiver');
+ // out = fs.createWriteStream('out.xlsx')
+ // stream = xlsxStream()
+ // stream.pipe out
-_ = require('lodash');
+ // stream.write(['aaa', 'bbb', 'ccc'])
+ // stream.write([1, 2, 3])
+ // stream.write([new Date, '090-1234-5678', 'これはテストです'])
-duplex = require('duplexer');
+ // stream.end()
+ var _, archiver, duplex, sheetStream, templates, through, utils, xlsxStream;
-templates = require('./templates');
+ through = require('through');
-utils = require("./utils");
+ archiver = require('archiver');
-sheetStream = require("./sheet");
+ _ = require('lodash');
-module.exports = xlsxStream = function(opts) {
- var defaultRepeater, defaultSheet, index, item, proxy, sheets, styles, zip, _i, _len, _ref;
- if (opts == null) {
- opts = {};
- }
- zip = archiver.create('zip', opts);
- defaultRepeater = through();
- proxy = duplex(defaultRepeater, zip);
- zip.pause();
- process.nextTick(function() {
- return zip.resume();
- });
- defaultSheet = null;
- sheets = [];
- styles = {
- numFmts: [
- {
- numFmtId: "0",
- formatCode: ""
- }
- ],
- cellStyleXfs: [
- {
- numFmtId: "0",
- formatCode: ""
- }, {
- numFmtId: "1",
- formatCode: "0"
- }, {
- numFmtId: "14",
- formatCode: "m/d/yy"
- }
- ],
- customFormatsCount: 0,
- formatCodesToStyleIndex: {}
- };
- index = 0;
- _ref = styles.cellStyleXfs;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- item = _ref[_i];
- styles.formatCodesToStyleIndex[item.formatCode || ""] = index;
- index++;
- }
- defaultRepeater.once('data', function(data) {
- defaultSheet = proxy.sheet('Sheet1');
- defaultSheet.write(data);
- defaultRepeater.pipe(defaultSheet);
- return defaultRepeater.on('end', proxy.finalize);
- });
- proxy.sheet = function(name) {
- var sheet;
- index = sheets.length + 1;
- sheet = {
- index: index,
- name: name || ("Sheet" + index),
- rel: "worksheets/sheet" + index + ".xml",
- path: "xl/worksheets/sheet" + index + ".xml",
- styles: styles
- };
- sheets.push(sheet);
- return sheetStream(zip, sheet, opts);
- };
- proxy.finalize = function() {
- var buffer, func, name, obj, sheet, _j, _len1, _ref1, _ref2, _ref3;
- zip.append(templates.styles(styles), {
- name: "xl/styles.xml",
- store: opts.store
+ duplex = require('duplexer');
+
+ templates = require('./templates');
+
+ utils = require("./utils");
+
+ sheetStream = require("./sheet");
+
+ module.exports = xlsxStream = function(opts = {}) {
+ var defaultRepeater, defaultSheet, i, index, item, len, proxy, ref, sheets, styles, zip;
+ // archiving into a zip file using archiver (internally using node's zlib built-in module)
+ zip = archiver.create('zip', opts);
+ defaultRepeater = through();
+ proxy = duplex(defaultRepeater, zip);
+ // prevent loosing data before listening 'data' event in node v0.8
+ zip.pause();
+ process.nextTick(function() {
+ return zip.resume();
});
- _ref1 = templates.statics;
- for (name in _ref1) {
- buffer = _ref1[name];
- zip.append(buffer, {
- name: name,
- store: opts.store
- });
+ defaultSheet = null;
+ sheets = [];
+ styles = {
+ numFmts: [
+ {
+ numFmtId: "0",
+ formatCode: ""
+ }
+ ],
+ cellStyleXfs: [
+ {
+ numFmtId: "0",
+ formatCode: ""
+ },
+ {
+ numFmtId: "1",
+ formatCode: "0"
+ },
+ {
+ numFmtId: "14",
+ formatCode: "m/d/yy"
+ }
+ ],
+ customFormatsCount: 0,
+ formatCodesToStyleIndex: {}
+ };
+ index = 0;
+ ref = styles.cellStyleXfs;
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ styles.formatCodesToStyleIndex[item.formatCode || ""] = index;
+ index++;
}
- _ref2 = templates.semiStatics;
- for (name in _ref2) {
- func = _ref2[name];
- zip.append(func(opts), {
- name: name,
+ // writing data without sheet() results in creating a default worksheet named 'Sheet1'
+ defaultRepeater.once('data', function(data) {
+ defaultSheet = proxy.sheet('Sheet1');
+ defaultSheet.write(data);
+ defaultRepeater.pipe(defaultSheet);
+ return defaultRepeater.on('end', proxy.finalize);
+ });
+ // Append a new worksheet to the workbook
+ proxy.sheet = function(name) {
+ var sheet;
+ index = sheets.length + 1;
+ sheet = {
+ index: index,
+ name: name || `Sheet${index}`,
+ rel: `worksheets/sheet${index}.xml`,
+ path: `xl/worksheets/sheet${index}.xml`,
+ styles: styles
+ };
+ sheets.push(sheet);
+ return sheetStream(zip, sheet, opts);
+ };
+ // finalize the xlsx file
+ proxy.finalize = function() {
+ var buffer, func, j, len1, name, obj, ref1, ref2, ref3, sheet;
+ // styles
+ zip.append(templates.styles(styles), {
+ name: "xl/styles.xml",
store: opts.store
});
- }
- _ref3 = templates.sheet_related;
- for (name in _ref3) {
- obj = _ref3[name];
- buffer = obj.header;
- for (_j = 0, _len1 = sheets.length; _j < _len1; _j++) {
- sheet = sheets[_j];
- buffer += obj.sheet(sheet);
+ ref1 = templates.statics;
+ // static files
+ for (name in ref1) {
+ buffer = ref1[name];
+ zip.append(buffer, {
+ name,
+ store: opts.store
+ });
}
- buffer += obj.footer;
- zip.append(buffer, {
- name: name,
- store: opts.store
- });
- }
- return zip.finalize(function(e, bytes) {
- if (e != null) {
- return proxy.emit('error', e);
+ ref2 = templates.semiStatics;
+ for (name in ref2) {
+ func = ref2[name];
+ zip.append(func(opts), {
+ name,
+ store: opts.store
+ });
}
- return proxy.emit('finalize', bytes);
- });
+ ref3 = templates.sheet_related;
+ // files modified by number of sheets
+ for (name in ref3) {
+ obj = ref3[name];
+ buffer = obj.header;
+ for (j = 0, len1 = sheets.length; j < len1; j++) {
+ sheet = sheets[j];
+ buffer += obj.sheet(sheet);
+ }
+ buffer += obj.footer;
+ zip.append(buffer, {
+ name,
+ store: opts.store
+ });
+ }
+ return zip.finalize(function(e, bytes) {
+ if (e != null) {
+ return proxy.emit('error', e);
+ }
+ return proxy.emit('finalize', bytes);
+ });
+ };
+ return proxy;
};
- return proxy;
-};
+
+}).call(this);
diff --git a/lib/sheet.js b/lib/sheet.js
index ced49a2..725f9dc 100644
--- a/lib/sheet.js
+++ b/lib/sheet.js
@@ -1,75 +1,75 @@
-var sheetStream, template, through, utils, _;
+(function() {
+ var _, sheetStream, template, through, utils, worksheetTemplates;
-_ = require("lodash");
+ _ = require("lodash");
-through = require('through');
+ through = require('through');
-utils = require('./utils');
+ utils = require('./utils');
-template = require('./templates');
-worksheetTemplates = template.worksheet;
+ template = require('./templates');
-module.exports = sheetStream = function(zip, sheet, opts) {
- var colChar, converter, nRow, onData, onEnd;
- var links = [];
- if (opts == null) {
- opts = {};
- }
- colChar = _.memoize(utils.colChar);
- nRow = 0;
- onData = function(row) {
- var buf, col, i, val, _i, _j, _len, _len1, _ref;
- nRow++;
- buf = "";
- if (opts.columns != null) {
- _ref = opts.columns;
- for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
- col = _ref[i];
- buf += utils.buildCell("" + (colChar(i)) + nRow, row[col], sheet.styles);
+ worksheetTemplates = template.worksheet;
+
+ module.exports = sheetStream = function(zip, sheet, opts = {}) {
+ var colChar, converter, links, nRow, onData, onEnd;
+ // 列番号の26進表記(A, B, .., Z, AA, AB, ..)
+ // 一度計算したらキャッシュしておく。
+ colChar = _.memoize(utils.colChar);
+ links = [];
+ // 行ごとに変換してxl/worksheets/sheet1.xml に追加
+ nRow = 0;
+ onData = function(row) {
+ var buf, col, i, j, k, len, len1, ref, val;
+ nRow++;
+ buf = ``;
+ if (opts.columns != null) {
+ ref = opts.columns;
+ for (i = j = 0, len = ref.length; j < len; i = ++j) {
+ col = ref[i];
+ buf += utils.buildCell(`${colChar(i)}${nRow}`, row[col], sheet.styles);
+ }
+ } else {
+ for (i = k = 0, len1 = row.length; k < len1; i = ++k) {
+ val = row[i];
+ buf += utils.buildCell(`${colChar(i)}${nRow}`, val, sheet.styles);
+ }
}
- } else {
- for (i = _j = 0, _len1 = row.length; _j < _len1; i = ++_j) {
- val = row[i];
- if (typeof val === 'string') {
- if (val.startsWith('http://')) {
- links.push(`${colChar(i)}${nRow}-${val}`)
- }
+ buf += '
';
+ return this.queue(buf);
+ };
+ onEnd = function() {
+ var converter, func, j, len, link, linkCounter, name, rel;
+ this.queue(worksheetTemplates.footer);
+ if (links.length > 0) {
+ rel = template.rels;
+ for (name in rel) {
+ func = rel[name];
+ zip.append(func(links), {
+ name: name
+ });
}
- buf += utils.buildCell("" + (colChar(i)) + nRow, val, sheet.styles);
+ this.queue(worksheetTemplates.hyperLinkStart);
+ linkCounter = 0;
+ for (j = 0, len = links.length; j < len; j++) {
+ link = links[j];
+ linkCounter++;
+ this.queue(worksheetTemplates.hyperLink(link, linkCounter));
+ }
+ this.queue(worksheetTemplates.hyperLinkEnd);
}
- }
- buf += '
';
- return this.queue(buf);
+ this.queue(worksheetTemplates.endSheet);
+ this.queue(null);
+ return converter = colChar = zip = null;
+ };
+ converter = through(onData, onEnd);
+ zip.append(converter, {
+ name: sheet.path,
+ store: opts.store
+ });
+ // ヘッダ部分を追加
+ converter.queue(worksheetTemplates.header);
+ return converter;
};
- onEnd = function() {
- var converter;
- this.queue(worksheetTemplates.footer);
- if (links) {
- let rel = template.rels;
- for (name in rel) {
- func = rel[name];
- zip.append(func(links), {
- name: name
- });
- }
- this.queue(worksheetTemplates.hyperLinkStart);
- let linkCounter = 0;
- links.forEach(link => {
- linkCounter++;
- this.queue(worksheetTemplates.hyperLink(link, linkCounter));
- });
- this.queue(worksheetTemplates.hyperLinkEnd);
- }
- this.queue(worksheetTemplates.endSheet);
- this.queue(null);
- return converter = colChar = zip = null;
- };
- converter = through(onData, onEnd);
- zip.append(converter, {
- name: sheet.path,
- store: opts.store
- });
- converter.queue(worksheetTemplates.header);
- return converter;
-};
+}).call(this);
diff --git a/lib/templates.js b/lib/templates.js
index 2b407de..a670b30 100644
--- a/lib/templates.js
+++ b/lib/templates.js
@@ -1,90 +1,199 @@
-var esc, utils, xml;
+(function() {
+ var esc, utils, xml;
-utils = require('./utils');
+ utils = require('./utils');
-xml = utils.compress;
+ xml = utils.compress;
-esc = utils.escapeXML;
+ esc = utils.escapeXML;
-module.exports = {
- worksheet: {
- header: xml("\n\n \n \n \n \n "),
- footer: xml(" \n"),
- hyperLinkStart: xml("\n"),
- hyperLink: (link, rId) => {
- const parts = link.split('-');
- const escapedLink = parts[1].replace(/&/g, '&')
- const xmlString = `\n`;
- return xml(xmlString)
- },
- hyperLinkEnd: xml("\n"),
- endSheet: xml("")
- },
- sheet_related: {
- "[Content_Types].xml": {
- header: xml("\n\n \n \n \n \n \n \n "),
- sheet: function(sheet) {
- return "";
+ module.exports = {
+ worksheet: {
+ header: xml(`
+
+
+
+
+
+ `),
+ footer: xml(``),
+ hyperLinkStart: xml(``),
+ hyperLink: function(link, rId) {
+ var escapedLink, parts, xmlString;
+ parts = link.split('-');
+ escapedLink = parts[1].replace(/&/g, '&');
+ xmlString = ``;
+ return xml(xmlString);
},
- footer: xml("")
+ hyperLinkEnd: xml(``),
+ endSheet: xml(``)
},
- "xl/_rels/workbook.xml.rels": {
- header: xml("\n"),
- sheet: function(sheet) {
- return "";
+ sheet_related: {
+ "[Content_Types].xml": {
+ header: xml(`
+
+
+
+
+
+
+
+ `),
+ sheet: function(sheet) {
+ return ``;
+ },
+ footer: xml(``)
},
- footer: xml(" \n \n")
- },
- "xl/workbook.xml": {
- header: xml("\n\n \n \n \n \n \n "),
- sheet: function(sheet) {
- return xml("");
+ "xl/_rels/workbook.xml.rels": {
+ header: xml(`
+`),
+ sheet: function(sheet) {
+ return ``;
+ },
+ footer: xml(`
+
+`)
},
- footer: xml(" \n \n")
- }
- },
- styles: function(styl) {
- var cellXfItems, cellXfs, item, numFmtItems, numFmts, _i, _j, _len, _len1, _ref, _ref1;
- numFmtItems = "";
- _ref = styl.numFmts;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- item = _ref[_i];
- numFmtItems += " \n";
- }
- numFmts = numFmtItems ? "\n " + numFmtItems + "" : "";
- cellXfItems = "";
- _ref1 = styl.cellStyleXfs;
- for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
- item = _ref1[_j];
- cellXfItems += " \n";
- }
- cellXfs = cellXfItems ? "\n " + cellXfItems + "\n" : "";
- return xml("\n\n " + numFmts + "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n " + cellXfs + "\n \n \n \n \n \n \n \n \n \n \n");
- },
- statics: {
- "_rels/.rels": xml("\n \n \n \n \n"),
- "xl/sharedStrings.xml": xml("\n"),
- "docProps/app.xml": xml("\n\n node-xlsx-stream\n 0\n false\n Microsoft Corporation\n false\n false\n false\n " + (require('../package.json').version) + "\n")
- },
- semiStatics: {
- "docProps/core.xml": function(opts) {
- var today;
- today = new Date().toISOString();
- return "\n\n node-xlsx-stream\n node-xlsx-stream\n " + today + "\n " + today + "\n";
- }
- },
- rels: {
- "xl/worksheets/_rels/sheet1.xml.rels": function (links) {
- xmlString = xml("\n");
- let linksCounter = 0;
- links.forEach(link => {
- linksCounter++;
- const parts = link.split('-');
- const escapedLink = parts[1].replace(/&/g, '&')
- xmlString += xml(`\n`);
- });
- xmlString += xml("");
- return xmlString;
+ "xl/workbook.xml": {
+ header: xml(`
+
+
+
+
+
+
+ `),
+ sheet: function(sheet) {
+ return xml(``);
+ },
+ footer: xml(`
+
+`)
+ }
+ },
+ styles: function(styl) {
+ var cellXfItems, cellXfs, i, item, j, len, len1, numFmtItems, numFmts, ref, ref1;
+ numFmtItems = "";
+ ref = styl.numFmts;
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ numFmtItems += ``;
+ }
+ numFmts = numFmtItems ? `
+ ${numFmtItems}
+` : "";
+ cellXfItems = "";
+ ref1 = styl.cellStyleXfs;
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ item = ref1[j];
+ cellXfItems += ``;
+ }
+ cellXfs = cellXfItems ? `
+ ${cellXfItems}
+` : "";
+ return xml(`
+
+ ${numFmts}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${cellXfs}
+
+
+
+
+
+
+
+
+
+
+`);
+ },
+ statics: {
+ "_rels/.rels": xml(`
+
+
+
+
+`),
+ "xl/sharedStrings.xml": xml(`
+`),
+ "docProps/app.xml": xml(`
+
+ node-xlsx-stream
+ 0
+ false
+ Microsoft Corporation
+ false
+ false
+ false
+ ${require('../package.json').version}
+`)
+ },
+ semiStatics: {
+ "docProps/core.xml": function(opts) {
+ var today;
+ today = new Date().toISOString();
+ return `
+
+ node-xlsx-stream
+ node-xlsx-stream
+ ${today}
+ ${today}
+`;
+ }
+ },
+ rels: {
+ "xl/worksheets/_rels/sheet1.xml.rels": function(links) {
+ var escapedLink, i, len, link, linksCounter, parts, xmlString;
+ xmlString = xml(`
+`);
+ linksCounter = 0;
+ for (i = 0, len = links.length; i < len; i++) {
+ link = links[i];
+ linksCounter++;
+ parts = link.split('-');
+ escapedLink = parts[1].replace(/&/g, '&');
+ xmlString += xml(``);
+ }
+ xmlString += xml(``);
+ return xmlString;
+ }
}
- }
-};
+ };
+
+}).call(this);
diff --git a/lib/utils.js b/lib/utils.js
index d20b98d..1bf13e6 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,149 +1,153 @@
-var compress, escapeXML, _;
+(function() {
+ var _, compress, escapeXML;
-_ = require("lodash");
+ _ = require("lodash");
-module.exports = {
- colChar: function(input) {
- var a, colIndex;
- input = input.toString(26);
- colIndex = '';
- while (input.length) {
- a = input.charCodeAt(input.length - 1);
- colIndex = String.fromCharCode(a + (a >= 48 && a <= 57 ? 17 : -22)) + colIndex;
- input = input.length > 1 ? (parseInt(input.substr(0, input.length - 1), 26) - 1).toString(26) : "";
- }
- return colIndex;
- },
- escapeXML: escapeXML = function(str) {
- return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''');
- },
- compress: compress = function(str) {
- return String(str).replace(/\n\s*/g, '');
- },
- buildCell: function(ref, val, styles) {
- var f, getStyle, r, s, t, v;
- getStyle = function(nf) {
- var getBuiltinNumFmtId, numFmtId, r, s;
- if (!nf) {
- return;
- }
- r = styles.formatCodesToStyleIndex[nf];
- if (r) {
- return r;
+ module.exports = {
+ colChar: function(input) {
+ var a, colIndex;
+ input = input.toString(26);
+ colIndex = '';
+ while (input.length) {
+ a = input.charCodeAt(input.length - 1);
+ colIndex = String.fromCharCode(a + (a >= 48 && a <= 57 ? 17 : -22)) + colIndex;
+ input = input.length > 1 ? (parseInt(input.substr(0, input.length - 1), 26) - 1).toString(26) : "";
}
- getBuiltinNumFmtId = function(nf) {
- var builtin_nfs;
- builtin_nfs = {
- 'General': 0,
- '': 0,
- '0': 1,
- '0.00': 2,
- '#,##0': 3,
- '#,##0.00': 4,
- '0%': 9,
- '0.00%': 10,
- '0.00E+00': 11,
- '# ?/?': 12,
- '# ??/??': 13,
- 'm/d/yy': 14,
- 'd-mmm-yy': 15,
- 'd-mmm': 16,
- 'mmm-yy': 17,
- 'h:mm AM/PM': 18,
- 'h:mm:ss AM/PM': 19,
- 'h:mm': 20,
- 'h:mm:ss': 21,
- 'm/d/yy h:mm': 22,
- '[$-404]e/m/d': 27,
- '#,##0 ;(#,##0)': 37,
- '#,##0 ;[Red](#,##0)': 38,
- '#,##0.00;(#,##0.00)': 39,
- '#,##0.00;[Red](#,##0.00)': 40,
- '_("$"* #,##0.00_);_("$"* \\(#,##0.00\\);_("$"* "-"??_);_(@_)': 44,
- 'mm:ss': 45,
- '[h]:mm:ss': 46,
- 'mmss.0': 47,
- '##0.0E+0': 48,
- '@': 49,
- 't0': 59,
- 't0.00': 60,
- 't#,##0': 61,
- 't#,##0.00': 62,
- 't0%': 67,
- 't0.00%': 68,
- 't# ?/?': 69,
- 't# ??/??': 70
+ return colIndex;
+ },
+ escapeXML: escapeXML = function(str) {
+ return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''');
+ },
+ compress: compress = function(str) {
+ return String(str).replace(/\n\s*/g, '');
+ },
+ buildCell: function(ref, val, styles) {
+ var f, getStyle, r, s, t, v;
+ getStyle = function(nf) {
+ var getBuiltinNumFmtId, numFmtId, r, s;
+ if (!nf) {
+ return;
+ }
+ r = styles.formatCodesToStyleIndex[nf];
+ if (r) {
+ return r;
+ }
+ getBuiltinNumFmtId = function(nf) {
+ var builtin_nfs;
+ // ECMA-376 18.8.30
+ builtin_nfs = {
+ 'General': 0,
+ '': 0,
+ '0': 1,
+ '0.00': 2,
+ '#,##0': 3,
+ '#,##0.00': 4,
+ '0%': 9,
+ '0.00%': 10,
+ '0.00E+00': 11,
+ '# ?/?': 12,
+ '# ??/??': 13,
+ 'm/d/yy': 14, // also 30
+ 'd-mmm-yy': 15,
+ 'd-mmm': 16,
+ 'mmm-yy': 17,
+ 'h:mm AM/PM': 18,
+ 'h:mm:ss AM/PM': 19,
+ 'h:mm': 20,
+ 'h:mm:ss': 21,
+ 'm/d/yy h:mm': 22,
+ '[$-404]e/m/d': 27, // also 36, 50, 57
+ '#,##0 ;(#,##0)': 37,
+ '#,##0 ;[Red](#,##0)': 38,
+ '#,##0.00;(#,##0.00)': 39,
+ '#,##0.00;[Red](#,##0.00)': 40,
+ '_("$"* #,##0.00_);_("$"* \\(#,##0.00\\);_("$"* "-"??_);_(@_)': 44,
+ 'mm:ss': 45,
+ '[h]:mm:ss': 46,
+ 'mmss.0': 47,
+ '##0.0E+0': 48,
+ '@': 49,
+ 't0': 59,
+ 't0.00': 60,
+ 't#,##0': 61,
+ 't#,##0.00': 62,
+ 't0%': 67,
+ 't0.00%': 68,
+ 't# ?/?': 69,
+ 't# ??/??': 70
+ };
+ r = builtin_nfs[nf];
+ return r;
};
- r = builtin_nfs[nf];
- return r;
- };
- numFmtId = getBuiltinNumFmtId(nf);
- if (!numFmtId) {
- styles.customFormatsCount++;
- numFmtId = 164 + styles.customFormatsCount;
- styles.numFmts.push({
+ numFmtId = getBuiltinNumFmtId(nf);
+ if (!numFmtId) {
+ styles.customFormatsCount++;
+ numFmtId = 164 + styles.customFormatsCount;
+ styles.numFmts.push({
+ numFmtId: numFmtId,
+ formatCode: nf
+ });
+ }
+ s = styles.cellStyleXfs.length;
+ styles.cellStyleXfs.push({
numFmtId: numFmtId,
formatCode: nf
});
+ styles.formatCodesToStyleIndex[nf] = s;
+ return s;
+ };
+ if (val == null) {
+ return '';
}
- s = styles.cellStyleXfs.length;
- styles.cellStyleXfs.push({
- numFmtId: numFmtId,
- formatCode: nf
- });
- styles.formatCodesToStyleIndex[nf] = s;
- return s;
- };
- if (val == null) {
- return '';
- }
- if (typeof val === 'object' && !_.isDate(val)) {
- v = val.v;
- t = val.t;
- s = val.s;
- f = val.f;
- if (!s && val.nf) {
- s = getStyle(val.nf);
+ if (typeof val === 'object' && !_.isDate(val)) {
+ v = val.v;
+ t = val.t;
+ s = val.s;
+ f = val.f;
+ if (!s && val.nf) {
+ s = getStyle(val.nf);
+ }
+ } else {
+ v = val;
}
- } else {
- v = val;
- }
- if (_.isNumber(v) && _.isFinite(v)) {
- v = '' + v + '';
- if (val.nf && !t) {
- t = 'n';
+ if (_.isNumber(v) && _.isFinite(v)) {
+ v = '' + v + '';
+ if (val.nf && !t) {
+ t = 'n';
+ }
+ } else if (_.isDate(v)) {
+ t = 'd';
+ if (s == null) {
+ s = '2';
+ }
+ v = '' + v.toISOString() + '';
+ } else if (_.isBoolean(v)) {
+ t = 'b';
+ v = '' + (v === true ? '1' : '0') + '';
+ } else if (v) {
+ v = '' + escapeXML(v) + '';
+ t = 'inlineStr';
}
- } else if (_.isDate(v)) {
- t = 'd';
- if (s == null) {
- s = '2';
+ if (!(v || f)) {
+ return '';
}
- v = '' + v.toISOString() + '';
- } else if (_.isBoolean(v)) {
- t = 'b';
- v = '' + (v === true ? '1' : '0') + '';
- } else if (v) {
- v = '' + escapeXML(v) + '';
- t = 'inlineStr';
- }
- if (!(v || f)) {
- return '';
- }
- r = '';
- if (f) {
- r += '' + f + '';
- }
- if (v) {
- r += v;
+ r = '';
+ if (f) {
+ r += '' + f + '';
+ }
+ if (v) {
+ r += v;
+ }
+ r += '';
+ return r;
}
- r += '';
- return r;
- }
-};
+ };
+
+}).call(this);
diff --git a/package.json b/package.json
index 2b3c577..778a633 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "xlsx-stream",
"description": "Creates SpreadsheetML (.xlsx) files in sequence with streaming interface.",
- "version": "0.1.2",
+ "version": "0.2.0",
"homepage": "https://github.com/nunukim/node-xlsx-stream",
"author": {
"name": "Ryota Suzuki",
@@ -23,27 +23,31 @@
],
"main": "lib/index.js",
"engines": {
- "node": "~ 0.8.0"
+ "node": ">0.8.0"
+ },
+ "scripts": {
+ "grunt": "grunt",
+ "test": "grunt",
+ "build": "npm install"
},
- "scripts": {},
"devDependencies": {
- "grunt-contrib-clean": "~0.5.0",
- "grunt-contrib-watch": "~0.5.3",
- "grunt-contrib-coffee": "~0.7.0",
- "grunt-release": "~0.6.0",
- "grunt-vows-runner": "~0.6.0",
- "grunt-mkdir": "~0.1.1",
- "grunt": "~0.4.0",
- "coffee-script": "~1.6.3",
- "vows": "~0.7.0",
- "excel-parser": "~0.2.1",
- "concat-stream": "~1.2.1"
+ "grunt": "^1.6.1",
+ "grunt-cli": "^1.5.0",
+ "grunt-contrib-clean": "^2.0.1",
+ "grunt-contrib-watch": "^1.1.0",
+ "grunt-contrib-coffee": "^2.1.0",
+ "grunt-release": "^0.14.0",
+ "grunt-shell": "^4.0.0",
+ "grunt-mkdir": "^1.1.0",
+ "coffee-script": "^1.12.7",
+ "vows": "^0.8.3",
+ "concat-stream": "^2.0.0"
},
"dependencies": {
- "lodash": "~ 1.3.0",
- "through": "~ 2.3.4",
- "duplexer": "~ 0.1.1",
- "archiver": "~0.4.10"
+ "lodash": "^4.17.21",
+ "through": "^2.3.8",
+ "duplexer": "^0.1.2",
+ "archiver": "^7.0.1"
},
"keywords": [
"xlsx",
diff --git a/src/sheet.coffee b/src/sheet.coffee
index 4155e9c..3059399 100644
--- a/src/sheet.coffee
+++ b/src/sheet.coffee
@@ -2,12 +2,14 @@ _ = require "lodash"
through = require('through')
utils = require('./utils')
-template = require('./templates').worksheet
+template = require('./templates')
+worksheetTemplates = template.worksheet
module.exports = sheetStream = (zip, sheet, opts={})->
# 列番号の26進表記(A, B, .., Z, AA, AB, ..)
# 一度計算したらキャッシュしておく。
colChar = _.memoize utils.colChar
+ links = []
# 行ごとに変換してxl/worksheets/sheet1.xml に追加
nRow = 0
@@ -20,9 +22,23 @@ module.exports = sheetStream = (zip, sheet, opts={})->
buf += utils.buildCell("#{colChar(i)}#{nRow}", val, sheet.styles) for val, i in row
buf += ''
@queue buf
+
onEnd = ->
- # フッタ部分を追加
- @queue template.footer
+ @queue worksheetTemplates.footer
+
+ if links.length > 0
+ rel = template.rels
+ for name, func of rel
+ zip.append func(links), name: name
+
+ @queue worksheetTemplates.hyperLinkStart
+ linkCounter = 0
+ for link in links
+ linkCounter++
+ @queue worksheetTemplates.hyperLink(link, linkCounter)
+ @queue worksheetTemplates.hyperLinkEnd
+
+ @queue worksheetTemplates.endSheet
@queue null
converter = colChar = zip = null
@@ -30,6 +46,6 @@ module.exports = sheetStream = (zip, sheet, opts={})->
zip.append converter, name: sheet.path, store: opts.store
# ヘッダ部分を追加
- converter.queue template.header
+ converter.queue worksheetTemplates.header
return converter
diff --git a/src/templates.coffee b/src/templates.coffee
index 1db321c..80ec3c0 100644
--- a/src/templates.coffee
+++ b/src/templates.coffee
@@ -4,11 +4,13 @@ xml = utils.compress
esc = utils.escapeXML
module.exports =
- # worksheet
worksheet:
header: xml """
-
+
@@ -17,10 +19,24 @@ module.exports =
"""
footer: xml """
+ """
+ hyperLinkStart: xml """
+
+ """
+ hyperLink: (link, rId) ->
+ parts = link.split('-')
+ escapedLink = parts[1].replace(/&/g, '&')
+ xmlString = """
+
+ """
+ xml xmlString
+ hyperLinkEnd: xml """
+
+ """
+ endSheet: xml """
"""
- # Static files
sheet_related:
"[Content_Types].xml":
header: xml """
@@ -34,9 +50,10 @@ module.exports =
"""
- sheet: (sheet)-> """
-
- """
+ sheet: (sheet) ->
+ """
+
+ """
footer: xml """
"""
@@ -46,9 +63,10 @@ module.exports =
"""
- sheet: (sheet)-> """
-
- """
+ sheet: (sheet) ->
+ """
+
+ """
footer: xml """
@@ -58,36 +76,42 @@ module.exports =
"xl/workbook.xml":
header: xml """
-
+
-
+
"""
- sheet: (sheet)-> xml """
-
- """
+ sheet: (sheet) ->
+ xml """
+
+ """
footer: xml """
"""
- # Styles file
- styles: (styl)->
+ styles: (styl) ->
numFmtItems = ""
for item in styl.numFmts
- numFmtItems += " \n"
+ numFmtItems += """
+
+ """
numFmts = if numFmtItems then """
- #{numFmtItems}
+ #{numFmtItems}
+
""" else ""
cellXfItems = ""
for item in styl.cellStyleXfs
- cellXfItems += " \n"
+ cellXfItems += """
+
+ """
cellXfs = if cellXfItems then """
#{cellXfItems}
@@ -96,7 +120,9 @@ module.exports =
xml """
-
+
#{numFmts}
@@ -138,11 +164,10 @@ module.exports =
"""
- # Static files
statics:
"_rels/.rels": xml """
-
+
@@ -166,17 +191,40 @@ module.exports =
false
#{require('../package.json').version}
- """
+ """
semiStatics:
- "docProps/core.xml": (opts)->
+ "docProps/core.xml": (opts) ->
today = new Date().toISOString()
"""
-
+
node-xlsx-stream
node-xlsx-stream
#{today}
#{today}
"""
+
+ rels:
+ "xl/worksheets/_rels/sheet1.xml.rels": (links) ->
+ xmlString = xml """
+
+
+ """
+ linksCounter = 0
+ for link in links
+ linksCounter++
+ parts = link.split('-')
+ escapedLink = parts[1].replace(/&/g, '&')
+ xmlString += xml """
+
+ """
+ xmlString += xml """
+
+ """
+ xmlString
diff --git a/test/test.coffee b/test/test.coffee
index a135907..de6b9c9 100644
--- a/test/test.coffee
+++ b/test/test.coffee
@@ -1,7 +1,7 @@
xlsx_stream = require "../"
vows = require "vows"
assert = require "assert"
-office = require "office"
+#office = require "office"
fs = require "fs"
path = require "path"