BIFF3/4 write support

- more precise support for unknown BIFF type (see #2390)
- `format_cell` render error strings
This commit is contained in:
SheetJS 2021-09-26 18:51:19 -04:00
parent 88e662af99
commit 8658054872
20 changed files with 218 additions and 154 deletions

1
.gitignore vendored

@ -4,6 +4,7 @@ package-lock.json
_book/
misc/coverage.html
misc/prof.js
misc/*.[sS][vV][gG]
v8.log
tmp
*.[tT][xX][tT]

@ -102,10 +102,13 @@ bytes: ## Display minified and gzipped file sizes
.PHONY: graph
graph: formats.png legend.png ## Rebuild format conversion graph
formats.png: formats.dot
circo -Tpng -o$@ $<
legend.png: misc/legend.dot
dot -Tpng -o$@ $<
misc/formats.svg: misc/formats.dot
circo -Tsvg -o$@ $<
misc/legend.svg: misc/legend.dot
dot -Tsvg -o$@ $<
formats.png legend.png: %.png: misc/%.svg
node misc/coarsify.js misc/$*.svg misc/$*.svg.svg
npx svgexport misc/$*.svg.svg $@ 0.5x
.PHONY: nexe

111
README.md

@ -26,27 +26,26 @@ Community Translations of this README:
[**Issues and Bug Reports**](https://github.com/sheetjs/sheetjs/issues)
[**File format support for known spreadsheet data formats:**](#file-formats)
![License](https://img.shields.io/github/license/SheetJS/sheetjs)
[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/SheetJS/sheetjs)](https://snyk.io/test/github/SheetJS/sheetjs)
[![npm Downloads](https://img.shields.io/npm/dm/xlsx.svg)](https://npmjs.org/package/xlsx)
[![jsDelivr Downloads](https://data.jsdelivr.com/v1/package/npm/xlsx/badge)](https://www.jsdelivr.com/package/npm/xlsx)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs)
<details>
<summary><b>Graph of supported formats</b> (click to show)</summary>
[**Browser Test and Support Matrix**](https://oss.sheetjs.com/sheetjs/tests/)
[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs)
**Supported File Formats**
![circo graph of format support](formats.png)
<details><summary><b>Diagram Legend</b> (click to show)</summary>
![graph legend](legend.png)
</details>
[**Browser Test**](https://oss.sheetjs.com/sheetjs/tests/)
[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs)
[![Build Status](https://semaphoreci.com/api/v1/sheetjs/sheetjs/branches/master/shields_badge.svg)](https://semaphoreci.com/sheetjs/sheetjs)
[![Coverage Status](https://img.shields.io/coveralls/SheetJS/sheetjs/master.svg)](https://coveralls.io/r/SheetJS/sheetjs?branch=master)
[![Dependencies Status](https://david-dm.org/sheetjs/sheetjs/status.svg)](https://david-dm.org/sheetjs/sheetjs)
[![npm Downloads](https://img.shields.io/npm/dt/xlsx.svg)](https://npmjs.org/package/xlsx)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs)
## Table of Contents
<details>
@ -162,7 +161,7 @@ In the browser, just add a script tag:
|-----------:|:-------------------------------------------|
| `unpkg` | <https://unpkg.com/xlsx/> |
| `jsDelivr` | <https://jsdelivr.com/package/npm/xlsx> |
| `CDNjs` | <https://cdnjs.com/libraries/xlsx> |
| `CDNjs` | <https://cdnjs.com/libraries/xlsx> |
| `packd` | <https://bundle.run/xlsx@latest?name=XLSX> |
`unpkg` makes the latest version available at:
@ -245,7 +244,11 @@ An appropriate version for each dependency is included in the dist/ directory.
The complete single-file version is generated at `dist/xlsx.full.min.js`
A slimmer build with XLSX / HTML support is generated at `dist/xlsx.mini.min.js`
A slimmer build is generated at `dist/xlsx.mini.min.js`. Compared to full build:
- codepage library skipped (no support for XLS encodings)
- XLSX compression option not currently available
- no support for XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003
- node stream utils removed
Webpack and Browserify builds include optional modules by default. Webpack can
be configured to remove support with `resolve.alias`:
@ -846,7 +849,7 @@ Parse options are described in the [Parsing Options](#parsing-options) section.
`XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`.
In browser-based environments, it will attempt to force a client-side download.
`XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`.
`XLSX.writeFileAsync(wb, filename, o, cb)` attempts to write `wb` to `filename`.
If `o` is omitted, the writer will use the third argument as the callback.
`XLSX.stream` contains a set of streaming write functions.
@ -1347,10 +1350,10 @@ prefixed with an apostrophe `'`, consistent with Excel's formula bar display.
| Storage Representation | Formats | Read | Write |
|:-----------------------|:-------------------------|:-----:|:-----:|
| A1-style strings | XLSX | :o: | :o: |
| RC-style strings | XLML and plain text | :o: | :o: |
| BIFF Parsed formulae | XLSB and all XLS formats | :o: | |
| OpenFormula formulae | ODS/FODS/UOS | :o: | :o: |
| A1-style strings | XLSX | ✔ | ✔ |
| RC-style strings | XLML and plain text | ✔ | ✔ |
| BIFF Parsed formulae | XLSB and all XLS formats | ✔ | |
| OpenFormula formulae | ODS/FODS/UOS | ✔ | ✔ |
Since Excel prohibits named cells from colliding with names of A1 or RC style
cell references, a (not-so-simple) regex conversion is possible. BIFF Parsed
@ -1374,6 +1377,7 @@ type ColInfo = {
wch?: number; // width in characters
/* other fields for preserving features from files */
level?: number; // 0-indexed outline / group level
MDW?: number; // Excel's "Max Digit Width" unit, always integral
};
```
@ -1852,6 +1856,8 @@ output formats. The specific file type is controlled with `bookType` option:
| `xlsb` | `.xlsb` | ZIP | multi | Excel 2007+ Binary Format |
| `biff8` | `.xls` | CFB | multi | Excel 97-2004 Workbook Format |
| `biff5` | `.xls` | CFB | multi | Excel 5.0/95 Workbook Format |
| `biff4` | `.xls` | none | single | Excel 4.0 Worksheet Format |
| `biff3` | `.xls` | none | single | Excel 3.0 Worksheet Format |
| `biff2` | `.xls` | none | single | Excel 2.0 Worksheet Format |
| `xlml` | `.xls` | none | multi | Excel 2003-2004 (SpreadsheetML) |
| `ods` | `.ods` | ZIP | multi | OpenDocument Spreadsheet |
@ -1990,14 +1996,19 @@ XLSX.utils.sheet_add_aoa(ws, [[4,5,6,7,8,9,0]], {origin: -1});
`XLSX.utils.json_to_sheet` takes an array of objects and returns a worksheet
with automatically-generated "headers" based on the keys of the objects. The
default column order is determined by the first appearance of the field using
`Object.keys`, but can be overridden using the options argument:
`Object.keys`. The function accepts an options argument:
| Option Name | Default | Description |
| :---------- | :------: | :-------------------------------------------------- |
|`header` | | Use specified column order (default `Object.keys`) |
|`dateNF` | FMT 14 | Use specified date format in string output |
|`cellDates` | false | Store dates as type `d` (default is `n`) |
|`skipHeader` | false | If true, do not include header row in output |
| Option Name | Default | Description |
| :---------- | :-----: | :--------------------------------------------------- |
|`header` | | Use specified field order (default `Object.keys`) ** |
|`dateNF` | FMT 14 | Use specified date format in string output |
|`cellDates` | false | Store dates as type `d` (default is `n`) |
|`skipHeader` | false | If true, do not include header row in output |
- All fields from each row will be written. If `header` is an array and it does
not contain a particular field, the key will be appended to the array.
- Cell types are deduced from the type of each value. For example, a `Date`
object will generate a Date cell, while a string will generate a Text cell.
<details>
<summary><b>Examples</b> (click to show)</summary>
@ -2368,31 +2379,31 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| Format | Read | Write |
|:-------------------------------------------------------------|:-----:|:-----:|
| **Excel Worksheet/Workbook Formats** |:-----:|:-----:|
| Excel 2007+ XML Formats (XLSX/XLSM) | :o: | :o: |
| Excel 2007+ Binary Format (XLSB BIFF12) | :o: | :o: |
| Excel 2003-2004 XML Format (XML "SpreadsheetML") | :o: | :o: |
| Excel 97-2004 (XLS BIFF8) | :o: | :o: |
| Excel 5.0/95 (XLS BIFF5) | :o: | :o: |
| Excel 4.0 (XLS/XLW BIFF4) | :o: | |
| Excel 3.0 (XLS BIFF3) | :o: | |
| Excel 2.0/2.1 (XLS BIFF2) | :o: | :o: |
| Excel 2007+ XML Formats (XLSX/XLSM) | ✔ | ✔ |
| Excel 2007+ Binary Format (XLSB BIFF12) | ✔ | ✔ |
| Excel 2003-2004 XML Format (XML "SpreadsheetML") | ✔ | ✔ |
| Excel 97-2004 (XLS BIFF8) | ✔ | ✔ |
| Excel 5.0/95 (XLS BIFF5) | ✔ | ✔ |
| Excel 4.0 (XLS/XLW BIFF4) | ✔ | ✔ |
| Excel 3.0 (XLS BIFF3) | ✔ | ✔ |
| Excel 2.0/2.1 (XLS BIFF2) | ✔ | ✔ |
| **Excel Supported Text Formats** |:-----:|:-----:|
| Delimiter-Separated Values (CSV/TXT) | :o: | :o: |
| Data Interchange Format (DIF) | :o: | :o: |
| Symbolic Link (SYLK/SLK) | :o: | :o: |
| Lotus Formatted Text (PRN) | :o: | :o: |
| UTF-16 Unicode Text (TXT) | :o: | :o: |
| Delimiter-Separated Values (CSV/TXT) | ✔ | ✔ |
| Data Interchange Format (DIF) | ✔ | ✔ |
| Symbolic Link (SYLK/SLK) | ✔ | ✔ |
| Lotus Formatted Text (PRN) | ✔ | ✔ |
| UTF-16 Unicode Text (TXT) | ✔ | ✔ |
| **Other Workbook/Worksheet Formats** |:-----:|:-----:|
| OpenDocument Spreadsheet (ODS) | :o: | :o: |
| Flat XML ODF Spreadsheet (FODS) | :o: | :o: |
| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | :o: | |
| dBASE II/III/IV / Visual FoxPro (DBF) | :o: | :o: |
| Lotus 1-2-3 (WKS/WK1/WK2/WK3/WK4/123) | :o: | |
| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | :o: | |
| OpenDocument Spreadsheet (ODS) | ✔ | ✔ |
| Flat XML ODF Spreadsheet (FODS) | ✔ | ✔ |
| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | ✔ | |
| dBASE II/III/IV / Visual FoxPro (DBF) | ✔ | ✔ |
| Lotus 1-2-3 (WKS/WK1/WK2/WK3/WK4/123) | ✔ | |
| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | |
| **Other Common Spreadsheet Output Formats** |:-----:|:-----:|
| HTML Tables | :o: | :o: |
| Rich Text Format tables (RTF) | | :o: |
| Ethercalc Record Format (ETH) | :o: | :o: |
| HTML Tables | ✔ | ✔ |
| Rich Text Format tables (RTF) | | ✔ |
| Ethercalc Record Format (ETH) | ✔ | ✔ |
Features not supported by a given file format will not be written. Formats with
range limits will be silently truncated:
@ -2403,6 +2414,8 @@ range limits will be silently truncated:
| Excel 2007+ Binary Format (XLSB BIFF12) | XFD1048576 | 16384 | 1048576 |
| Excel 97-2004 (XLS BIFF8) | IV65536 | 256 | 65536 |
| Excel 5.0/95 (XLS BIFF5) | IV16384 | 256 | 16384 |
| Excel 4.0 (XLS BIFF4) | IV16384 | 256 | 16384 |
| Excel 3.0 (XLS BIFF3) | IV16384 | 256 | 16384 |
| Excel 2.0/2.1 (XLS BIFF2) | IV16384 | 256 | 16384 |
Excel 2003 SpreadsheetML range limits are governed by the version of Excel and

@ -24,8 +24,8 @@ program
.option('-Y, --ods', 'emit ODS to <sheetname> or <file>.ods')
.option('-8, --xls', 'emit XLS to <sheetname> or <file>.xls (BIFF8)')
.option('-5, --biff5','emit XLS to <sheetname> or <file>.xls (BIFF5)')
//.option('-4, --biff4','emit XLS to <sheetname> or <file>.xls (BIFF4)')
//.option('-3, --biff3','emit XLS to <sheetname> or <file>.xls (BIFF3)')
.option('-4, --biff4','emit XLS to <sheetname> or <file>.xls (BIFF4)')
.option('-3, --biff3','emit XLS to <sheetname> or <file>.xls (BIFF3)')
.option('-2, --biff2','emit XLS to <sheetname> or <file>.xls (BIFF2)')
.option('-i, --xla', 'emit XLA to <sheetname> or <file>.xla')
.option('-6, --xlml', 'emit SSML to <sheetname> or <file>.xls (2003 XML)')
@ -256,7 +256,7 @@ switch(true) {
default:
if(!program.book) {
var stream = X.stream.to_csv(ws, {FS:program.fieldSep, RS:program.rowSep});
var stream = X.stream.to_csv(ws, {FS:program.fieldSep||",", RS:program.rowSep||"\n"});
if(program.output) stream.pipe(fs.createWriteStream(program.output));
else stream.pipe(process.stdout);
} else doit(function(ws) { return X.utils.sheet_to_csv(ws,{FS:program.fieldSep, RS:program.rowSep}); });

@ -87,6 +87,7 @@ function format_cell(cell/*:Cell*/, v/*:any*/, o/*:any*/) {
if(cell == null || cell.t == null || cell.t == 'z') return "";
if(cell.w !== undefined) return cell.w;
if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF;
if(cell.t == "e") return BErr[cell.v] || cell.v;
if(v == undefined) return safe_format_cell(cell, cell.v);
return safe_format_cell(cell, v);
}

@ -403,7 +403,7 @@ function parse_Bes(blob/*::, length*/) {
}
function write_Bes(v, t/*:string*/, o) {
if(!o) o = new_buf(2);
o.write_shift(1, +v);
o.write_shift(1, ((t == 'e') ? +v : +!!v));
o.write_shift(1, ((t == 'e') ? 1 : 0));
return o;
}

@ -422,6 +422,7 @@ function write_LabelSst(R/*:number*/, C/*:number*/, v/*:number*/, os/*:number*/
/* [MS-XLS] 2.4.148 */
function parse_Label(blob, length, opts) {
if(opts.biffguess && opts.biff == 2) opts.biff = 5;
var target = blob.l + length;
var cell = parse_XLSCell(blob, 6);
if(opts.biff == 2) blob.l++;
@ -573,7 +574,9 @@ function write_XF(data, ixfeP, opts, o) {
o.write_shift(2, (data.numFmtId||0));
o.write_shift(2, (ixfeP<<4));
}
o.write_shift(4, 0);
var f = 0;
if(data.numFmtId > 0 && b5) f |= 0x0400;
o.write_shift(4, f);
o.write_shift(4, 0);
if(!b5) o.write_shift(4, 0);
o.write_shift(2, 0);
@ -600,7 +603,7 @@ function write_Guts(guts/*:Array<number>*/) {
/* [MS-XLS] 2.4.24 */
function parse_BoolErr(blob, length, opts) {
var cell = parse_XLSCell(blob, 6);
if(opts.biff == 2) ++blob.l;
if(opts.biff == 2 || length == 9) ++blob.l;
var val = parse_Bes(blob, 2);
cell.val = val;
cell.t = (val === true || val === false) ? 'b' : 'e';
@ -614,7 +617,8 @@ function write_BoolErr(R/*:number*/, C/*:number*/, v, os/*:number*/, opts, t/*:s
}
/* [MS-XLS] 2.4.180 Number */
function parse_Number(blob) {
function parse_Number(blob, length, opts) {
if(opts.biffguess && opts.biff == 2) opts.biff = 5;
var cell = parse_XLSCell(blob, 6);
var xnum = parse_Xnum(blob, 8);
cell.val = xnum;
@ -1025,6 +1029,7 @@ function parse_ImData(blob) {
/* BIFF2_??? where ??? is the name from [XLS] */
function parse_BIFF2STR(blob, length, opts) {
if(opts.biffguess && opts.biff == 5) opts.biff = 2;
var cell = parse_XLSCell(blob, 6);
++blob.l;
var str = parse_XLUnicodeString2(blob, length-7, opts);

@ -935,8 +935,7 @@ function stringify_formula(formula/*Array<any>*/, range, cell/*:any*/, supbooks,
if(supbooks.sharedf[encode_cell(c)]) {
var parsedf = (supbooks.sharedf[encode_cell(c)]);
stack.push(stringify_formula(parsedf, _range, q, supbooks, opts));
}
else {
} else {
var fnd = false;
for(e1=0;e1!=supbooks.arrayf.length; ++e1) {
/* TODO: should be something like range_has */

@ -217,7 +217,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(R.r === 2 || R.r == 12) {
var rt = blob.read_shift(2); length -= 2;
if(!opts.enc && rt !== RecordType && (((rt&0xFF)<<8)|(rt>>8)) !== RecordType) throw new Error("rt mismatch: " + rt + "!=" + RecordType);
if(R.r == 12){ blob.l += 10; length -= 10; } // skip FRT
if(R.r == 12){
blob.l += 10; length -= 10;
} // skip FRT
}
//console.error(R,blob.l,length,blob.length);
var val/*:any*/ = ({}/*:any*/);
@ -359,13 +361,16 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
/*::[*/0x0002/*::]*/:2,
/*::[*/0x0007/*::]*/:2
}[val.BIFFVer] || 8;
opts.biffguess = val.BIFFVer == 0;
if(val.BIFFVer == 0 && val.dt == 0x1000) { opts.biff = 5; seen_codepage = true; set_cp(opts.codepage = 28591); }
if(opts.biff == 8 && val.BIFFVer == 0 && val.dt == 16) opts.biff = 2;
if(file_depth++) break;
cell_valid = true;
out = ((options.dense ? [] : {})/*:any*/);
if(opts.biff < 8 && !seen_codepage) { seen_codepage = true; set_cp(opts.codepage = options.codepage || 1252); }
if(opts.biff < 5) {
if(opts.biff < 5 || val.BIFFVer == 0 && val.dt == 0x1000) {
if(cur_sheet === "") cur_sheet = "Sheet1";
range = {s:{r:0,c:0},e:{r:0,c:0}};
/* fake BoundSheet8 */
@ -388,19 +393,19 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
if(out["!type"] == "chart") if(options.dense ? (out[val.r]||[])[val.c]: out[encode_cell({c:val.c, r:val.r})]) ++val.c;
temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe]||{}, v:val.val, t:'n'}/*:any*/);
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
} break;
case 'BoolErr': {
temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:val.t}/*:any*/);
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
} break;
case 'RK': {
temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.rknum, t:'n'}/*:any*/);
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
} break;
@ -408,7 +413,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
for(var j = val.c; j <= val.C; ++j) {
var ixfe = val.rkrec[j-val.c][0];
temp_val= ({ixfe:ixfe, XF:XFs[ixfe], v:val.rkrec[j-val.c][1], t:'n'}/*:any*/);
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:j, r:val.r}, temp_val, options);
}
@ -426,7 +431,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
else temp_val.F = ((options.dense ? (out[_fr]||[])[_fc]: out[_fe]) || {}).F;
} else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
}
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell(val.cell, temp_val, options);
last_formula = val;
@ -439,7 +444,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(options.cellFormula) {
temp_val.f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts);
}
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell(last_formula.cell, temp_val, options);
last_formula = null;
@ -471,13 +476,13 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
temp_val=make_cell(sst[val.isst].t, val.ixfe, 's');
if(sst[val.isst].h) temp_val.h = sst[val.isst].h;
temp_val.XF = XFs[temp_val.ixfe];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
break;
case 'Blank': if(options.sheetStubs) {
temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], t:'z'}/*:any*/);
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
} break;
@ -485,7 +490,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
for(var _j = val.c; _j <= val.C; ++_j) {
var _ixfe = val.ixfe[_j-val.c];
temp_val= ({ixfe:_ixfe, XF:XFs[_ixfe], t:'z'}/*:any*/);
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:_j, r:val.r}, temp_val, options);
}
@ -494,7 +499,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
case 'Label': case 'BIFF2STR':
temp_val=make_cell(val.val, val.ixfe, 's');
temp_val.XF = XFs[temp_val.ixfe];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F];
if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
break;
@ -832,6 +837,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
/* BIFF2-4 records */
case 'BIFF2FONTCLR': case 'BIFF2FMTCNT': case 'BIFF2FONTXTRA': break;
case 'BIFF2XF': case 'BIFF3XF': case 'BIFF4XF': break;
case 'BIFF2XFINDEX': break;
case 'BIFF4FMTCNT': case 'BIFF2ROW': case 'BIFF2WINDOW2': break;
/* Miscellaneous */

@ -1222,6 +1222,7 @@ var XLSRecordEnum = {
/*::[*/0x0034/*::]*/: { n:"DDEObjName" },
/*::[*/0x003e/*::]*/: { n:"BIFF2WINDOW2" },
/*::[*/0x0043/*::]*/: { n:"BIFF2XF" },
/*::[*/0x0044/*::]*/: { n:"BIFF2XFINDEX", f:parseuint16 },
/*::[*/0x0045/*::]*/: { n:"BIFF2FONTCLR" },
/*::[*/0x0056/*::]*/: { n:"BIFF4FMTCNT" }, /* 16-bit cnt, similar to BIFF2 */
/*::[*/0x007e/*::]*/: { n:"RK" }, /* Not necessarily same as 0x027e */

@ -43,8 +43,7 @@ function write_BIFF2Cell(out, r/*:number*/, c/*:number*/) {
function write_BIFF2BERR(r/*:number*/, c/*:number*/, val, t/*:?string*/) {
var out = new_buf(9);
write_BIFF2Cell(out, r, c);
if(t == 'e') { out.write_shift(1, val); out.write_shift(1, 1); }
else { out.write_shift(1, val?1:0); out.write_shift(1, 0); }
write_Bes(val, t || 'b', out);
return out;
}
@ -105,7 +104,7 @@ function write_biff2_buf(wb/*:Workbook*/, opts/*:WriteOpts*/) {
var idx = 0;
for(var i=0;i<wb.SheetNames.length;++i) if(wb.SheetNames[i] == o.sheet) idx=i;
if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
write_biff_rec(ba, 0x0009, write_BOF(wb, 0x10, o));
write_biff_rec(ba, (o.biff == 4 ? 0x0409 : (o.biff == 3 ? 0x0209 : 0x0009)), write_BOF(wb, 0x10, o));
/* ... */
write_ws_biff2(ba, wb.Sheets[wb.SheetNames[idx]], idx, o, wb);
/* ... */
@ -195,7 +194,7 @@ function write_ws_biff8_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:n
if(opts.bookSST) {
var isst = get_sst_id(opts.Strings, cell.v, opts.revStrings);
write_biff_rec(ba, "LabelSst", write_LabelSst(R, C, isst, os, opts));
} else write_biff_rec(ba, "Label", write_Label(R, C, cell.v, os, opts));
} else write_biff_rec(ba, "Label", write_Label(R, C, (cell.v||"").slice(0,255), os, opts));
break;
default:
write_biff_rec(ba, "Blank", write_XLSCell(R, C, os));

@ -26,24 +26,23 @@ Community Translations of this README:
[**Issues and Bug Reports**](https://github.com/sheetjs/sheetjs/issues)
[**File format support for known spreadsheet data formats:**](#file-formats)
![License](https://img.shields.io/github/license/SheetJS/sheetjs)
[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/SheetJS/sheetjs)](https://snyk.io/test/github/SheetJS/sheetjs)
[![npm Downloads](https://img.shields.io/npm/dm/xlsx.svg)](https://npmjs.org/package/xlsx)
[![jsDelivr Downloads](https://data.jsdelivr.com/v1/package/npm/xlsx/badge)](https://www.jsdelivr.com/package/npm/xlsx)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs)
<details>
<summary><b>Graph of supported formats</b> (click to show)</summary>
[**Browser Test and Support Matrix**](https://oss.sheetjs.com/sheetjs/tests/)
[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs)
**Supported File Formats**
![circo graph of format support](formats.png)
<details><summary><b>Diagram Legend</b> (click to show)</summary>
![graph legend](legend.png)
</details>
[**Browser Test**](https://oss.sheetjs.com/sheetjs/tests/)
[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs)
[![Build Status](https://semaphoreci.com/api/v1/sheetjs/sheetjs/branches/master/shields_badge.svg)](https://semaphoreci.com/sheetjs/sheetjs)
[![Coverage Status](https://img.shields.io/coveralls/SheetJS/sheetjs/master.svg)](https://coveralls.io/r/SheetJS/sheetjs?branch=master)
[![Dependencies Status](https://david-dm.org/sheetjs/sheetjs/status.svg)](https://david-dm.org/sheetjs/sheetjs)
[![npm Downloads](https://img.shields.io/npm/dt/xlsx.svg)](https://npmjs.org/package/xlsx)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs)

@ -41,6 +41,8 @@ output formats. The specific file type is controlled with `bookType` option:
| `xlsb` | `.xlsb` | ZIP | multi | Excel 2007+ Binary Format |
| `biff8` | `.xls` | CFB | multi | Excel 97-2004 Workbook Format |
| `biff5` | `.xls` | CFB | multi | Excel 5.0/95 Workbook Format |
| `biff4` | `.xls` | none | single | Excel 4.0 Worksheet Format |
| `biff3` | `.xls` | none | single | Excel 3.0 Worksheet Format |
| `biff2` | `.xls` | none | single | Excel 2.0 Worksheet Format |
| `xlml` | `.xls` | none | multi | Excel 2003-2004 (SpreadsheetML) |
| `ods` | `.ods` | ZIP | multi | OpenDocument Spreadsheet |

@ -10,8 +10,8 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| Excel 2003-2004 XML Format (XML "SpreadsheetML") | ✔ | ✔ |
| Excel 97-2004 (XLS BIFF8) | ✔ | ✔ |
| Excel 5.0/95 (XLS BIFF5) | ✔ | ✔ |
| Excel 4.0 (XLS/XLW BIFF4) | ✔ | |
| Excel 3.0 (XLS BIFF3) | ✔ | |
| Excel 4.0 (XLS/XLW BIFF4) | ✔ | ✔ |
| Excel 3.0 (XLS BIFF3) | ✔ | ✔ |
| Excel 2.0/2.1 (XLS BIFF2) | ✔ | ✔ |
| **Excel Supported Text Formats** |:-----:|:-----:|
| Delimiter-Separated Values (CSV/TXT) | ✔ | ✔ |
@ -40,6 +40,8 @@ range limits will be silently truncated:
| Excel 2007+ Binary Format (XLSB BIFF12) | XFD1048576 | 16384 | 1048576 |
| Excel 97-2004 (XLS BIFF8) | IV65536 | 256 | 65536 |
| Excel 5.0/95 (XLS BIFF5) | IV16384 | 256 | 16384 |
| Excel 4.0 (XLS BIFF4) | IV16384 | 256 | 16384 |
| Excel 3.0 (XLS BIFF3) | IV16384 | 256 | 16384 |
| Excel 2.0/2.1 (XLS BIFF2) | IV16384 | 256 | 16384 |
Excel 2003 SpreadsheetML range limits are governed by the version of Excel and

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 111 KiB

9
misc/coarsify.js Normal file

@ -0,0 +1,9 @@
/* based on the `coarse` project README */
const fs = require('fs');
const coarse = require('coarse');
const svg = fs.readFileSync(process.argv[2]);
const roughened = coarse(svg);
fs.writeFileSync(process.argv[3], roughened);

@ -26,23 +26,23 @@ Community Translations of this README:
[**Issues and Bug Reports**](https://github.com/sheetjs/sheetjs/issues)
[**File format support for known spreadsheet data formats:**](#file-formats)
![License](https://img.shields.io/github/license/SheetJS/sheetjs)
[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/SheetJS/sheetjs)](https://snyk.io/test/github/SheetJS/sheetjs)
[![npm Downloads](https://img.shields.io/npm/dm/xlsx.svg)](https://npmjs.org/package/xlsx)
[![jsDelivr Downloads](https://data.jsdelivr.com/v1/package/npm/xlsx/badge)](https://www.jsdelivr.com/package/npm/xlsx)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs)
![circo graph of format support](formats.png)
![graph legend](legend.png)
[**Browser Test**](https://oss.sheetjs.com/sheetjs/tests/)
[**Browser Test and Support Matrix**](https://oss.sheetjs.com/sheetjs/tests/)
[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs)
[![Build Status](https://semaphoreci.com/api/v1/sheetjs/sheetjs/branches/master/shields_badge.svg)](https://semaphoreci.com/sheetjs/sheetjs)
[![Coverage Status](https://img.shields.io/coveralls/SheetJS/sheetjs/master.svg)](https://coveralls.io/r/SheetJS/sheetjs?branch=master)
[![Dependencies Status](https://david-dm.org/sheetjs/sheetjs/status.svg)](https://david-dm.org/sheetjs/sheetjs)
[![npm Downloads](https://img.shields.io/npm/dt/xlsx.svg)](https://npmjs.org/package/xlsx)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs)
**Supported File Formats**
![circo graph of format support](formats.png)
![graph legend](legend.png)
## Table of Contents
@ -154,7 +154,7 @@ In the browser, just add a script tag:
|-----------:|:-------------------------------------------|
| `unpkg` | <https://unpkg.com/xlsx/> |
| `jsDelivr` | <https://jsdelivr.com/package/npm/xlsx> |
| `CDNjs` | <https://cdnjs.com/libraries/xlsx> |
| `CDNjs` | <https://cdnjs.com/libraries/xlsx> |
| `packd` | <https://bundle.run/xlsx@latest?name=XLSX> |
`unpkg` makes the latest version available at:
@ -234,7 +234,11 @@ An appropriate version for each dependency is included in the dist/ directory.
The complete single-file version is generated at `dist/xlsx.full.min.js`
A slimmer build with XLSX / HTML support is generated at `dist/xlsx.mini.min.js`
A slimmer build is generated at `dist/xlsx.mini.min.js`. Compared to full build:
- codepage library skipped (no support for XLS encodings)
- XLSX compression option not currently available
- no support for XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003
- node stream utils removed
Webpack and Browserify builds include optional modules by default. Webpack can
be configured to remove support with `resolve.alias`:
@ -768,7 +772,7 @@ Parse options are described in the [Parsing Options](#parsing-options) section.
`XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`.
In browser-based environments, it will attempt to force a client-side download.
`XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`.
`XLSX.writeFileAsync(wb, filename, o, cb)` attempts to write `wb` to `filename`.
If `o` is omitted, the writer will use the third argument as the callback.
`XLSX.stream` contains a set of streaming write functions.
@ -1231,10 +1235,10 @@ prefixed with an apostrophe `'`, consistent with Excel's formula bar display.
| Storage Representation | Formats | Read | Write |
|:-----------------------|:-------------------------|:-----:|:-----:|
| A1-style strings | XLSX | :o: | :o: |
| RC-style strings | XLML and plain text | :o: | :o: |
| BIFF Parsed formulae | XLSB and all XLS formats | :o: | |
| OpenFormula formulae | ODS/FODS/UOS | :o: | :o: |
| A1-style strings | XLSX | ✔ | ✔ |
| RC-style strings | XLML and plain text | ✔ | ✔ |
| BIFF Parsed formulae | XLSB and all XLS formats | ✔ | |
| OpenFormula formulae | ODS/FODS/UOS | ✔ | ✔ |
Since Excel prohibits named cells from colliding with names of A1 or RC style
cell references, a (not-so-simple) regex conversion is possible. BIFF Parsed
@ -1257,6 +1261,7 @@ type ColInfo = {
wch?: number; // width in characters
/* other fields for preserving features from files */
level?: number; // 0-indexed outline / group level
MDW?: number; // Excel's "Max Digit Width" unit, always integral
};
```
@ -1702,6 +1707,8 @@ output formats. The specific file type is controlled with `bookType` option:
| `xlsb` | `.xlsb` | ZIP | multi | Excel 2007+ Binary Format |
| `biff8` | `.xls` | CFB | multi | Excel 97-2004 Workbook Format |
| `biff5` | `.xls` | CFB | multi | Excel 5.0/95 Workbook Format |
| `biff4` | `.xls` | none | single | Excel 4.0 Worksheet Format |
| `biff3` | `.xls` | none | single | Excel 3.0 Worksheet Format |
| `biff2` | `.xls` | none | single | Excel 2.0 Worksheet Format |
| `xlml` | `.xls` | none | multi | Excel 2003-2004 (SpreadsheetML) |
| `ods` | `.ods` | ZIP | multi | OpenDocument Spreadsheet |
@ -1834,14 +1841,19 @@ XLSX.utils.sheet_add_aoa(ws, [[4,5,6,7,8,9,0]], {origin: -1});
`XLSX.utils.json_to_sheet` takes an array of objects and returns a worksheet
with automatically-generated "headers" based on the keys of the objects. The
default column order is determined by the first appearance of the field using
`Object.keys`, but can be overridden using the options argument:
`Object.keys`. The function accepts an options argument:
| Option Name | Default | Description |
| :---------- | :------: | :-------------------------------------------------- |
|`header` | | Use specified column order (default `Object.keys`) |
|`dateNF` | FMT 14 | Use specified date format in string output |
|`cellDates` | false | Store dates as type `d` (default is `n`) |
|`skipHeader` | false | If true, do not include header row in output |
| Option Name | Default | Description |
| :---------- | :-----: | :--------------------------------------------------- |
|`header` | | Use specified field order (default `Object.keys`) ** |
|`dateNF` | FMT 14 | Use specified date format in string output |
|`cellDates` | false | Store dates as type `d` (default is `n`) |
|`skipHeader` | false | If true, do not include header row in output |
- All fields from each row will be written. If `header` is an array and it does
not contain a particular field, the key will be appended to the array.
- Cell types are deduced from the type of each value. For example, a `Date`
object will generate a Date cell, while a string will generate a Text cell.
The original sheet cannot be reproduced using plain objects since JS object keys
@ -2188,31 +2200,31 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| Format | Read | Write |
|:-------------------------------------------------------------|:-----:|:-----:|
| **Excel Worksheet/Workbook Formats** |:-----:|:-----:|
| Excel 2007+ XML Formats (XLSX/XLSM) | :o: | :o: |
| Excel 2007+ Binary Format (XLSB BIFF12) | :o: | :o: |
| Excel 2003-2004 XML Format (XML "SpreadsheetML") | :o: | :o: |
| Excel 97-2004 (XLS BIFF8) | :o: | :o: |
| Excel 5.0/95 (XLS BIFF5) | :o: | :o: |
| Excel 4.0 (XLS/XLW BIFF4) | :o: | |
| Excel 3.0 (XLS BIFF3) | :o: | |
| Excel 2.0/2.1 (XLS BIFF2) | :o: | :o: |
| Excel 2007+ XML Formats (XLSX/XLSM) | ✔ | ✔ |
| Excel 2007+ Binary Format (XLSB BIFF12) | ✔ | ✔ |
| Excel 2003-2004 XML Format (XML "SpreadsheetML") | ✔ | ✔ |
| Excel 97-2004 (XLS BIFF8) | ✔ | ✔ |
| Excel 5.0/95 (XLS BIFF5) | ✔ | ✔ |
| Excel 4.0 (XLS/XLW BIFF4) | ✔ | ✔ |
| Excel 3.0 (XLS BIFF3) | ✔ | ✔ |
| Excel 2.0/2.1 (XLS BIFF2) | ✔ | ✔ |
| **Excel Supported Text Formats** |:-----:|:-----:|
| Delimiter-Separated Values (CSV/TXT) | :o: | :o: |
| Data Interchange Format (DIF) | :o: | :o: |
| Symbolic Link (SYLK/SLK) | :o: | :o: |
| Lotus Formatted Text (PRN) | :o: | :o: |
| UTF-16 Unicode Text (TXT) | :o: | :o: |
| Delimiter-Separated Values (CSV/TXT) | ✔ | ✔ |
| Data Interchange Format (DIF) | ✔ | ✔ |
| Symbolic Link (SYLK/SLK) | ✔ | ✔ |
| Lotus Formatted Text (PRN) | ✔ | ✔ |
| UTF-16 Unicode Text (TXT) | ✔ | ✔ |
| **Other Workbook/Worksheet Formats** |:-----:|:-----:|
| OpenDocument Spreadsheet (ODS) | :o: | :o: |
| Flat XML ODF Spreadsheet (FODS) | :o: | :o: |
| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | :o: | |
| dBASE II/III/IV / Visual FoxPro (DBF) | :o: | :o: |
| Lotus 1-2-3 (WKS/WK1/WK2/WK3/WK4/123) | :o: | |
| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | :o: | |
| OpenDocument Spreadsheet (ODS) | ✔ | ✔ |
| Flat XML ODF Spreadsheet (FODS) | ✔ | ✔ |
| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | ✔ | |
| dBASE II/III/IV / Visual FoxPro (DBF) | ✔ | ✔ |
| Lotus 1-2-3 (WKS/WK1/WK2/WK3/WK4/123) | ✔ | |
| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | |
| **Other Common Spreadsheet Output Formats** |:-----:|:-----:|
| HTML Tables | :o: | :o: |
| Rich Text Format tables (RTF) | | :o: |
| Ethercalc Record Format (ETH) | :o: | :o: |
| HTML Tables | ✔ | ✔ |
| Rich Text Format tables (RTF) | | ✔ |
| Ethercalc Record Format (ETH) | ✔ | ✔ |
Features not supported by a given file format will not be written. Formats with
range limits will be silently truncated:
@ -2223,6 +2235,8 @@ range limits will be silently truncated:
| Excel 2007+ Binary Format (XLSB BIFF12) | XFD1048576 | 16384 | 1048576 |
| Excel 97-2004 (XLS BIFF8) | IV65536 | 256 | 65536 |
| Excel 5.0/95 (XLS BIFF5) | IV16384 | 256 | 16384 |
| Excel 4.0 (XLS BIFF4) | IV16384 | 256 | 16384 |
| Excel 3.0 (XLS BIFF3) | IV16384 | 256 | 16384 |
| Excel 2.0/2.1 (XLS BIFF2) | IV16384 | 256 | 16384 |
Excel 2003 SpreadsheetML range limits are governed by the version of Excel and

@ -1,8 +1,9 @@
digraph G {
graph [mindist=0.1];
node [fontname="Indie Flower"];
csf [shape=doublecircle,label="Common\nSpreadsheet\nFormat\n(JS Object)"];
subgraph XL {
node [style=filled,color=green];
node [style=filled,color="#00FF00"];
xlsx [label="XLSX\nXLSM"];
xlsb [label="XLSB\nBIFF12"];
xlml [label="SSML\n(2003/4)"];
@ -58,7 +59,9 @@ digraph G {
xls2 -> csf
csf -> xls2
xls3 -> csf
csf -> xls3
xls4 -> csf
csf -> xls4
csf -> slk
slk -> csf
csf -> dif

@ -1,38 +1,45 @@
digraph G {
graph [mindist=0];
node [fontname="Indie Flower"];
labelloc=t;
label="Legend"
//label="Legend"
fontname="Indie Flower"
style=filled
fillcolor="transparent"
subgraph cluster_0 {
label="Supported Format Types"
color="white"
XL[label="Excel",style=filled,color=green];
color="transparent"
fontname="Indie Flower"
XL[label="Excel",style=filled,color="#00FF00"];
CSF[label="JS",shape=doublecircle];
OLD[label="Other",style=filled,color=cyan];
{ edge[style=invis] XL -> CSF -> OLD[constraint=false]}
}
subgraph cluster_1 {
label="Workbook Format Conversions (blue arrow)"
color="white"
label="Workbook Format Conversions\n(blue arrow)"
color="transparent"
fontname="Indie Flower"
x1i[label="XLSX"]
c1[shape=doublecircle,label="JS"];
x1o[label="XLSB"]
{ edge[color=blue]
x1i->c1[constraint=false,label="read"]
c1->x1o[constraint=false,label="write"];
x1i->c1[constraint=false,label="read",fontname="Indie Flower"]
c1->x1o[constraint=false,label="write",fontname="Indie Flower"];
}
}
subgraph cluster_2 {
label="Single-Worksheet Format Conversions (green arrow)"
color="white"
label="Single-Worksheet Format Conversions\n(green arrow)"
color="transparent"
fontname="Indie Flower"
x2i[label="SYLK"]
c2[shape=doublecircle,label="JS"];
x2o[label="CSV"]
{ edge[color=aquamarine4]
x2i->c2[constraint=false,label="read"]
c2->x2o[constraint=false,label="write"];
x2i->c2[constraint=false,label="read",fontname="Indie Flower"]
c2->x2o[constraint=false,label="write",fontname="Indie Flower"];
}
}