@trebco/treb 28.0.5 → 28.2.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.
@@ -19,8 +19,12 @@
19
19
  *
20
20
  */
21
21
 
22
- import type JSZip from 'jszip';
22
+ import UZip from 'uzip';
23
+
24
+ // import type JSZip from 'jszip';
23
25
  // import * as xmlparser from 'fast-xml-parser';
26
+
27
+
24
28
  import { XMLParser } from 'fast-xml-parser';
25
29
  import { XMLUtils, XMLOptions, XMLOptions2 } from './xml-utils';
26
30
 
@@ -39,6 +43,7 @@ import { StyleCache } from './workbook-style2';
39
43
  import { Theme } from './workbook-theme2';
40
44
  import { Sheet, VisibleState } from './workbook-sheet2';
41
45
  import type { RelationshipMap } from './relationship';
46
+ import { ZipWrapper } from './zip-wrapper';
42
47
 
43
48
 
44
49
  /*
@@ -79,11 +84,21 @@ export interface ChartDescription {
79
84
  series?: ChartSeries[];
80
85
  }
81
86
 
87
+ export interface AnchoredImageDescription {
88
+ type: 'image';
89
+ image?: Uint8Array;
90
+ filename?: string;
91
+ anchor: TwoCellAnchor,
92
+ }
93
+
82
94
  export interface AnchoredChartDescription {
95
+ type: 'chart';
83
96
  chart?: ChartDescription,
84
97
  anchor: TwoCellAnchor,
85
98
  }
86
99
 
100
+ export type AnchoredDrawingPart = AnchoredChartDescription|AnchoredImageDescription;
101
+
87
102
  export interface TableFooterType {
88
103
  type: 'label'|'formula';
89
104
  value: string;
@@ -138,17 +153,17 @@ export class Workbook {
138
153
  return this.sheets.length;
139
154
  }
140
155
 
141
- constructor(public zip: JSZip) {
156
+ constructor(public zip: ZipWrapper) {
142
157
 
143
158
  }
144
159
 
145
160
  /**
146
161
  * given a path in the zip file, read and parse the rels file
147
162
  */
148
- public async ReadRels(path: string): Promise<RelationshipMap> {
163
+ public ReadRels(path: string): RelationshipMap {
149
164
 
150
165
  const rels: RelationshipMap = {};
151
- const data = await this.zip.file(path)?.async('text') as string;
166
+ const data = this.zip.Has(path) ? this.zip.Get(path) : '';
152
167
 
153
168
  //
154
169
  // force array on <Relationship/> elements, but be slack on the rest
@@ -169,30 +184,30 @@ export class Workbook {
169
184
 
170
185
  }
171
186
 
172
- public async Init(): Promise<void> {
187
+ public Init() {
173
188
 
174
189
  // read workbook rels
175
- this.rels = await this.ReadRels( 'xl/_rels/workbook.xml.rels');
176
-
190
+ this.rels = this.ReadRels( 'xl/_rels/workbook.xml.rels');
191
+
177
192
  // shared strings
178
- let data = await this.zip.file('xl/sharedStrings.xml')?.async('text') as string;
193
+ let data = this.zip.Has('xl/sharedStrings.xml') ? this.zip.Get('xl/sharedStrings.xml') : '';
179
194
  let xml = xmlparser2.parse(data || '');
180
195
  this.shared_strings.FromXML(xml);
181
196
 
182
197
  // theme
183
- data = await this.zip.file('xl/theme/theme1.xml')?.async('text') as string;
198
+ data = this.zip.Get('xl/theme/theme1.xml');
184
199
  xml = xmlparser2.parse(data);
185
200
  this.theme.FromXML(xml);
186
201
 
187
202
  // styles
188
- data = await this.zip.file('xl/styles.xml')?.async('text') as string;
203
+ data = this.zip.Get('xl/styles.xml');
189
204
  xml = xmlparser2.parse(data);
190
205
  this.style_cache.FromXML(xml, this.theme);
191
206
 
192
207
  // console.info({c: this.style_cache});
193
208
 
194
209
  // read workbook
195
- data = await this.zip.file('xl/workbook.xml')?.async('text') as string;
210
+ data = this.zip.Get('xl/workbook.xml');
196
211
  xml = xmlparser2.parse(data);
197
212
 
198
213
  // defined names
@@ -240,9 +255,9 @@ export class Workbook {
240
255
  worksheet.path = `xl/${this.rels[rid].target}`;
241
256
  worksheet.rels_path = worksheet.path.replace('worksheets', 'worksheets/_rels') + '.rels';
242
257
 
243
- data = await this.zip.file(worksheet.path)?.async('text') as string;
258
+ data = this.zip.Get(worksheet.path);
244
259
  worksheet.sheet_data = xmlparser2.parse(data || '');
245
- worksheet.rels = await this.ReadRels(worksheet.rels_path);
260
+ worksheet.rels = this.ReadRels(worksheet.rels_path);
246
261
 
247
262
  worksheet.Parse();
248
263
  // console.info("TS", worksheet);
@@ -256,9 +271,9 @@ export class Workbook {
256
271
 
257
272
  }
258
273
 
259
- public async ReadTable(reference: string): Promise<TableDescription|undefined> {
274
+ public ReadTable(reference: string): TableDescription|undefined {
260
275
 
261
- const data = await this.zip.file(reference.replace(/^../, 'xl'))?.async('text') as string;
276
+ const data = this.zip.Get(reference.replace(/^../, 'xl'));
262
277
 
263
278
  if (!data) {
264
279
  return undefined;
@@ -276,19 +291,21 @@ export class Workbook {
276
291
  };
277
292
 
278
293
  return table;
294
+
279
295
  }
280
296
 
281
- public async ReadDrawing(reference: string): Promise<AnchoredChartDescription[] | undefined> {
297
+ public ReadDrawing(reference: string): AnchoredDrawingPart[] | undefined {
282
298
 
283
- const data = await this.zip.file(reference.replace(/^../, 'xl'))?.async('text') as string;
299
+ const data = this.zip.Get(reference.replace(/^../, 'xl'));
300
+
284
301
  if (!data) {
285
302
  return undefined;
286
303
  }
287
304
  const xml = xmlparser2.parse(data);
288
305
 
289
- const drawing_rels = await this.ReadRels(reference.replace(/^..\/drawings/, 'xl/drawings/_rels') + '.rels');
306
+ const drawing_rels = this.ReadRels(reference.replace(/^..\/drawings/, 'xl/drawings/_rels') + '.rels');
290
307
 
291
- const results: AnchoredChartDescription[] = [];
308
+ const results: AnchoredDrawingPart[] = [];
292
309
  const anchor_nodes = XMLUtils.FindAll(xml, 'xdr:wsDr/xdr:twoCellAnchor');
293
310
 
294
311
  /* FIXME: move to drawing? */
@@ -308,18 +325,46 @@ export class Workbook {
308
325
  from: ParseAnchor(anchor_node['xdr:from']),
309
326
  to: ParseAnchor(anchor_node['xdr:to']),
310
327
  };
311
- const result: AnchoredChartDescription = { anchor };
312
328
 
313
329
  const chart_reference = XMLUtils.FindAll(anchor_node, `xdr:graphicFrame/a:graphic/a:graphicData/c:chart`)[0];
314
330
 
315
331
  if (chart_reference && chart_reference.a$ && chart_reference.a$['r:id']) {
332
+ const result: AnchoredChartDescription = { type: 'chart', anchor };
316
333
  const chart_rel = drawing_rels[chart_reference.a$['r:id']];
317
334
  if (chart_rel && chart_rel.target) {
318
- result.chart = await this.ReadChart(chart_rel.target);
335
+ result.chart = this.ReadChart(chart_rel.target);
319
336
  }
337
+ results.push(result);
320
338
  }
339
+ else {
321
340
 
322
- results.push(result);
341
+ const media_reference = XMLUtils.FindAll(anchor_node, `xdr:pic/xdr:blipFill/a:blip`)[0];
342
+ if (media_reference && media_reference.a$['r:embed']) {
343
+ const media_rel = drawing_rels[media_reference.a$['r:embed']];
344
+
345
+ // const chart_rel = drawing_rels[chart_reference.a$['r:id']];
346
+ // console.info("Maybe an image?", media_reference, media_rel)
347
+
348
+ if (media_rel && media_rel.target) {
349
+ if (/(?:jpg|jpeg|png|gif)$/i.test(media_rel.target)) {
350
+
351
+ // const result: AnchoredImageDescription = { type: 'image' };
352
+ const path = media_rel.target.replace(/^\.\./, 'xl');
353
+ const filename = path.replace(/^.*\//, '');
354
+
355
+ const result: AnchoredImageDescription = {
356
+ type: 'image', anchor, image: this.zip.GetBinary(path), filename
357
+ }
358
+
359
+ results.push(result);
360
+
361
+ }
362
+ }
363
+
364
+ }
365
+
366
+
367
+ }
323
368
 
324
369
  }
325
370
 
@@ -332,9 +377,9 @@ export class Workbook {
332
377
  * FIXME: this is using the old options with old structure, just have
333
378
  * not updated it yet
334
379
  */
335
- public async ReadChart(reference: string): Promise<ChartDescription|undefined> {
380
+ public ReadChart(reference: string): ChartDescription|undefined {
336
381
 
337
- const data = await this.zip.file(reference.replace(/^../, 'xl'))?.async('text') as string;
382
+ const data = this.zip.Get(reference.replace(/^../, 'xl'));
338
383
  if (!data) { return undefined; }
339
384
 
340
385
  const xml = xmlparser1.parse(data);
@@ -0,0 +1,96 @@
1
+
2
+ import UZip from 'uzip';
3
+ import Base64JS from 'base64-js';
4
+
5
+ export class ZipWrapper {
6
+
7
+ public records: UZip.UZIPFiles;
8
+ public text: Map<string, string> = new Map();
9
+
10
+ public constructor(buffer: ArrayBuffer) {
11
+ this.records = UZip.parse(buffer);
12
+ }
13
+
14
+ /**
15
+ * check if entry exists
16
+ */
17
+ public Has(path: string) {
18
+ return this.text.has(path) || !!this.records?.[path];
19
+ }
20
+
21
+ /**
22
+ * nondestructive
23
+ */
24
+ public ArrayBuffer() {
25
+
26
+ const records: Record<string, Uint8Array> = {};
27
+ if (this.records) {
28
+ for (const [key, value] of Object.entries(this.records)) {
29
+ records[key] = new Uint8Array(value);
30
+ }
31
+ }
32
+
33
+ const encoder = new TextEncoder();
34
+ for (const [key, value] of this.text.entries()) {
35
+ records[key] = encoder.encode(value);
36
+ }
37
+
38
+ return UZip.encode(records);
39
+
40
+ }
41
+
42
+ /**
43
+ * set a binary file. set this directly in the records table,
44
+ * instead of the text table.
45
+ */
46
+ public SetBinary(path: string, data: string, encoding?: 'base64'|'binary') {
47
+
48
+ if (encoding === 'base64') {
49
+ const bytes = Base64JS.toByteArray(data);
50
+ this.records[path] = bytes;
51
+ }
52
+ else {
53
+ throw new Error('unsupported encoding: ' + encoding);
54
+ }
55
+
56
+ }
57
+
58
+ public Set(path: string, text: string) {
59
+
60
+ this.text.set(path, text);
61
+ if (!!this.records[path]) {
62
+ delete this.records[path];
63
+ }
64
+ }
65
+
66
+ public GetBinary(path: string) {
67
+ const data = this.records[path];
68
+ if (data) {
69
+ return new Uint8Array(data);
70
+ }
71
+ throw new Error('path not in records: ' + path);
72
+ }
73
+
74
+ public Get(path: string) {
75
+
76
+ let text = this.text.get(path);
77
+
78
+ if (text) {
79
+ return text;
80
+ }
81
+
82
+ const data = this.records[path];
83
+ if (data) {
84
+ text = new TextDecoder().decode(data);
85
+ this.text.set(path, text);
86
+ delete this.records[path];
87
+ return text;
88
+ }
89
+
90
+ console.info(this);
91
+
92
+ throw new Error('path not in zip file: ' + path);
93
+
94
+ }
95
+
96
+ }
@@ -91,9 +91,9 @@ export interface ImageAnnotationData {
91
91
  * @privateRemarks
92
92
  * why is this a string?
93
93
  */
94
- scale: string;
94
+ scale?: string;
95
95
 
96
- original_size: ImageSize;
96
+ original_size?: ImageSize;
97
97
 
98
98
  }
99
99