UNPKG

@kobold/excel

Version:

Kobold excel data handler

174 lines (127 loc) 7.33 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property"); _Object$defineProperty(exports, "__esModule", { value: true }); exports.Sheet = void 0; var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes")); var _findIndex = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/find-index")); var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/keys")); var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/map")); var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/slice")); var _map2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/map")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty")); var _binaryParser = require("binary-parser"); var _files = require("./files"); var _utilities = require("./utilities"); // TODO: where should this live? is it composed by excel, or is it handled as part of the page file reader itself? // Should page pre-parse rows? const rowHeaderParser = new _binaryParser.Parser().endianess('big').uint32('dataSize').uint16('rowCount'); const rowHeaderSize = rowHeaderParser.sizeOf(); const languageStringMap = { [_files.Language.NONE]: 'NONE', [_files.Language.JAPANESE]: 'ja', [_files.Language.ENGLISH]: 'en', [_files.Language.GERMAN]: 'de', [_files.Language.FRENCH]: 'fr', [_files.Language.CHINESE_SIMPLIFIED]: 'chs', [_files.Language.CHINESE_TRADITIONAL]: 'cht', [_files.Language.KOREAN]: 'ko', [_files.Language.TRADITIONAL_CHINESE]: 'tc' }; class Sheet { constructor(opts) { (0, _defineProperty2.default)(this, "kobold", void 0); (0, _defineProperty2.default)(this, "RowClass", void 0); (0, _defineProperty2.default)(this, "language", void 0); (0, _defineProperty2.default)(this, "headerCache", void 0); (0, _defineProperty2.default)(this, "pageCache", new _map2.default()); this.kobold = opts.kobold; this.RowClass = opts.RowClass; this.language = opts.language; } async *getRows(opts) { var _context; const header = await this.getHeader(); // Make sure they pass the lower of the two as froms if ((opts === null || opts === void 0 ? void 0 : opts.from) != null && opts.to != null) { (0, _utilities.assert)(opts.from <= opts.to, `Specified "from" row index must precede "to" index.`); } // Work out what pages the requested from/to rows reside in, falling back to first/last. Prettier _really_ fucks these up // prettier-ignore const from = (opts === null || opts === void 0 ? void 0 : opts.from) != null ? this.getPageForRow(header.pages, opts.from) : 0; // prettier-ignore const to = (opts === null || opts === void 0 ? void 0 : opts.to) != null ? this.getPageForRow(header.pages, opts.to) : header.pages.length - 1; const pageDefinitions = (0, _slice.default)(_context = header.pages).call(_context, from, to + 1); // Preload the pages we'll be looping const pagePreLoad = (0, _map.default)(pageDefinitions).call(pageDefinitions, ({ startId }) => this.getPage(startId)); // holy nested loops batman for await (const page of pagePreLoad) { for (const index of (0, _keys.default)(_context2 = page.rowOffsets).call(_context2)) { var _context2, _opts$from, _opts$to; // Skip rows not contained within the requested range, if any if (index < ((_opts$from = opts === null || opts === void 0 ? void 0 : opts.from) !== null && _opts$from !== void 0 ? _opts$from : 0) || index > ((_opts$to = opts === null || opts === void 0 ? void 0 : opts.to) !== null && _opts$to !== void 0 ? _opts$to : Infinity)) { continue; } const rowHeader = this.parseRowHeader(page, index); // TODO: DEFAULT Variants seem to be stable on rowCount:1 here, but keep an eye on it for (let subIndex = 0; subIndex < rowHeader.rowCount; subIndex++) { yield this.buildRow(header, page, index, subIndex); } } } } async getRow(index, subIndex = 0) { const header = await this.getHeader(); // Work out what page the requested row is on const pageDefinition = header.pages[this.getPageForRow(header.pages, index)]; const page = await this.getPage(pageDefinition.startId); return this.buildRow(header, page, index, subIndex); } getPageForRow(pages, index) { const pageIndex = (0, _findIndex.default)(pages).call(pages, page => page.startId <= index && page.startId + page.rowCount > index); (0, _utilities.assert)(pageIndex >= 0, `Requested index ${index} is not defined by any sheet pages`); return pageIndex; } // TODO: Probably should rename this given how much logic i'm throwing in async getHeader() { if (this.headerCache == null) { const path = `exd/${this.RowClass.sheet}.exh`; this.headerCache = await this.kobold.getFile(path, _files.ExcelHeader); } // If the sheet _only_ supports NONE, silently fall back const { languages } = this.headerCache; if (languages.length === 1 && languages[0] === _files.Language.NONE) { this.language = _files.Language.NONE; } // If the requested language isn't supported by the sheet, panic (0, _utilities.assert)((0, _includes.default)(languages).call(languages, this.language), `Requested language ${_files.Language[this.language]} is not provided by sheet ${this.RowClass.sheet} (provided: ${(0, _map.default)(languages).call(languages, lang => _files.Language[lang]).join(', ')})`); return this.headerCache; } async getPage(startId) { const language = this.language; // exd/{sheetName}_{page}[_{languageString}].exd const path = language === _files.Language.NONE ? `exd/${this.RowClass.sheet}_${startId}.exd` : `exd/${this.RowClass.sheet}_${startId}_${languageStringMap[language]}.exd`.toLowerCase(); let page = this.pageCache.get(path); if (page == null) { page = await this.kobold.getFile(path, _files.ExcelPage); this.pageCache.set(path, page); } return page; } parseRowHeader(page, index) { const rowOffset = page.rowOffsets.get(index); (0, _utilities.assert)(rowOffset != null); // TODO: This subarray call is unfortunate - look into getting rid of it return rowHeaderParser.parse(page.data.subarray(rowOffset, rowOffset + rowHeaderSize)); } buildRow(header, page, index, subIndex) { const rowOffset = page.rowOffsets.get(index); (0, _utilities.assert)(rowOffset != null); // TODO: This is duplicated work when using getRows() - look into consolidating const rowHeader = this.parseRowHeader(page, index); (0, _utilities.assert)(subIndex < 0 || subIndex < rowHeader.rowCount, `Requested subrow ${subIndex} of row ${index} outside valid range 0 - ${rowHeader.rowCount - 1}`); const rowStart = rowOffset + rowHeaderSize; const rowLength = header.rowSize + rowHeader.dataSize; const rowData = page.data.subarray(rowStart, rowStart + rowLength); return new this.RowClass({ index, subIndex, sheetHeader: header, data: rowData }); } } exports.Sheet = Sheet;