import _parseFrcsSurveyFile from '../survey/parseFrcsSurveyFile' import { ParseFrcsSurveyFileOptions } from '../survey/FrcsSurveyFile' import _parseFrcsPlotFile from '../parseFrcsPlotFile' import _parseFrcsTripSummaryFile from '../parseFrcsTripSummaryFile' import type { FrcsSurveyFile, InvalidFrcsSurveyFile, } from '../survey/FrcsSurveyFile' class LinesTransform extends TransformStream { parts: string[] = [] constructor() { super({ transform: (chunk, controller) => { let end = 0 for (const match of chunk.matchAll(/\r\n?|\n/gm)) { this.parts.push(chunk.substring(end, match.index)) controller.enqueue(this.parts.join('')) this.parts.length = 0 end = match.index + match[0].length } if (end < chunk.length) this.parts.push(chunk.substring(end)) }, flush: (controller) => { if (this.parts.length) { controller.enqueue(this.parts.join('')) this.parts.length = 0 } }, }) } } function readableStreamValues( stream: ReadableStream, { preventCancel = false }: { preventCancel?: boolean } = {} ): AsyncIterableIterator { const reader = stream.getReader() return { async next() { try { const result = await reader.read() if (result.done) { reader.releaseLock() } return result } catch (e) { reader.releaseLock() throw e } }, async return(value: T) { if (!preventCancel) { const cancelPromise = reader.cancel(value) reader.releaseLock() await cancelPromise } else { reader.releaseLock() } return { done: true, value } }, [Symbol.asyncIterator]() { return this }, } } function linesOf(input: Blob | ReadableStream): AsyncIterable { return readableStreamValues( (input instanceof ReadableStream ? input : input.stream()) .pipeThrough(new TextDecoderStream()) .pipeThrough(new LinesTransform()) ) } const convertLineBased = ( fn: (file: string, lines: AsyncIterable) => Promise ) => { function converted(file: File): Promise function converted(file: string, input: Blob | ReadableStream): Promise function converted( file: string | File, input?: Blob | ReadableStream ): Promise { if (file instanceof File) return converted(file.name, file) return fn(file, linesOf(input!)) } return converted } function chunksOf(input: Blob | ReadableStream): AsyncIterable { return readableStreamValues( (input instanceof ReadableStream ? input : input.stream()).pipeThrough( new TextDecoderStream('windows-1252') ) ) } export function parseFrcsSurveyFile( file: File, options?: ParseFrcsSurveyFileOptions ): Promise export function parseFrcsSurveyFile( file: string, input: Blob | ReadableStream, options?: ParseFrcsSurveyFileOptions ): Promise export function parseFrcsSurveyFile( file: string | File, input?: Blob | ReadableStream | ParseFrcsSurveyFileOptions, options?: ParseFrcsSurveyFileOptions ): Promise { if (file instanceof File) return _parseFrcsSurveyFile(file.name, chunksOf(file), options) if (input instanceof Blob || input instanceof ReadableStream) { return _parseFrcsSurveyFile(file, chunksOf(input), options) } throw new Error(`input ust be a Blob or ReadableStream if file is not a File`) } export const parseFrcsPlotFile = convertLineBased(_parseFrcsPlotFile) export const parseFrcsTripSummaryFile = convertLineBased( _parseFrcsTripSummaryFile )