xlsxrb 0.1.2 → 0.1.3

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.
data/benchmark.rb CHANGED
@@ -192,6 +192,13 @@ def run_in_subprocess(_name, &block)
192
192
  end
193
193
  end
194
194
 
195
+ def median(array)
196
+ return 0.0 if array.empty?
197
+ sorted = array.sort
198
+ len = sorted.length
199
+ (sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
200
+ end
201
+
195
202
  def run_benchmark(name, snippet)
196
203
  print format("%-25s", name)
197
204
  results = ITERATIONS.times.map do
@@ -200,23 +207,25 @@ def run_benchmark(name, snippet)
200
207
  end
201
208
  puts
202
209
 
203
- avg_time = results.compact.map { |r| r[:time] }.compact.sum / results.compact.size.to_f
204
- avg_cpu = results.compact.map { |r| r[:cpu] }.compact.sum / results.compact.size.to_f
205
- avg_mem = results.compact.map { |r| r[:memory] }.compact.sum / results.compact.size.to_f
206
- avg_gc = results.compact.map { |r| r[:gc_count] }.compact.sum / results.compact.size.to_f
207
- avg_alloc = results.compact.map { |r| r[:alloc_objects] }.compact.sum / results.compact.size.to_f
208
- avg_wchar = results.compact.map { |r| r[:wchar] }.compact.sum / results.compact.size.to_f
209
- avg_rchar = results.compact.map { |r| r[:rchar] }.compact.sum / results.compact.size.to_f
210
+ valid_results = results.compact
211
+
212
+ median_time = median(valid_results.map { |r| r[:time] }.compact)
213
+ median_cpu = median(valid_results.map { |r| r[:cpu] }.compact)
214
+ median_mem = median(valid_results.map { |r| r[:memory] }.compact)
215
+ median_gc = median(valid_results.map { |r| r[:gc_count] }.compact)
216
+ median_alloc = median(valid_results.map { |r| r[:alloc_objects] }.compact)
217
+ median_wchar = median(valid_results.map { |r| r[:wchar] }.compact)
218
+ median_rchar = median(valid_results.map { |r| r[:rchar] }.compact)
210
219
 
211
220
  {
212
221
  name: name,
213
- time: avg_time,
214
- cpu: avg_cpu,
215
- memory: avg_mem,
216
- gc_count: avg_gc,
217
- alloc_m: avg_alloc / 1_000_000.0,
218
- wchar_mb: avg_wchar / 1_048_576.0,
219
- rchar_mb: avg_rchar / 1_048_576.0
222
+ time: median_time,
223
+ cpu: median_cpu,
224
+ memory: median_mem,
225
+ gc_count: median_gc,
226
+ alloc_m: median_alloc / 1_000_000.0,
227
+ wchar_mb: median_wchar / 1_048_576.0,
228
+ rchar_mb: median_rchar / 1_048_576.0
220
229
  }
221
230
  end
222
231
 
@@ -0,0 +1,67 @@
1
+ # Development & Contribution Guide
2
+
3
+ This document outlines the internal development workflow, test commands, and E2E testing policies for contributors working on `xlsxrb`.
4
+
5
+ ## Test Commands
6
+
7
+ To run the different tiers of our testing strategy:
8
+
9
+ 1. **Unit Tests:**
10
+ ```bash
11
+ bundle exec rake test:unit
12
+ ```
13
+ 2. **Contract Tests:**
14
+ ```bash
15
+ bundle exec rake test:contract
16
+ ```
17
+ 3. **Interoperability (E2E) Tests:**
18
+ Requires .NET SDK to be installed (pre-configured in Dev Container).
19
+ ```bash
20
+ bundle exec rake test:e2e
21
+ ```
22
+ 4. **Visual Regression Tests (VRT):**
23
+ Requires LibreOffice, ImageMagick, and `poppler-utils`.
24
+ ```bash
25
+ bundle exec rake test:visual
26
+ ```
27
+ 5. **Run All Tests:**
28
+ ```bash
29
+ bundle exec rake test
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Development Workflow
35
+
36
+ High-level API expansion follows the Facade rules documented in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). In short: if a low-level writer feature is stable, the default expectation is that it should eventually be exposed through the high-level DSL as well, with consistent naming, both streaming and in-memory coverage, backward-compatible options/block forms where practical, and matching Facade-level tests.
37
+
38
+ To ensure systematic progress, reliable round-trip compatibility, and strict adherence to the ECMA-376 specification, we follow this iterative development cycle for each new feature:
39
+
40
+ 1. **Select a Feature:** Choose a specific element or behavior from the specification to implement.
41
+ 2. **Writer Unit Tests:** Write unit tests for the Writer component targeting this feature.
42
+ 3. **Writer Implementation:** Implement the Writer functionality.
43
+ 4. **Run Writer Tests:** Execute the Writer unit tests. If they fail, return to step 3.
44
+ 5. **Writer E2E & Validation:** Test the Writer's generated XLSX file using the Open XML SDK. This includes structural validation using `OpenXmlValidator`. If the test or validation fails, return to step 2.
45
+ 6. **Reader Unit Tests:** Write unit tests for the Reader component. Crucially, include round-trip tests to ensure the Reader can accurately parse the output of your Writer.
46
+ 7. **Reader Implementation:** Implement the Reader functionality.
47
+ 8. **Run Reader Tests:** Execute the Reader unit tests. If they fail, return to step 6 or 7. If the round-trip test reveals a structural flaw in the Writer's output, return all the way back to step 2.
48
+ 9. **Reader E2E:** Verify that the Reader can successfully parse a valid XLSX file generated by the Open XML SDK that includes the new feature. If it fails, return to step 6 or 7.
49
+ 10. **Full Test Suite:** Run the entire test suite (`rake test`). If any tests fail, trace back to the appropriate step.
50
+ 11. **Commit:** Commit the changes. The commit message must clearly describe the specific feature implemented in this cycle.
51
+ 12. **Next Feature:** Proceed to the next feature and return to step 1.
52
+
53
+ ---
54
+
55
+ ## E2E Policy
56
+
57
+ E2E tests are required for every new feature. Omitting them is the exception, not the rule, and requires explicit justification.
58
+
59
+ A strong signal that E2E should not be omitted: if you are adding a new XML element, a new attribute on a top-level structure, or a new public API parameter, E2E is expected.
60
+
61
+ Omission is only acceptable when **all** of the following hold:
62
+
63
+ 1. The change adds a minor attribute to an XML structure that is **already exercised end-to-end** by an existing E2E scenario for the same element.
64
+ 2. No new XML element or branch is introduced.
65
+ 3. Unit tests and round-trip tests fully cover the new behaviour.
66
+ 4. `rake test` passes with Open XML SDK validation included.
67
+ 5. The commit message explicitly names the existing E2E scenario that provides coverage and states why a new scenario adds no value.
@@ -0,0 +1,82 @@
1
+ /*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
2
+ // Source: https://github.com/gzguidoti/coi-serviceworker
3
+ // Purpose: Allows running WebAssembly with SharedArrayBuffer on browsers by setting COOP and COEP headers via Service Worker.
4
+ let coepCredentialless = false;
5
+ if (typeof window === 'undefined') {
6
+ self.addEventListener("install", () => self.skipWaiting());
7
+ self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
8
+
9
+ self.addEventListener("message", (ev) => {
10
+ if (!ev.data) {
11
+ return;
12
+ } else if (ev.data.type === "deregister") {
13
+ self.registration
14
+ .unregister()
15
+ .then(() => {
16
+ return self.clients.matchAll();
17
+ })
18
+ .then(clients => {
19
+ clients.forEach((client) => client.navigate(client.url));
20
+ });
21
+ } else if (ev.data.type === "coepCredentialless") {
22
+ coepCredentialless = ev.data.value;
23
+ }
24
+ });
25
+
26
+ self.addEventListener("fetch", function (event) {
27
+ const r = event.request;
28
+ if (r.cache === "only-if-cached" && r.mode !== "same-origin") {
29
+ return;
30
+ }
31
+
32
+ const request = (coepCredentialless && r.mode === "no-cors")
33
+ ? new Request(r, { credentials: "omit" })
34
+ : r;
35
+ event.respondWith(
36
+ fetch(request)
37
+ .then((response) => {
38
+ if (response.status === 0) {
39
+ return response;
40
+ }
41
+
42
+ const newHeaders = new Headers(response.headers);
43
+ newHeaders.set("Cross-Origin-Embedder-Policy",
44
+ coepCredentialless ? "credentialless" : "require-corp"
45
+ );
46
+ if (!coepCredentialless) {
47
+ newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin");
48
+ }
49
+ newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");
50
+
51
+ return new Response(response.body, {
52
+ status: response.status,
53
+ statusText: response.statusText,
54
+ headers: newHeaders,
55
+ });
56
+ })
57
+ .catch((e) => console.error(e))
58
+ );
59
+ });
60
+ } else {
61
+ (() => {
62
+ const reloaded = sessionStorage.getItem("coiReloaded");
63
+ const isSecureContext = window.isSecureContext;
64
+ if (!isSecureContext) return;
65
+
66
+ if (navigator.serviceWorker) {
67
+ navigator.serviceWorker.register(window.document.currentScript.src).then(
68
+ (registration) => {
69
+ registration.addEventListener("updatefound", () => {
70
+ sessionStorage.setItem("coiReloaded", "true");
71
+ window.location.reload();
72
+ });
73
+ if (registration.active && !navigator.serviceWorker.controller) {
74
+ sessionStorage.setItem("coiReloaded", "true");
75
+ window.location.reload();
76
+ }
77
+ },
78
+ (err) => console.error("COI registration failed: ", err)
79
+ );
80
+ }
81
+ })();
82
+ }
@@ -0,0 +1,77 @@
1
+ /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: Derived from ZetaOffice WebAssembly example (https://github.com/zetaoffice/zeta-wasm-examples)
4
+ // Purpose: Runs WebAssembly-based LibreOffice (Calc) in a separate Web Worker thread.
5
+
6
+ // Debugging note:
7
+ // Switch the web worker in the browsers debug tab to debug this code.
8
+ // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined.
9
+
10
+ 'use strict';
11
+
12
+
13
+ // global variables - zetajs environment:
14
+ let zetajs, css;
15
+
16
+ // = global variables (some are global for easier debugging) =
17
+ // common variables:
18
+ let context, desktop, xModel, ctrl;
19
+
20
+
21
+ function demo() {
22
+ context = zetajs.getUnoComponentContext();
23
+ const bean_overwrite = new css.beans.PropertyValue({Name: 'Overwrite', Value: true});
24
+ const bean_odt_export = new css.beans.PropertyValue({Name: 'FilterName', Value: 'writer8'});
25
+ desktop = css.frame.Desktop.create(context);
26
+
27
+ zetajs.mainPort.onmessage = function (e) {
28
+ switch (e.data.cmd) {
29
+ case 'upload':
30
+ loadFile(e.data.filename);
31
+ break;
32
+ case 'download':
33
+ xModel.store();
34
+ zetajs.mainPort.postMessage({cmd: 'download', id: e.data.id});
35
+ break;
36
+ default:
37
+ throw Error('Unknown message command: ' + e.data.cmd);
38
+ }
39
+ }
40
+ zetajs.mainPort.postMessage({cmd: 'thr_running'});
41
+ }
42
+
43
+ function loadFile(filename) {
44
+ if (xModel) {
45
+ try {
46
+ const xCloseable = css.util.XCloseable.query(xModel);
47
+ if (xCloseable) {
48
+ xCloseable.close(true);
49
+ } else {
50
+ xModel.dispose();
51
+ }
52
+ } catch (e) {
53
+ console.warn("xCloseable.close failed, trying dispose:", e);
54
+ try {
55
+ xModel.dispose();
56
+ } catch (e2) {
57
+ console.error("Failed to dispose old xModel:", e2);
58
+ }
59
+ }
60
+ xModel = null;
61
+ }
62
+
63
+ const in_path = 'file:///tmp/office/' + filename;
64
+ xModel = desktop.loadComponentFromURL(in_path, '_default', 0, []);
65
+ ctrl = xModel.getCurrentController();
66
+ ctrl.getFrame().getContainerWindow().FullScreen = true;
67
+ zetajs.mainPort.postMessage({cmd: 'ui_ready'});
68
+ }
69
+
70
+ Module.zetajs.then(function(pZetajs) {
71
+ // initializing zetajs environment:
72
+ zetajs = pZetajs;
73
+ css = zetajs.uno.com.sun.star;
74
+ demo(); // launching demo
75
+ });
76
+
77
+ /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */