reamkit 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/dist/esm/core/converter/facade.d.ts +12 -6
- package/dist/esm/core/converter/facade.js +28 -4
- package/dist/esm/core/converter/ream.d.ts +5 -3
- package/dist/esm/core/converter/ream.js +16 -3
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +18 -0
- package/dist/esm/core/drawingml/sparkline-geometry.d.ts +8 -0
- package/dist/esm/core/drawingml/sparkline-geometry.js +111 -0
- package/dist/esm/core/ir/adapters.d.ts +5 -5
- package/dist/esm/core/ir/sheet.d.ts +31 -0
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -0
- package/dist/esm/core/spreadsheet-model/types.d.ts +184 -0
- package/dist/esm/excel/conditional-format.d.ts +16 -0
- package/dist/esm/excel/conditional-format.js +305 -0
- package/dist/esm/excel/index.d.ts +4 -3
- package/dist/esm/excel/print-model.d.ts +2 -1
- package/dist/esm/excel/print-model.js +114 -2
- package/dist/esm/excel/sheet-drawing.d.ts +1 -1
- package/dist/esm/excel/sheet-to-flow.d.ts +3 -0
- package/dist/esm/excel/sheet-to-flow.js +51 -0
- package/dist/esm/excel/styles-parser.d.ts +1 -50
- package/dist/esm/excel/styles-parser.js +38 -1
- package/dist/esm/excel/table-parser.d.ts +2 -0
- package/dist/esm/excel/table-parser.js +51 -0
- package/dist/esm/excel/workbook-parser.d.ts +1 -5
- package/dist/esm/excel/worksheet-parser.d.ts +1 -60
- package/dist/esm/excel/worksheet-parser.js +239 -1
- package/dist/esm/excel/xlsx-reader.d.ts +3 -1
- package/dist/esm/excel/xlsx-reader.js +86 -51
- package/dist/esm/excel/xlsx-writer.d.ts +4 -0
- package/dist/esm/excel/xlsx-writer.js +271 -0
- package/dist/esm/html/html-writer.js +27 -0
- package/dist/esm/layout/styled-layout.js +91 -1
- package/package.json +4 -3
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { FEATURES } from "../core/ir/features.js";
|
|
2
|
+
import { buildOpcPackage } from "../core/opc/opc-writer.js";
|
|
3
|
+
import "../core/opc/index.js";
|
|
4
|
+
//#region src/excel/xlsx-writer.ts
|
|
5
|
+
var encoder = new TextEncoder();
|
|
6
|
+
var REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
|
7
|
+
var REL_WORKSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
|
|
8
|
+
var REL_SHARED_STRINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
|
|
9
|
+
var REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
|
|
10
|
+
var REL_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
|
|
11
|
+
var CT_WORKBOOK = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
|
|
12
|
+
var CT_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
|
|
13
|
+
var CT_SHARED_STRINGS = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
|
|
14
|
+
var CT_STYLES = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
|
|
15
|
+
var CT_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml";
|
|
16
|
+
var MAIN_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
17
|
+
var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
18
|
+
function writeXlsx(sheet) {
|
|
19
|
+
const losses = [];
|
|
20
|
+
const worksheetParts = [];
|
|
21
|
+
const tableParts = [];
|
|
22
|
+
const worksheetRels = [];
|
|
23
|
+
const sheetEntries = [];
|
|
24
|
+
const workbookRels = [];
|
|
25
|
+
let tableCounter = 0;
|
|
26
|
+
sheet.sheets.forEach((s, i) => {
|
|
27
|
+
const idx = i + 1;
|
|
28
|
+
const rid = `rId${idx}`;
|
|
29
|
+
const wsPath = `xl/worksheets/sheet${idx}.xml`;
|
|
30
|
+
const tableRelIds = [];
|
|
31
|
+
const wsTableRels = [];
|
|
32
|
+
for (const t of s.grid.tables ?? []) {
|
|
33
|
+
const tid = ++tableCounter;
|
|
34
|
+
const tRid = `rIdTbl${tid}`;
|
|
35
|
+
tableRelIds.push(tRid);
|
|
36
|
+
tableParts.push({
|
|
37
|
+
path: `xl/tables/table${tid}.xml`,
|
|
38
|
+
data: encoder.encode(tableXml(t, tid)),
|
|
39
|
+
contentType: CT_TABLE
|
|
40
|
+
});
|
|
41
|
+
wsTableRels.push(rel(tRid, REL_TABLE, `../tables/table${tid}.xml`));
|
|
42
|
+
}
|
|
43
|
+
if (wsTableRels.length > 0) worksheetRels.push({
|
|
44
|
+
sourcePart: wsPath,
|
|
45
|
+
relationships: wsTableRels
|
|
46
|
+
});
|
|
47
|
+
worksheetParts.push({
|
|
48
|
+
path: wsPath,
|
|
49
|
+
data: encoder.encode(worksheetXml(s.grid, tableRelIds)),
|
|
50
|
+
contentType: CT_WORKSHEET
|
|
51
|
+
});
|
|
52
|
+
workbookRels.push(rel(rid, REL_WORKSHEET, `worksheets/sheet${idx}.xml`));
|
|
53
|
+
sheetEntries.push({
|
|
54
|
+
name: s.name,
|
|
55
|
+
sheetId: idx,
|
|
56
|
+
rid
|
|
57
|
+
});
|
|
58
|
+
recordSheetLosses(s, losses);
|
|
59
|
+
});
|
|
60
|
+
const sstRid = `rId${sheet.sheets.length + 1}`;
|
|
61
|
+
const stylesRid = `rId${sheet.sheets.length + 2}`;
|
|
62
|
+
workbookRels.push(rel(sstRid, REL_SHARED_STRINGS, "sharedStrings.xml"));
|
|
63
|
+
workbookRels.push(rel(stylesRid, REL_STYLES, "styles.xml"));
|
|
64
|
+
return {
|
|
65
|
+
bytes: buildOpcPackage({
|
|
66
|
+
parts: [
|
|
67
|
+
{
|
|
68
|
+
path: "xl/workbook.xml",
|
|
69
|
+
data: encoder.encode(workbookXml(sheetEntries, sheet.definedNames, sheet.date1904)),
|
|
70
|
+
contentType: CT_WORKBOOK
|
|
71
|
+
},
|
|
72
|
+
...worksheetParts,
|
|
73
|
+
...tableParts,
|
|
74
|
+
{
|
|
75
|
+
path: "xl/sharedStrings.xml",
|
|
76
|
+
data: encoder.encode(sharedStringsXml(sheet.sharedStrings)),
|
|
77
|
+
contentType: CT_SHARED_STRINGS
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
path: "xl/styles.xml",
|
|
81
|
+
data: encoder.encode(stylesXml(sheet.styles)),
|
|
82
|
+
contentType: CT_STYLES
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
rootRelationships: [rel("rId1", REL_OFFICE_DOCUMENT, "xl/workbook.xml")],
|
|
86
|
+
partRelationships: [{
|
|
87
|
+
sourcePart: "xl/workbook.xml",
|
|
88
|
+
relationships: workbookRels
|
|
89
|
+
}, ...worksheetRels]
|
|
90
|
+
}),
|
|
91
|
+
losses
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
new Set([FEATURES.text, FEATURES.tables]);
|
|
95
|
+
function rel(id, type, target) {
|
|
96
|
+
return {
|
|
97
|
+
id,
|
|
98
|
+
type,
|
|
99
|
+
target,
|
|
100
|
+
targetMode: "Internal"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function recordSheetLosses(s, losses) {
|
|
104
|
+
if (s.charts && s.charts.length > 0) losses.push({
|
|
105
|
+
severity: "dropped",
|
|
106
|
+
feature: FEATURES.charts,
|
|
107
|
+
detail: `sheet "${s.name}": embedded charts not written back`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function workbookXml(sheets, definedNames, date1904) {
|
|
111
|
+
const sheetTags = sheets.map((s) => `<sheet name="${escapeAttr(s.name)}" sheetId="${s.sheetId}" r:id="${s.rid}"/>`).join("");
|
|
112
|
+
const definedNamesXml = definedNames.length > 0 ? `<definedNames>${definedNames.map((d) => `<definedName name="${escapeAttr(d.name)}"${d.localSheetId !== void 0 ? ` localSheetId="${d.localSheetId}"` : ""}>${escapeText(d.value)}</definedName>`).join("")}</definedNames>` : "";
|
|
113
|
+
return XML_DECL + `<workbook xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (date1904 ? "<workbookPr date1904=\"1\"/>" : "") + `<sheets>${sheetTags}</sheets>` + definedNamesXml + "</workbook>";
|
|
114
|
+
}
|
|
115
|
+
function worksheetXml(grid, tableRelIds = []) {
|
|
116
|
+
const dimension = grid.maxRow >= 0 && grid.maxColumn >= 0 ? `<dimension ref="A1:${cellRef(grid.maxRow, grid.maxColumn)}"/>` : "<dimension ref=\"A1\"/>";
|
|
117
|
+
const colsXml = grid.columns.length > 0 ? `<cols>${grid.columns.map((c) => `<col min="${c.min}" max="${c.max}" width="${c.widthChars}" customWidth="1"/>`).join("")}</cols>` : "";
|
|
118
|
+
const heightByRow = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const rh of grid.rowHeights) heightByRow.set(rh.row, {
|
|
120
|
+
heightPt: rh.heightPt,
|
|
121
|
+
customHeight: rh.customHeight
|
|
122
|
+
});
|
|
123
|
+
const cellsByRow = /* @__PURE__ */ new Map();
|
|
124
|
+
for (const cell of grid.cells) {
|
|
125
|
+
let row = cellsByRow.get(cell.row);
|
|
126
|
+
if (!row) {
|
|
127
|
+
row = [];
|
|
128
|
+
cellsByRow.set(cell.row, row);
|
|
129
|
+
}
|
|
130
|
+
row.push(cell);
|
|
131
|
+
}
|
|
132
|
+
const rowsXml = [...new Set([...cellsByRow.keys(), ...heightByRow.keys()])].sort((a, b) => a - b).map((r) => {
|
|
133
|
+
const cells = (cellsByRow.get(r) ?? []).slice().sort((a, b) => a.column - b.column);
|
|
134
|
+
const h = heightByRow.get(r);
|
|
135
|
+
const heightAttrs = h ? ` ht="${h.heightPt}"${h.customHeight ? " customHeight=\"1\"" : ""}` : "";
|
|
136
|
+
return `<row r="${r + 1}"${heightAttrs}>${cells.map(cellXml).join("")}</row>`;
|
|
137
|
+
}).join("");
|
|
138
|
+
const mergesXml = grid.merges.length > 0 ? `<mergeCells count="${grid.merges.length}">${grid.merges.map((m) => `<mergeCell ref="${cellRef(m.startRow, m.startColumn)}:${cellRef(m.endRow, m.endColumn)}"/>`).join("")}</mergeCells>` : "";
|
|
139
|
+
return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
|
|
140
|
+
}
|
|
141
|
+
function tableXml(t, id) {
|
|
142
|
+
const ref = rangeRef(t.ref);
|
|
143
|
+
const name = t.name ?? `Table${id}`;
|
|
144
|
+
const ncols = t.ref.endColumn - t.ref.startColumn + 1;
|
|
145
|
+
const columns = Array.from({ length: ncols }, (_, c) => `<tableColumn id="${c + 1}" name="Column${c + 1}"/>`).join("");
|
|
146
|
+
const styleInfo = t.styleName ? `<tableStyleInfo name="${escapeAttr(t.styleName)}" showFirstColumn="${t.showFirstColumn ? 1 : 0}" showLastColumn="${t.showLastColumn ? 1 : 0}" showRowStripes="${t.showRowStripes ? 1 : 0}" showColumnStripes="${t.showColumnStripes ? 1 : 0}"/>` : "";
|
|
147
|
+
return XML_DECL + `<table xmlns="${MAIN_NS}" id="${id}" name="${escapeAttr(name)}" displayName="${escapeAttr(name)}" ref="${ref}"${t.headerRowCount !== 1 ? ` headerRowCount="${t.headerRowCount}"` : ""} totalsRowShown="0">` + (t.autoFilter ? `<autoFilter ref="${ref}"/>` : "") + `<tableColumns count="${ncols}">${columns}</tableColumns>` + styleInfo + "</table>";
|
|
148
|
+
}
|
|
149
|
+
function conditionalFormattingXml(cfs) {
|
|
150
|
+
if (!cfs || cfs.length === 0) return "";
|
|
151
|
+
return cfs.map((cf) => {
|
|
152
|
+
return `<conditionalFormatting sqref="${cf.ranges.map(rangeRef).join(" ")}">${cf.rules.map(cfRuleXml).join("")}</conditionalFormatting>`;
|
|
153
|
+
}).join("");
|
|
154
|
+
}
|
|
155
|
+
function cfRuleXml(rule) {
|
|
156
|
+
const p = ` priority="${rule.priority}"`;
|
|
157
|
+
switch (rule.type) {
|
|
158
|
+
case "cellIs": return `<cfRule type="cellIs"${p} operator="${rule.operator}" dxfId="${rule.dxfId}">` + rule.formulas.map((f) => `<formula>${escapeText(f)}</formula>`).join("") + "</cfRule>";
|
|
159
|
+
case "colorScale": return `<cfRule type="colorScale"${p}><colorScale>` + rule.cfvos.map(cfvoXml).join("") + rule.colorsHex.map((c) => `<color rgb="FF${c}"/>`).join("") + "</colorScale></cfRule>";
|
|
160
|
+
case "dataBar": return `<cfRule type="dataBar"${p}><dataBar` + (rule.minLength !== void 0 ? ` minLength="${rule.minLength}"` : "") + (rule.maxLength !== void 0 ? ` maxLength="${rule.maxLength}"` : "") + ">" + rule.cfvos.map(cfvoXml).join("") + `<color rgb="FF${rule.colorHex}"/></dataBar></cfRule>`;
|
|
161
|
+
case "iconSet": return `<cfRule type="iconSet"${p}><iconSet iconSet="${rule.iconSet}"` + (rule.reverse ? " reverse=\"1\"" : "") + ">" + rule.cfvos.map(cfvoXml).join("") + "</iconSet></cfRule>";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function cfvoXml(cfvo) {
|
|
165
|
+
return `<cfvo type="${cfvo.type}"${cfvo.val !== void 0 ? ` val="${escapeAttr(cfvo.val)}"` : ""}/>`;
|
|
166
|
+
}
|
|
167
|
+
var X14_NS = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main";
|
|
168
|
+
var XM_NS = "http://schemas.microsoft.com/office/excel/2006/main";
|
|
169
|
+
function sparklineExtXml(sparklines) {
|
|
170
|
+
if (!sparklines || sparklines.length === 0) return "";
|
|
171
|
+
return `<extLst><ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}" xmlns:x14="${X14_NS}"><x14:sparklineGroups xmlns:xm="${XM_NS}">${sparklines.map((sp) => {
|
|
172
|
+
return `<x14:sparklineGroup${sp.kind === "column" ? " type=\"column\"" : sp.kind === "winLoss" ? " type=\"stacked\"" : ""}>${sp.colorHex ? `<x14:colorSeries rgb="FF${sp.colorHex}"/>` : ""}<x14:sparklines><x14:sparkline><xm:f>${escapeText(sp.dataRange)}</xm:f><xm:sqref>${escapeText(sp.sqref)}</xm:sqref></x14:sparkline></x14:sparklines></x14:sparklineGroup>`;
|
|
173
|
+
}).join("")}</x14:sparklineGroups></ext></extLst>`;
|
|
174
|
+
}
|
|
175
|
+
function rangeRef(r) {
|
|
176
|
+
const a = cellRef(r.startRow, r.startColumn);
|
|
177
|
+
if (r.startRow === r.endRow && r.startColumn === r.endColumn) return a;
|
|
178
|
+
return `${a}:${cellRef(r.endRow, r.endColumn)}`;
|
|
179
|
+
}
|
|
180
|
+
function printOptionsXml(po) {
|
|
181
|
+
if (!po) return "";
|
|
182
|
+
const flag = (name, v) => v !== void 0 ? ` ${name}="${v ? "1" : "0"}"` : "";
|
|
183
|
+
const attrs = flag("gridLines", po.gridLines) + flag("horizontalCentered", po.horizontalCentered) + flag("verticalCentered", po.verticalCentered);
|
|
184
|
+
return attrs ? `<printOptions${attrs}/>` : "";
|
|
185
|
+
}
|
|
186
|
+
function pageMarginsXml(m) {
|
|
187
|
+
if (!m) return "";
|
|
188
|
+
return `<pageMargins left="${m.leftInches}" right="${m.rightInches}" top="${m.topInches}" bottom="${m.bottomInches}"` + (m.headerInches !== void 0 ? ` header="${m.headerInches}"` : "") + (m.footerInches !== void 0 ? ` footer="${m.footerInches}"` : "") + "/>";
|
|
189
|
+
}
|
|
190
|
+
function pageSetupXml(s) {
|
|
191
|
+
if (!s) return "";
|
|
192
|
+
const attr = (name, v) => v !== void 0 ? ` ${name}="${v}"` : "";
|
|
193
|
+
const attrs = attr("paperSize", s.paperSize) + attr("orientation", s.orientation) + attr("scale", s.scale) + attr("fitToWidth", s.fitToWidth) + attr("fitToHeight", s.fitToHeight);
|
|
194
|
+
return attrs ? `<pageSetup${attrs}/>` : "";
|
|
195
|
+
}
|
|
196
|
+
function breaksXml(tag, breaks) {
|
|
197
|
+
if (!breaks || breaks.length === 0) return "";
|
|
198
|
+
const brks = breaks.map((id) => `<brk id="${id}" man="1"/>`).join("");
|
|
199
|
+
return `<${tag} count="${breaks.length}" manualBreakCount="${breaks.length}">${brks}</${tag}>`;
|
|
200
|
+
}
|
|
201
|
+
function cellXml(cell) {
|
|
202
|
+
const ref = cellRef(cell.row, cell.column);
|
|
203
|
+
const sAttr = cell.styleIndex !== void 0 ? ` s="${cell.styleIndex}"` : "";
|
|
204
|
+
const tAttr = cell.type === "n" ? "" : ` t="${cell.type}"`;
|
|
205
|
+
if (cell.type === "inlineStr") return `<c r="${ref}"${sAttr}${tAttr}><is><t xml:space="preserve">${escapeText(cell.inlineText ?? "")}</t></is></c>`;
|
|
206
|
+
if (cell.rawValue === "") return `<c r="${ref}"${sAttr}${tAttr}/>`;
|
|
207
|
+
return `<c r="${ref}"${sAttr}${tAttr}><v>${escapeText(cell.rawValue)}</v></c>`;
|
|
208
|
+
}
|
|
209
|
+
function sharedStringsXml(strings) {
|
|
210
|
+
const items = strings.map((s) => `<si><t xml:space="preserve">${escapeText(s)}</t></si>`).join("");
|
|
211
|
+
return XML_DECL + `<sst xmlns="${MAIN_NS}" count="${strings.length}" uniqueCount="${strings.length}">` + items + "</sst>";
|
|
212
|
+
}
|
|
213
|
+
function stylesXml(styles) {
|
|
214
|
+
const numFmts = styles.numFmts.size > 0 ? `<numFmts count="${styles.numFmts.size}">${[...styles.numFmts].map(([id, code]) => `<numFmt numFmtId="${id}" formatCode="${escapeAttr(code)}"/>`).join("")}</numFmts>` : "";
|
|
215
|
+
const fonts = `<fonts count="${styles.fonts.length}">${styles.fonts.map(fontXml).join("")}</fonts>`;
|
|
216
|
+
const fills = `<fills count="${styles.fills.length}">${styles.fills.map(fillXml).join("")}</fills>`;
|
|
217
|
+
const borders = `<borders count="${styles.borders.length}">${styles.borders.map(borderXml).join("")}</borders>`;
|
|
218
|
+
const cellStyleXfs = "<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>";
|
|
219
|
+
const cellXfs = `<cellXfs count="${styles.cellXfs.length}">${styles.cellXfs.map(cellXfXml).join("")}</cellXfs>`;
|
|
220
|
+
const dxfs = styles.dxfs && styles.dxfs.length > 0 ? `<dxfs count="${styles.dxfs.length}">${styles.dxfs.map((d) => `<dxf>${d.font ? `<font>${fontInner(d.font)}</font>` : ""}${d.fill ? `<fill>${patternFill(d.fill)}</fill>` : ""}</dxf>`).join("")}</dxfs>` : "";
|
|
221
|
+
return XML_DECL + `<styleSheet xmlns="${MAIN_NS}">` + numFmts + fonts + fills + borders + cellStyleXfs + cellXfs + dxfs + "</styleSheet>";
|
|
222
|
+
}
|
|
223
|
+
function fontXml(f) {
|
|
224
|
+
return `<font>${fontInner(f)}</font>`;
|
|
225
|
+
}
|
|
226
|
+
function fontInner(f) {
|
|
227
|
+
return (f.sizePt !== void 0 ? `<sz val="${f.sizePt}"/>` : "") + (f.bold ? "<b/>" : "") + (f.italic ? "<i/>" : "") + (f.underline ? "<u/>" : "") + (f.colorHex ? `<color rgb="FF${f.colorHex}"/>` : "") + (f.name ? `<name val="${escapeAttr(f.name)}"/>` : "");
|
|
228
|
+
}
|
|
229
|
+
function fillXml(f) {
|
|
230
|
+
return `<fill>${patternFill(f)}</fill>`;
|
|
231
|
+
}
|
|
232
|
+
function patternFill(f) {
|
|
233
|
+
const inner = (f.fgColorHex ? `<fgColor rgb="FF${f.fgColorHex}"/>` : "") + (f.bgColorHex ? `<bgColor rgb="FF${f.bgColorHex}"/>` : "");
|
|
234
|
+
return `<patternFill${f.patternType ? ` patternType="${f.patternType}"` : ""}>${inner}</patternFill>`;
|
|
235
|
+
}
|
|
236
|
+
function borderXml(b) {
|
|
237
|
+
return `<border>${edgeXml("left", b.left)}${edgeXml("right", b.right)}${edgeXml("top", b.top)}${edgeXml("bottom", b.bottom)}</border>`;
|
|
238
|
+
}
|
|
239
|
+
function edgeXml(name, edge) {
|
|
240
|
+
if (!edge || !edge.style) return `<${name}/>`;
|
|
241
|
+
const color = edge.colorHex ? `<color rgb="FF${edge.colorHex}"/>` : "";
|
|
242
|
+
return `<${name} style="${edge.style}">${color}</${name}>`;
|
|
243
|
+
}
|
|
244
|
+
function cellXfXml(xf) {
|
|
245
|
+
const apply = applyAttr("applyNumberFormat", xf.applyNumberFormat) + applyAttr("applyFont", xf.applyFont) + applyAttr("applyFill", xf.applyFill) + applyAttr("applyBorder", xf.applyBorder) + applyAttr("applyAlignment", xf.applyAlignment);
|
|
246
|
+
const a = xf.alignment;
|
|
247
|
+
const alignment = a ? `<alignment${a.horizontal ? ` horizontal="${a.horizontal}"` : ""}${a.vertical ? ` vertical="${a.vertical}"` : ""}${a.wrapText ? " wrapText=\"1\"" : ""}/>` : "";
|
|
248
|
+
return `<xf numFmtId="${xf.numFmtId}" fontId="${xf.fontId}" fillId="${xf.fillId}" borderId="${xf.borderId}"${apply}>` + alignment + "</xf>";
|
|
249
|
+
}
|
|
250
|
+
function applyAttr(name, value) {
|
|
251
|
+
return value !== void 0 ? ` ${name}="${value ? "1" : "0"}"` : "";
|
|
252
|
+
}
|
|
253
|
+
var XML_DECL = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
|
|
254
|
+
function cellRef(row, col) {
|
|
255
|
+
let n = col + 1;
|
|
256
|
+
let letters = "";
|
|
257
|
+
while (n > 0) {
|
|
258
|
+
const rem = (n - 1) % 26;
|
|
259
|
+
letters = String.fromCharCode(65 + rem) + letters;
|
|
260
|
+
n = Math.floor((n - 1) / 26);
|
|
261
|
+
}
|
|
262
|
+
return `${letters}${row + 1}`;
|
|
263
|
+
}
|
|
264
|
+
function escapeText(s) {
|
|
265
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
266
|
+
}
|
|
267
|
+
function escapeAttr(s) {
|
|
268
|
+
return escapeText(s).replace(/"/g, """);
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
export { writeXlsx };
|
|
@@ -3,6 +3,7 @@ import { detectImageFormat } from "../core/images.js";
|
|
|
3
3
|
import { EMPTY_STYLE_SHEET, resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
|
|
4
4
|
import "../core/style-cascade/index.js";
|
|
5
5
|
import { PathBuilder, flipTransform, svgPathData } from "../core/vector.js";
|
|
6
|
+
import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
|
|
6
7
|
import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
7
8
|
import { buildChartScene } from "../core/drawingml/chart-geometry.js";
|
|
8
9
|
import { buildShapePaths, buildShapeTransform, buildStroke } from "../core/drawingml/shape-render.js";
|
|
@@ -402,6 +403,14 @@ function emitCell(out, cell, table, ctx, pos) {
|
|
|
402
403
|
pushBorder(css, "left", c?.left ?? (pos.firstCol ? t?.left : t?.insideV));
|
|
403
404
|
pushBorder(css, "right", c?.right ?? (pos.lastCol ? t?.right : t?.insideV));
|
|
404
405
|
if (cell.properties.shading) css.push(`background-color:#${cell.properties.shading.colorHex}`);
|
|
406
|
+
if (cell.properties.dataBar) {
|
|
407
|
+
const db = cell.properties.dataBar;
|
|
408
|
+
const clamp = (n) => Math.max(0, Math.min(1, n));
|
|
409
|
+
const start = (clamp(db.startFraction ?? 0) * 100).toFixed(2);
|
|
410
|
+
const end = (clamp((db.startFraction ?? 0) + db.fraction) * 100).toFixed(2);
|
|
411
|
+
const c = db.colorHex;
|
|
412
|
+
css.push(`background-image:linear-gradient(to right,transparent ${start}%,#${c} ${start}%,#${c} ${end}%,transparent ${end}%)`);
|
|
413
|
+
}
|
|
405
414
|
const margins = cell.properties.margins ?? table.properties.defaultCellMargins;
|
|
406
415
|
const padTop = margins?.top ?? 0;
|
|
407
416
|
const padRight = margins?.right ?? 5.4;
|
|
@@ -409,9 +418,27 @@ function emitCell(out, cell, table, ctx, pos) {
|
|
|
409
418
|
const padLeft = margins?.left ?? 5.4;
|
|
410
419
|
css.push(`padding:${fmt(padTop)}pt ${fmt(padRight)}pt ${fmt(padBottom)}pt ${fmt(padLeft)}pt`);
|
|
411
420
|
out.push(`<${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${css.join(";")}">`);
|
|
421
|
+
if (cell.properties.icon) out.push(cellIconSvg(cell.properties.icon));
|
|
422
|
+
if (cell.properties.sparkline) out.push(cellSparklineSvg(cell.properties.sparkline));
|
|
412
423
|
for (const child of cell.content) emitBlock(out, child, ctx);
|
|
413
424
|
out.push(`</${tag}>`);
|
|
414
425
|
}
|
|
426
|
+
function cellSparklineSvg(sp) {
|
|
427
|
+
const W = 56;
|
|
428
|
+
const H = 16;
|
|
429
|
+
if (sp.values.length === 0) return "";
|
|
430
|
+
const prims = buildSparkline(sp.kind, sp.values, W, H, sp.colorHex);
|
|
431
|
+
const fmt = (n) => (Math.round(n * 100) / 100).toString();
|
|
432
|
+
const parts = [];
|
|
433
|
+
for (const prim of prims) {
|
|
434
|
+
const d = prim.paths.map((p) => svgPathData(p.segments, fmt)).join(" ");
|
|
435
|
+
parts.push(prim.stroke ? `<path d="${d}" fill="none" stroke="#${prim.stroke.colorHex}" stroke-width="${prim.stroke.widthPt}"/>` : `<path d="${d}" fill="#${prim.fillColorHex ?? "376092"}"/>`);
|
|
436
|
+
}
|
|
437
|
+
return `<svg width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" style="vertical-align:middle"><g transform="translate(0,${H}) scale(1,-1)">${parts.join("")}</g></svg>`;
|
|
438
|
+
}
|
|
439
|
+
function cellIconSvg(icon) {
|
|
440
|
+
return `<svg width="10" height="10" viewBox="0 0 10 10" fill="${`#${icon.colorHex}`}" style="vertical-align:middle;margin-right:3px">${icon.shape === "square" ? `<rect x="1" y="1" width="8" height="8"/>` : icon.shape === "diamond" ? `<polygon points="5,1 9,5 5,9 1,5"/>` : icon.shape === "triangleUp" ? `<polygon points="5,1 9,9 1,9"/>` : icon.shape === "triangleDown" ? `<polygon points="1,1 9,1 5,9"/>` : icon.shape === "triangleRight" ? `<polygon points="9,5 1,1 1,9"/>` : `<circle cx="5" cy="5" r="4"/>`}</svg>`;
|
|
441
|
+
}
|
|
415
442
|
function pushBorder(css, side, border) {
|
|
416
443
|
if (!border || border.style === "none") return;
|
|
417
444
|
const width = border.width ?? .5;
|
|
@@ -12,6 +12,7 @@ import { DEFAULT_RESOLVED_PARAGRAPH, DEFAULT_RESOLVED_RUN } from "../core/style-
|
|
|
12
12
|
import { resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
|
|
13
13
|
import "../core/style-cascade/index.js";
|
|
14
14
|
import { PathBuilder, flipTransform } from "../core/vector.js";
|
|
15
|
+
import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
|
|
15
16
|
import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
16
17
|
import { buildChartScene } from "../core/drawingml/chart-geometry.js";
|
|
17
18
|
import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
|
|
@@ -560,6 +561,31 @@ function buildChartLayout(chart, widthPt, heightPt, font) {
|
|
|
560
561
|
function rectAtPath(x, y, w, h) {
|
|
561
562
|
return new PathBuilder().moveTo(x, y).lineTo(x + w, y).lineTo(x + w, y + h).lineTo(x, y + h).close().build();
|
|
562
563
|
}
|
|
564
|
+
var CF_ICON_SIZE_PT = 9;
|
|
565
|
+
var CF_ICON_GUTTER_PT = 12;
|
|
566
|
+
function buildCellIconShape(icon, size) {
|
|
567
|
+
return {
|
|
568
|
+
paths: [cellIconPath(icon.shape, size)],
|
|
569
|
+
fillColorHex: icon.colorHex
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function cellIconPath(shape, s) {
|
|
573
|
+
switch (shape) {
|
|
574
|
+
case "square": return rectAtPath(s * .12, s * .12, s * .76, s * .76);
|
|
575
|
+
case "diamond": return new PathBuilder().moveTo(s / 2, s * .92).lineTo(s * .92, s / 2).lineTo(s / 2, s * .08).lineTo(s * .08, s / 2).close().build();
|
|
576
|
+
case "triangleUp": return trianglePath([s / 2, s * .9], [s * .1, s * .12], [s * .9, s * .12]);
|
|
577
|
+
case "triangleDown": return trianglePath([s / 2, s * .1], [s * .1, s * .88], [s * .9, s * .88]);
|
|
578
|
+
case "triangleRight": return trianglePath([s * .9, s / 2], [s * .12, s * .1], [s * .12, s * .9]);
|
|
579
|
+
case "circle": return circlePath(s / 2, s / 2, s * .42);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function trianglePath(a, b, c) {
|
|
583
|
+
return new PathBuilder().moveTo(a[0], a[1]).lineTo(b[0], b[1]).lineTo(c[0], c[1]).close().build();
|
|
584
|
+
}
|
|
585
|
+
function circlePath(cx, cy, r) {
|
|
586
|
+
const k = .5522847498307936 * r;
|
|
587
|
+
return new PathBuilder().moveTo(cx + r, cy).cubicTo(cx + r, cy + k, cx + k, cy + r, cx, cy + r).cubicTo(cx - k, cy + r, cx - r, cy + k, cx - r, cy).cubicTo(cx - r, cy - k, cx - k, cy - r, cx, cy - r).cubicTo(cx + k, cy - r, cx + r, cy - k, cx + r, cy).close().build();
|
|
588
|
+
}
|
|
563
589
|
function rectPrim(r) {
|
|
564
590
|
return {
|
|
565
591
|
paths: [rectAtPath(r.x, r.y, r.w, r.h)],
|
|
@@ -1431,8 +1457,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1431
1457
|
const tableMar = tableProps.defaultCellMargins;
|
|
1432
1458
|
const padTopPt = cellMar?.top ?? tableMar?.top ?? 0;
|
|
1433
1459
|
const padBottomPt = cellMar?.bottom ?? tableMar?.bottom ?? 0;
|
|
1434
|
-
const
|
|
1460
|
+
const padLeftBase = cellMar?.left ?? tableMar?.left ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
|
|
1435
1461
|
const padRightPt = cellMar?.right ?? tableMar?.right ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
|
|
1462
|
+
const padLeftPt = padLeftBase + (cell.properties.icon ? CF_ICON_GUTTER_PT : 0);
|
|
1436
1463
|
const innerWidth = Math.max(1, widthPt - padLeftPt - padRightPt);
|
|
1437
1464
|
const lines = [];
|
|
1438
1465
|
const nestedTables = [];
|
|
@@ -1462,6 +1489,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1462
1489
|
padLeftPt,
|
|
1463
1490
|
borders,
|
|
1464
1491
|
...cell.properties.shading?.colorHex ? { shadingColorHex: cell.properties.shading.colorHex } : {},
|
|
1492
|
+
...cell.properties.dataBar ? { dataBar: cell.properties.dataBar } : {},
|
|
1493
|
+
...cell.properties.icon ? { icon: cell.properties.icon } : {},
|
|
1494
|
+
...cell.properties.sparkline ? { sparkline: cell.properties.sparkline } : {},
|
|
1465
1495
|
lines,
|
|
1466
1496
|
...nestedTables.length > 0 ? { nestedTables } : {},
|
|
1467
1497
|
contentHeightPt,
|
|
@@ -2084,6 +2114,66 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
2084
2114
|
height: pt(row.heightPt),
|
|
2085
2115
|
fillColorHex: cell.shadingColorHex
|
|
2086
2116
|
});
|
|
2117
|
+
if (cell.dataBar && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
|
|
2118
|
+
const start = Math.max(0, Math.min(1, cell.dataBar.startFraction ?? 0));
|
|
2119
|
+
const barWidth = cell.widthPt * Math.max(0, Math.min(1, cell.dataBar.fraction));
|
|
2120
|
+
if (barWidth > 0) out.push({
|
|
2121
|
+
type: "fill",
|
|
2122
|
+
x: pt(cellX + cell.widthPt * start),
|
|
2123
|
+
y: pt(pageHeight - rowBottom - row.heightPt),
|
|
2124
|
+
width: pt(barWidth),
|
|
2125
|
+
height: pt(row.heightPt),
|
|
2126
|
+
fillColorHex: cell.dataBar.colorHex
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
if (cell.icon && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
|
|
2130
|
+
const iconSize = Math.min(CF_ICON_SIZE_PT, Math.max(0, row.heightPt - 1));
|
|
2131
|
+
if (iconSize > 0) {
|
|
2132
|
+
const iconX = cellX + (CF_ICON_GUTTER_PT - iconSize) / 2;
|
|
2133
|
+
const iconBottomYUp = rowBottom + (row.heightPt - iconSize) / 2;
|
|
2134
|
+
const { paths, fillColorHex } = buildCellIconShape(cell.icon, iconSize);
|
|
2135
|
+
out.push({
|
|
2136
|
+
type: "shape",
|
|
2137
|
+
shape: {
|
|
2138
|
+
paths,
|
|
2139
|
+
fillColorHex,
|
|
2140
|
+
transform: flipTransform([
|
|
2141
|
+
1,
|
|
2142
|
+
0,
|
|
2143
|
+
0,
|
|
2144
|
+
1,
|
|
2145
|
+
iconX,
|
|
2146
|
+
iconBottomYUp
|
|
2147
|
+
], pageHeight)
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
if (cell.sparkline && cell.sparkline.values.length > 0 && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
|
|
2153
|
+
const inset = 1.5;
|
|
2154
|
+
const sw = cell.widthPt - 2 * inset;
|
|
2155
|
+
const sh = row.heightPt - 2 * inset;
|
|
2156
|
+
if (sw > 0 && sh > 0) {
|
|
2157
|
+
const prims = buildSparkline(cell.sparkline.kind, cell.sparkline.values, sw, sh, cell.sparkline.colorHex);
|
|
2158
|
+
const transform = flipTransform([
|
|
2159
|
+
1,
|
|
2160
|
+
0,
|
|
2161
|
+
0,
|
|
2162
|
+
1,
|
|
2163
|
+
cellX + inset,
|
|
2164
|
+
rowBottom + inset
|
|
2165
|
+
], pageHeight);
|
|
2166
|
+
for (const prim of prims) out.push({
|
|
2167
|
+
type: "shape",
|
|
2168
|
+
shape: {
|
|
2169
|
+
paths: prim.paths,
|
|
2170
|
+
...prim.fillColorHex ? { fillColorHex: prim.fillColorHex } : {},
|
|
2171
|
+
...prim.stroke ? { stroke: prim.stroke } : {},
|
|
2172
|
+
transform
|
|
2173
|
+
}
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2087
2177
|
emitCellBorders(out, cell, cellX, rowBottom, row.heightPt, pageHeight, row.rowIdx, row.rowCount, colCount);
|
|
2088
2178
|
if (cell.mergeRole === "middle" || cell.mergeRole === "end") continue;
|
|
2089
2179
|
let textY = rowTop - cell.padTopPt;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reamkit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Ream — DOCX/XLSX to PDF, SVG, HTML and back to DOCX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Ream — DOCX/XLSX to PDF, SVG, HTML and back to DOCX/XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alex Krassavin <info@reamkit.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -67,7 +67,8 @@
|
|
|
67
67
|
"corpus:fetch": "tsx scripts/corpus/fetch-corpus.ts",
|
|
68
68
|
"corpus:sandbox:build": "docker build -t docgen-losandbox:latest scripts/corpus/sandbox/",
|
|
69
69
|
"corpus": "tsx scripts/corpus/run.ts",
|
|
70
|
-
"corpus:roundtrip": "tsx scripts/corpus/roundtrip.ts"
|
|
70
|
+
"corpus:roundtrip": "tsx scripts/corpus/roundtrip.ts",
|
|
71
|
+
"corpus:roundtrip:xlsx": "tsx scripts/corpus/xlsx-roundtrip.ts"
|
|
71
72
|
},
|
|
72
73
|
"dependencies": {
|
|
73
74
|
"fast-xml-parser": "5.7.0",
|