xlsxrb 0.1.2 → 0.1.4

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/Rakefile CHANGED
@@ -192,11 +192,13 @@ task :wasm do
192
192
  require "open-uri"
193
193
  wasm_url = "https://cdn.jsdelivr.net/npm/@ruby/4.0-wasm-wasi@2.9.3-2.9.4/dist/ruby.wasm"
194
194
  FileUtils.mkdir_p(File.dirname(original_wasm_cache))
195
+ # rubocop:disable Security/Open
195
196
  URI.open(wasm_url) do |stream|
196
197
  File.open(original_wasm_cache, "wb") do |file|
197
198
  IO.copy_stream(stream, file)
198
199
  end
199
200
  end
201
+ # rubocop:enable Security/Open
200
202
  puts "Original ruby.wasm cached successfully."
201
203
  end
202
204
 
@@ -231,9 +233,7 @@ task :wasm do
231
233
 
232
234
  # Resolve and copy host's rexml files
233
235
  rexml_spec_path = $LOAD_PATH.find { |p| File.exist?(File.join(p, "rexml/rexml.rb")) }
234
- if rexml_spec_path
235
- FileUtils.cp_r(File.join(rexml_spec_path, "rexml"), bundle_assets_dir)
236
- end
236
+ FileUtils.cp_r(File.join(rexml_spec_path, "rexml"), bundle_assets_dir) if rexml_spec_path
237
237
 
238
238
  # Gateway for strscan
239
239
  File.write(File.join(bundle_assets_dir, "strscan.rb"), <<~RUBY)
@@ -259,11 +259,11 @@ task :wasm do
259
259
  ]
260
260
  stdlib_files.each do |name|
261
261
  path = $LOAD_PATH.find { |p| File.exist?(File.join(p, name)) }
262
- if path
263
- dest_path = File.join(bundle_assets_dir, name)
264
- FileUtils.mkdir_p(File.dirname(dest_path))
265
- FileUtils.cp(File.join(path, name), dest_path)
266
- end
262
+ next unless path
263
+
264
+ dest_path = File.join(bundle_assets_dir, name)
265
+ FileUtils.mkdir_p(File.dirname(dest_path))
266
+ FileUtils.cp(File.join(path, name), dest_path)
267
267
  end
268
268
 
269
269
  # C. Patch tmpdir.rb to automatically create /tmp in Wasm virtual filesystem (since Wasm has no writable /tmp by default)
@@ -286,20 +286,136 @@ task :wasm do
286
286
  puts "Building packed ruby.wasm from staging bundle..."
287
287
  cmd = "bundle exec rbwasm pack #{original_wasm_cache} --dir #{wasm_bundle_dir}::/usr/local/lib/ruby/site_ruby -o #{packed_wasm_path}"
288
288
  puts "Executing: #{cmd}"
289
- unless system(cmd)
290
- raise "Failed to build packed ruby.wasm using rbwasm pack!"
289
+ raise "Failed to build packed ruby.wasm using rbwasm pack!" unless system(cmd)
290
+ end
291
+
292
+ def download_file(url, dest)
293
+ # Check if the file exists and is not a tiny placeholder/error document
294
+ return if File.exist?(dest) && File.size(dest) > 1024
295
+
296
+ puts "Downloading #{url} to #{dest}..."
297
+ FileUtils.mkdir_p(File.dirname(dest))
298
+
299
+ require "net/http"
300
+ uri = URI.parse(url)
301
+ temp_dest = "#{dest}.tmp"
302
+
303
+ begin
304
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 15, read_timeout: 90) do |http|
305
+ request = Net::HTTP::Get.new(uri)
306
+ # Specify User-Agent to bypass scraping prevention on CDNs and act as a normal browser
307
+ request["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
308
+ # Force raw (identity) encoding to prevent receiving Brotli-compressed (.br) data,
309
+ # which Ruby's Net::HTTP cannot decode automatically, leading to corrupted Wasm files.
310
+ request["Accept-Encoding"] = "identity"
311
+
312
+ http.request(request) do |response|
313
+ raise "HTTP error #{response.code}: #{response.message}" if response.code.to_i != 200
314
+
315
+ File.open(temp_dest, "wb") do |output|
316
+ response.read_body do |chunk|
317
+ output.write(chunk)
318
+ end
319
+ end
320
+ end
321
+ end
322
+ # Atomic rename to prevent leaving incomplete files on failure
323
+ File.rename(temp_dest, dest)
324
+ puts "Downloaded successfully."
325
+
326
+ # Dynamic Brotli Decompression if the server ignored identity encoding and sent Brotli (.br) data
327
+ if File.exist?(dest) && File.binread(dest, 4)&.bytes == [0xCF, 0xFF, 0xFF, 0x7F]
328
+ puts "Detected Brotli compression on #{dest}. Decompressing..."
329
+
330
+ unpacked = "#{dest}.unpacked"
331
+ if system("brotli -d -f -o #{unpacked} #{dest}")
332
+ File.rename(unpacked, dest)
333
+ puts "Decompressed #{dest} successfully."
334
+ else
335
+ FileUtils.rm_f(unpacked)
336
+ raise "Failed to decompress Brotli file: #{dest}"
337
+ end
338
+ end
339
+ rescue StandardError => e
340
+ puts "Failed to download #{url}: #{e.message}"
341
+ FileUtils.rm_f(temp_dest)
342
+ FileUtils.rm_f(dest)
343
+ raise "Required asset download failed. Build aborted."
344
+ end
345
+ end
346
+
347
+ def fetch_google_fonts
348
+ css_dest = "docs/fonts/fonts.css"
349
+ return if File.exist?(css_dest)
350
+
351
+ puts "Fetching and localizing Google Fonts..."
352
+ font_url = "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
353
+
354
+ css_content = nil
355
+ begin
356
+ # Specify Chrome User-Agent to ensure Google Fonts returns modern and lightweight .woff2 formats
357
+ # instead of legacy formats (like .ttf or .eot) designed for older browsers
358
+ # rubocop:disable Security/Open
359
+ URI.open(font_url, "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") do |f|
360
+ css_content = f.read
361
+ end
362
+ # rubocop:enable Security/Open
363
+ rescue StandardError => e
364
+ puts "Failed to fetch Google Fonts CSS: #{e.message}"
365
+ return
291
366
  end
367
+
368
+ urls = css_content.scan(%r{url\((https://fonts\.gstatic\.com/[^)]+)\)}).flatten
369
+
370
+ urls.uniq.each do |url|
371
+ filename = url.split("/").last
372
+ local_path = "docs/fonts/#{filename}"
373
+ download_file(url, local_path)
374
+ css_content.gsub!(url, filename)
375
+ end
376
+
377
+ FileUtils.mkdir_p("docs/fonts")
378
+ File.write(css_dest, css_content)
379
+ puts "Google Fonts localized successfully."
380
+ end
381
+
382
+ desc "Fetch required external assets for offline usage"
383
+ task :fetch_assets do
384
+ require "open-uri"
385
+ require "fileutils"
386
+
387
+ # Fetch Ruby WASI JS
388
+ download_file(
389
+ "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.9.3-2.9.4/dist/browser.umd.js",
390
+ "docs/wasm/browser.umd.js"
391
+ )
392
+
393
+ # Fetch ZetaOffice Wasm
394
+ base_zeta_url = "https://cdn.zetaoffice.net/zetaoffice_latest/"
395
+ %w[soffice.js soffice.wasm soffice.data soffice.data.js.metadata qtloader.js].each do |file|
396
+ download_file(base_zeta_url + file, "docs/zetaoffice/#{file}")
397
+ end
398
+
399
+ # Fetch and localize Google Fonts
400
+ fetch_google_fonts
292
401
  end
293
402
 
294
403
  desc "Generate RDoc documentation including Visual Gallery"
295
- task doc: :wasm do
404
+ task doc: %i[wasm fetch_assets] do
296
405
  FileUtils.rm_rf("doc")
297
- sh "bundle exec rdoc --title 'xlsxrb Documentation' --main README.md README.md \"docs/visual/VisualGallery.md\" lib/"
406
+
407
+ # RDoc コマンドを実行 (--exclude を指定して docs 配下のプレビュー用アセットの誤パースを回避)
408
+ sh "bundle exec rdoc --op doc " \
409
+ "--exclude 'docs/coi-serviceworker\\.js' " \
410
+ "--exclude 'docs/zeta\\.js' " \
411
+ "--exclude 'docs/office_thread\\.js' " \
412
+ "--exclude 'docs/preview\\.html' " \
413
+ "--title 'xlsxrb Documentation' --main README.md README.md \"docs/visual/VisualGallery.md\" lib/"
298
414
 
299
415
  # Copy visual gallery images and files so they are available in RDoc output
300
416
  FileUtils.mkdir_p("doc/test/visual/baselines")
301
- FileUtils.cp_r("test/visual/baselines", "doc/test/visual")
302
417
  FileUtils.mkdir_p("doc/test/visual/support/illustrations")
418
+ FileUtils.cp_r(Dir.glob("test/visual/baselines/*"), "doc/test/visual/baselines")
303
419
  FileUtils.cp_r(Dir.glob("test/visual/support/illustrations/*.png"), "doc/test/visual/support/illustrations")
304
420
 
305
421
  FileUtils.mkdir_p("doc/docs/visual/files")
@@ -312,21 +428,36 @@ task doc: :wasm do
312
428
  FileUtils.mkdir_p("doc/css")
313
429
  FileUtils.mkdir_p("doc/js")
314
430
  FileUtils.mkdir_p("doc/wasm")
431
+ FileUtils.mkdir_p("doc/zetaoffice")
432
+ FileUtils.mkdir_p("doc/fonts")
433
+
315
434
  FileUtils.cp("docs/wasm/wasm_doc_helper.js", "doc/js/wasm_doc_helper.js")
316
435
  FileUtils.cp("docs/wasm/wasm_doc_helper.css", "doc/css/wasm_doc_helper.css")
317
436
  FileUtils.cp("docs/wasm/ruby.wasm", "doc/wasm/ruby.wasm")
437
+ FileUtils.cp("docs/wasm/browser.umd.js", "doc/wasm/browser.umd.js")
438
+ FileUtils.cp_r(Dir.glob("docs/zetaoffice/*"), "doc/zetaoffice")
439
+ FileUtils.cp_r(Dir.glob("docs/fonts/*"), "doc/fonts")
440
+
441
+ # Copy LibreOffice Wasm Preview assets to doc directory
442
+ FileUtils.cp("docs/preview.html", "doc/preview.html")
443
+ FileUtils.cp("docs/coi-serviceworker.js", "doc/coi-serviceworker.js")
444
+ FileUtils.cp("docs/zeta.js", "doc/zeta.js")
445
+ FileUtils.cp("docs/office_thread.js", "doc/office_thread.js")
318
446
 
319
447
  # Inject stylesheet and javascript loading tags to all generated HTML docs
320
448
  Dir.glob("doc/**/*.html").each do |html_path|
449
+ next if File.basename(html_path) == "preview.html"
450
+
321
451
  html_content = File.read(html_path)
322
452
  depth = html_path.sub(%r{\Adoc/}, "").count("/")
323
453
  rel_prefix = "../" * depth
324
454
 
325
- js_tag = %Q{<script src="#{rel_prefix}js/wasm_doc_helper.js" defer></script>}
326
- css_tag = %Q{<link href="#{rel_prefix}css/wasm_doc_helper.css" rel="stylesheet">}
455
+ coi_tag = %(<script src="#{rel_prefix}coi-serviceworker.js"></script>)
456
+ js_tag = %(<script src="#{rel_prefix}js/wasm_doc_helper.js" defer></script>)
457
+ css_tag = %(<link href="#{rel_prefix}css/wasm_doc_helper.css" rel="stylesheet">)
327
458
 
328
459
  if html_content.include?("<body")
329
- modified = html_content.sub("<body", "#{js_tag}\n#{css_tag}\n<body")
460
+ modified = html_content.sub("<body", "#{coi_tag}\n#{js_tag}\n#{css_tag}\n<body")
330
461
  File.write(html_path, modified)
331
462
  end
332
463
  end
@@ -346,7 +477,23 @@ namespace :doc do
346
477
  Port: port,
347
478
  DocumentRoot: File.expand_path("doc", __dir__),
348
479
  Logger: WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
349
- AccessLog: []
480
+ AccessLog: [],
481
+ # If the file is a Brotli-compressed Wasm/Data asset (checked via magic bytes),
482
+ # dynamically inject 'Content-Encoding: br' header so the browser decompresses it natively.
483
+ RequestCallback: lambda { |req, res|
484
+ if req.path.end_with?(".wasm") || req.path.end_with?(".data")
485
+ # Resolve physical file path from req.path manually since res.filename is nil at this stage
486
+ local_path = File.join(File.expand_path("doc", __dir__), req.path)
487
+ if File.exist?(local_path)
488
+ first_bytes = begin
489
+ File.binread(local_path, 4)
490
+ rescue StandardError
491
+ nil
492
+ end
493
+ res["Content-Encoding"] = "br" if first_bytes != "\x00asm"
494
+ end
495
+ end
496
+ }
350
497
  )
351
498
 
352
499
  puts "=================================================="
data/benchmark.rb CHANGED
@@ -192,6 +192,14 @@ 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
+
198
+ sorted = array.sort
199
+ len = sorted.length
200
+ (sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
201
+ end
202
+
195
203
  def run_benchmark(name, snippet)
196
204
  print format("%-25s", name)
197
205
  results = ITERATIONS.times.map do
@@ -200,23 +208,25 @@ def run_benchmark(name, snippet)
200
208
  end
201
209
  puts
202
210
 
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
211
+ valid_results = results.compact
212
+
213
+ median_time = median(valid_results.map { |r| r[:time] }.compact)
214
+ median_cpu = median(valid_results.map { |r| r[:cpu] }.compact)
215
+ median_mem = median(valid_results.map { |r| r[:memory] }.compact)
216
+ median_gc = median(valid_results.map { |r| r[:gc_count] }.compact)
217
+ median_alloc = median(valid_results.map { |r| r[:alloc_objects] }.compact)
218
+ median_wchar = median(valid_results.map { |r| r[:wchar] }.compact)
219
+ median_rchar = median(valid_results.map { |r| r[:rchar] }.compact)
210
220
 
211
221
  {
212
222
  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
223
+ time: median_time,
224
+ cpu: median_cpu,
225
+ memory: median_mem,
226
+ gc_count: median_gc,
227
+ alloc_m: median_alloc / 1_000_000.0,
228
+ wchar_mb: median_wchar / 1_048_576.0,
229
+ rchar_mb: median_rchar / 1_048_576.0
220
230
  }
221
231
  end
222
232
 
@@ -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: */