スキップしてメイン コンテンツに移動

[Angular] Read Spreadsheets with SheetJS

Intro

I wanted to send spreadsheets(.xlsx) to sever.
But before sending, I wanted to validate the files.
So I tried to read them with SheetJS.

GitHub - SheetJS/sheetjs: SheetJS Community Edition -- Spreadsheet Data Toolkit
xlsx · docs

Spreadsheet sample



Install & read data

Install

npm install --save xlsx

Open workbook and worksheet

For opening workbook, I used FileReader by Reading XLSX from FileReader.readAsArrayBuffer() · SheetJS/sheetjs Wiki · GitHub.

sheet-loader.service.ts


import { Injectable } from '@angular/core';
import * as xlsx from 'xlsx';
@Injectable({
  providedIn: 'root'
})
export class SheetLoaderService {
  constructor() { }
  public load(file: File) {
    const reader: FileReader = new FileReader();
    reader.onload = (e: any) => {
      /* read workbook */
      const loadResult: string = e.target.result;
      const workbook: xlsx.WorkBook = xlsx.read(loadResult, {type: 'binary'});
      /* set first sheet as target sheet */
      const sheetName: string = workbook.SheetNames[0];
      /* read worksheet */
      const sheet: xlsx.WorkSheet = workbook.Sheets[sheetName];
    };
    reader.readAsBinaryString(file);
  }
}

Find named cell

I wanted to get named cell's position to know where start reading cells.
Could I get them from Workbook or Worksheet?

The answer was from Workbook.

sheet-loader.service.ts


...
export class SheetLoaderService {
  constructor() { }
  public load(file: File) {
    const reader: FileReader = new FileReader();
    reader.onload = (e: any) => {
      /* read workbook */
      const loadResult: string = e.target.result;
      const workbook: xlsx.WorkBook = xlsx.read(loadResult, {type: 'binary'});
      console.log(workbook.Workbook.Names);
...

The result liked below.
(2) [{…}, {…}]
    0:
        Name: "greeting"
        Ref: "Sample!$A$6"
        __proto__: Object
    1:
        Name: "Title"
        Ref: "Sample!$C$14"
        __proto__: Object
    length: 2
    __proto__: Array(0)
Export Named Range to CSV · Issue #708 · SheetJS/sheetjs · GitHub

Get named cell's value

Thus I could get Worksheet name and cell address from "Ref".
Because I couldn't find get cell value directory, I did like this.

sheet-loader.service.ts


...
export class SheetLoaderService {
  constructor() { }
  public load(file: File) {
    const reader: FileReader = new FileReader();
    reader.onload = (e: any) => {
      /* read workbook */
      const loadResult: string = e.target.result;
      const workbook: xlsx.WorkBook = xlsx.read(loadResult, {type: 'binary'});
      const targetCell = this.getTargetCell(workbook);
    }
...
  private getTargetCell(workbook: xlsx.WorkBook):
      { sheet: xlsx.WorkSheet, cell: xlsx.CellObject }|null {
    const targetCell = workbook.Workbook.Names
      .find(n => n.Name === 'Title');
    console.log(targetCell.Ref);
    if (targetCell == null) {
      return null;
    }
    // targetCell.Ref's value was {WorksheetName}!${Column}${Row}
    const splitCellNameRefs = targetCell.Ref.split('!');

    const sheet: xlsx.WorkSheet = workbook.Sheets[splitCellNameRefs[0]];
    // convert from $C$14 to { c: 2, r: 13 }
    const cellAddress = xlsx.utils.decode_cell(splitCellNameRefs[1]);
    // convert from { c: 2, r: 13 } to C14
    const encodedCellAddress = xlsx.utils.encode_cell(cellAddress);
    // sheet['C14'] -> get value of the cell where was at Col:C, Row;14.
    const cellValue: xlsx.CellObject = sheet[encodedCellAddress];
    return {
      sheet,
      cell: cellValue
    };
  }

The result liked below.
{t: "s", v: "Hello world", r: "<t xml:space="preserve">Hello world</t>", h: "Hello world", w: "Hello world"}
  t: "s"
  v: "Hello world"
  r: "<t xml:space="preserve">Hello world</t>"
  h: "Hello world"
  w: "Hello world"
  __proto__: Object
I thought this code had been a little verbose.
There might be a more concise way to write.

Get cell values

sheet-loader.service.ts


...
export class SheetLoaderService {
  constructor() { }
  public load(file: File) {
    const reader: FileReader = new FileReader();
    reader.onload = (e: any) => {
      /* read workbook */
      const loadResult: string = e.target.result;
      const workbook: xlsx.WorkBook = xlsx.read(loadResult, {type: 'binary'});
      const targetCell = this.getTargetCell(workbook);
      if (targetCell == null) {
        return null;
      }
      const endColumnNumber = this.getEndColumnNumber(targetCell.sheet, targetCell.cellAddress);
      console.log(endColumnNumber);
      const cellValues = this.getCellValues(targetCell.sheet, targetCell.cellAddress, endColumnNumber);
      console.log(cellValues);
    }
...
  private getEndColumnNumber(sheet: xlsx.WorkSheet, startCell: xlsx.CellAddress): number {
    let currentColumn = startCell.c;
    while (true) {
      const nextCellValue = sheet[xlsx.utils.encode_cell({c: currentColumn, r: startCell.r})];
      // if the cell value was empty, stop and return column number
      if (nextCellValue == null ||
          nextCellValue.v == null ||
          nextCellValue.v === '') {
        return currentColumn;
      }
      currentColumn += 1;
    }
  }
  private getCellValues(sheet: xlsx.WorkSheet, startCell: xlsx.CellAddress, endColumnNumber: number): CellValue[] {
    const cellValues = new Array<CellValue>();
    for (let i = startCell.c; i <= endColumnNumber; i++) {
      let currentRow = startCell.r;
      while (true) {
        const nextCellValue = sheet[xlsx.utils.encode_cell({c: i, r: currentRow})];
        if (nextCellValue == null ||
            nextCellValue.w == null ||
            nextCellValue.w === '') {
          break;;
        }
        // use formatted cell value.
        cellValues.push({
          column: i,
          row: currentRow,
          value: nextCellValue.w
        });
        currentRow += 1;
      }
    }
    return cellValues;
  }

The result liked below.
(8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
  0: {column: 2, row: 13, value: "Hello world"}
  1: {column: 2, row: 14, value: "12月31日"}
  2: {column: 2, row: 15, value: "TRUE"}
  3: {column: 2, row: 16, value: "1"}
  4: {column: 3, row: 13, value: "It’s sunny today"}
  5: {column: 3, row: 14, value: "1月3日"}
  6: {column: 3, row: 15, value: "FALSE"}
  7: {column: 3, row: 16, value: "2"}
  length: 8
  __proto__: Array(0)

コメント

このブログの人気の投稿

[Angular][ASP.NET Core] Upload chunked files

Intro I wanted to send files to Web application (made by ASP.NET Core). If the file size had been small, I didn't need do any special things. But when I tried to send a large file, the error was occurred by ASP.NET Core's limitation. Though I could change the settings, but I didn't want to do that, because I hadn't known the file sizes what would been actually using. So I splitted the data into chunks first, and sent them. After receiving all chunks, I merged them into one file. There might be some libraries or APIs (ex. Stream API) what did them automatically, but I couldn't find them. What I did [ASP.NET Core] Make CORS enabled [Angular] Split a large file into chunks [Angular][ASP.NET Core] Send and receive data as form data [ASP.NET Core] Merge chunks into one file [ASP.NET Core] Make CORS enabled Because the client side application(Angular) and the server side application(ASP.NET Core) had been separated, I had to make CORS(Cross-Origin Requests)

[Nest.js] Use WebSocket with ws

Intro Until last time , I had used node-web-rtc to try WebRTC. But because the example was a little complicated for I understood the core functions of using WebRTC. So I look for other frameworks or libraries. PeerJS is a famous library for WebRTC. peers/peerjs: Peer-to-peer data in the browser. - GitHub peers/peerjs-server: Server for PeerJS - GitHub PeerJS - Simple peer-to-peer with WebRTC A problem is I don't know how to integrate to the Nest.js project. I couldn't find examples. So I don't choose at least this time. What shall I choose? According MDN, WebRTC doesn't specify strictly what technology is used on server application for connecting two devices. Signaling and video calling - Web APIs | MDN But in many examples include MDN's one use WebSocket. samples-server/s/webrtc-from-chat at master · mdn/samples-server · GitHub So I try WebSocket in the Nest.js project. Use WebSocket in a Nest.js project Nest.js has a function for using We

[Nest.js] Show static files

Intro I wanted to use Nest.js and WebRTC(node-webrtc). NestJS - A progressive Node.js framework Documentation | NestJS - A progressive Node.js framework And because I wanted to try with simple page(not use JavaScript frameworks), I added static HTML, CSS, JavaScript into a Nest.js project. Prepare Install First, I installed @nestjs/cli. First steps | NestJS - A progressive Node.js framework As same as last time , I couldn't do global install because I had used Volta. But I could installed by volta. volta install @nestjs/cli Create project nest new nest-web-rtc-sample volta pin node@12 Run npm start After doing "npm start", I could getting "Hello World!" from http://localhost:3000. Add static files I could add static files by two ways. @nestjs/serve-static First one of them was using "serve-static". Serve Static | NestJS - A progressive Node.js framework npm install --save @nestjs/serve-static And I needed adding a module into app.modu