SheetJS (js-xlsx) в браузере: читать книгу из BLOB-объекта

Я врезаюсь в стену, пытаясь использовать SheetJS для создания объекта Workbook из Blob, в отличие от события клиента, такого как перетаскивание или событие элемента ввода файла. Последние два описаны в примерах документации, но мне не хватает JS-фу, необходимого для обратного перевода из Blob что у меня есть, через FileReader события и в XLSX.read() API.

Учитывая Blob в браузере, как это можно сделать, или это возможно?

2 ответа

Ты можешь сделать

var wb = XLSX.read(await blob.arrayBuffer(), { type: "array" });

Я сохраняю в XLSX с помощью этого кода:

      import XLSX from 'xlsx'
import { saveAs } from 'file-saver'

export function saveAsXlsx({ fileName = 'export', worksheets }: Params) {
  const blob = getXlsxBlob(worksheets)
  saveAs(blob, `${fileName}.xlsx`)
}

export function getXlsxBlob(worksheets: Worksheet[]) {
  const workbook = XLSX.utils.book_new()

  worksheets.forEach(({name, header, body}) => {
    const worksheet = XLSX.utils.json_to_sheet(body, {header})
    XLSX.utils.book_append_sheet(workbook, worksheet, name)
  })

  const workbookOutput = XLSX.write(workbook, {
    type: 'binary',
    bookType: 'xlsx'
  })
  return new Blob([stringToArrayBuffer(workbookOutput)], {
    type: 'application/octet-stream'
  })
}

interface Params {
  fileName?: string
  worksheets: Worksheet[]
}

export interface Worksheet<T extends string = string> {
  name: string
  header: T[]
  body: Record<T, string | number>[]
}

function stringToArrayBuffer(string: string) {
  const buffer = new ArrayBuffer(string.length)
  const view = new Uint8Array(buffer)
  for (let i = 0; i < string.length; i++) view[i] = string.charCodeAt(i) & 0xff
  return buffer
}

Затем я пытаюсь проверить этот код. Я решил не возиться с реальными файлами, поэтому большую часть сложности я извлек в getXlsxBlobи сосредоточился на тестировании этой части.

Вот код, который я пытался протестировать:

      import xlsx from 'xlsx'

import { getXlsxBlob, Worksheet } from './saveAsXlsx'

const people: Worksheet = {
  name: 'People',
  header: ['Name', 'Age'],
  body: [
    {
      Name: 'John Doe',
      Age: 37
    },
    { Name: 'Jane Doe', Age: 35 }
  ]
}

const cars: Worksheet = {
  name: 'Cars',
  header: ['Brand', 'Model', 'Color'],
  body: [
    { Brand: 'Toyota', Model: 'RAV4', Color: 'red' },
    { Brand: 'SAAB', Model: '9-3', Color: 'blue' }
  ]
}

test('getXlsxBlob', async () => {
  const blob = getXlsxBlob([people, cars])
  
  const arrayBuffer = await blob.arrayBuffer()
  const workbook = xlsx.read(arrayBuffer, {type: 'array'})
  expect(workbook.SheetNames).toEqual(['People', 'Cars'])
  expect(xlsx.utils.sheet_to_json(workbook.Sheets['People']))
    .toEqual(people.body)
  expect(xlsx.utils.sheet_to_json(workbook.Sheets['Cars'])).toEqual(cars.body)
})

Он взрывается с

          TypeError: blob.arrayBuffer is not a function

      26 | test('getXlsxBlob', async () => {
      27 |   const blob = getXlsxBlob([people, cars])
    > 28 |   const arrayBuffer = await blob.arrayBuffer()
         |                                  ^

(было бы любопытно разобраться почему, если знаете - поделитесь в комментариях)

Но прибегая к FileReaderпомогает:

      test('getXlsxBlob', async () => {
  // ...
  const arrayBuffer = await readAsArrayBuffer(blob)
  // instead of
  // const arrayBuffer = await blob.arrayBuffer()
  // ...
})

function readAsArrayBuffer(blob: Blob) {
  return new Promise((resolve) => {
    const reader = new FileReader()
    reader.onload = (event) => {
      resolve(event.target?.result)
    }
    reader.readAsArrayBuffer(blob)
  })
}
Другие вопросы по тегам