Skip to content

Commit d3389bb

Browse files
committed
feat: add CSV to XLSX conversion support and improve error handling in x2t.ts
1 parent 5072547 commit d3389bb

File tree

1 file changed

+180
-3
lines changed

1 file changed

+180
-3
lines changed

lib/x2t.ts

Lines changed: 180 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,19 @@ class X2TConverter {
247247

248248
const result = this.x2tModule.ccall('main1', 'number', ['string'], [paramsPath]);
249249
if (result !== 0) {
250+
// Read the params XML for debugging
251+
try {
252+
const paramsContent = this.x2tModule.FS.readFile(paramsPath, { encoding: 'binary' });
253+
// Convert binary to string for logging
254+
if (paramsContent instanceof Uint8Array) {
255+
const paramsText = new TextDecoder('utf-8').decode(paramsContent);
256+
console.error('Conversion failed. Parameters XML:', paramsText);
257+
} else {
258+
console.error('Conversion failed. Parameters XML:', paramsContent);
259+
}
260+
} catch (_e) {
261+
// Ignore if we can't read the params file
262+
}
250263
throw new Error(`Conversion failed with code: ${result}`);
251264
}
252265
}
@@ -298,6 +311,73 @@ class X2TConverter {
298311
return media;
299312
}
300313

314+
/**
315+
* Load xlsx library dynamically from CDN
316+
*/
317+
private async loadXlsxLibrary(): Promise<any> {
318+
// Check if xlsx is already loaded
319+
if (typeof window !== 'undefined' && (window as any).XLSX) {
320+
return (window as any).XLSX;
321+
}
322+
323+
return new Promise((resolve, reject) => {
324+
const script = document.createElement('script');
325+
script.src = 'https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js';
326+
script.onload = () => {
327+
if (typeof window !== 'undefined' && (window as any).XLSX) {
328+
resolve((window as any).XLSX);
329+
} else {
330+
reject(new Error('Failed to load xlsx library'));
331+
}
332+
};
333+
script.onerror = () => {
334+
reject(new Error('Failed to load xlsx library from CDN'));
335+
};
336+
document.head.appendChild(script);
337+
});
338+
}
339+
340+
/**
341+
* Convert CSV to XLSX format using SheetJS library
342+
* This is a workaround since x2t may not support CSV directly
343+
*/
344+
private async convertCsvToXlsx(csvData: Uint8Array, fileName: string): Promise<File> {
345+
try {
346+
// Load xlsx library
347+
const XLSX = await this.loadXlsxLibrary();
348+
349+
// Remove UTF-8 BOM if present
350+
let csvText: string;
351+
if (csvData.length >= 3 && csvData[0] === 0xEF && csvData[1] === 0xBB && csvData[2] === 0xBF) {
352+
csvText = new TextDecoder('utf-8').decode(csvData.slice(3));
353+
} else {
354+
// Try UTF-8 first, fallback to other encodings if needed
355+
try {
356+
csvText = new TextDecoder('utf-8').decode(csvData);
357+
} catch {
358+
csvText = new TextDecoder('latin1').decode(csvData);
359+
}
360+
}
361+
362+
// Parse CSV using SheetJS
363+
const workbook = XLSX.read(csvText, { type: 'string', raw: false });
364+
365+
// Convert to XLSX binary format
366+
const xlsxBuffer = XLSX.write(workbook, { type: 'array', bookType: 'xlsx' });
367+
368+
// Create File object
369+
const xlsxFileName = fileName.replace(/\.csv$/i, '.xlsx');
370+
return new File([xlsxBuffer], xlsxFileName, {
371+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
372+
});
373+
} catch (error) {
374+
throw new Error(
375+
`Failed to convert CSV to XLSX: ${error instanceof Error ? error.message : 'Unknown error'}. ` +
376+
'Please convert your CSV file to XLSX format manually and try again.'
377+
);
378+
}
379+
}
380+
301381
/**
302382
* Convert document to bin format
303383
*/
@@ -313,16 +393,67 @@ class X2TConverter {
313393
const arrayBuffer = await file.arrayBuffer();
314394
const data = new Uint8Array(arrayBuffer);
315395

316-
// Generate safe file name
396+
// Handle CSV files - x2t may not support them directly, so convert to XLSX first
397+
if (fileExt.toLowerCase() === 'csv') {
398+
if (data.length === 0) {
399+
throw new Error('CSV file is empty');
400+
}
401+
console.log('CSV file detected. Converting to XLSX format...');
402+
console.log('CSV file size:', data.length, 'bytes');
403+
404+
// Convert CSV to XLSX first
405+
try {
406+
const xlsxFile = await this.convertCsvToXlsx(data, fileName);
407+
console.log('CSV converted to XLSX, now converting with x2t...');
408+
409+
// Now convert the XLSX file using x2t
410+
const xlsxArrayBuffer = await xlsxFile.arrayBuffer();
411+
const xlsxData = new Uint8Array(xlsxArrayBuffer);
412+
413+
// Use the XLSX file for conversion
414+
const sanitizedName = this.sanitizeFileName(xlsxFile.name);
415+
const inputPath = `/working/${sanitizedName}`;
416+
const outputPath = `${inputPath}.bin`;
417+
418+
// Write XLSX file to virtual file system
419+
this.x2tModule!.FS.writeFile(inputPath, xlsxData);
420+
421+
// Create conversion parameters - no special params needed for XLSX
422+
const params = this.createConversionParams(inputPath, outputPath, '');
423+
this.x2tModule!.FS.writeFile('/working/params.xml', params);
424+
425+
// Execute conversion
426+
this.executeConversion('/working/params.xml');
427+
428+
// Read conversion result
429+
const result = this.x2tModule!.FS.readFile(outputPath);
430+
const media = this.readMediaFiles();
431+
432+
return {
433+
fileName: sanitizedName,
434+
type: documentType,
435+
bin: result,
436+
media,
437+
};
438+
} catch (conversionError: any) {
439+
// If conversion fails, provide helpful error message
440+
throw new Error(
441+
`Failed to convert CSV file: ${conversionError?.message || 'Unknown error'}. ` +
442+
'Please ensure your CSV file is properly formatted and try again.'
443+
);
444+
}
445+
}
446+
447+
// For all other file types, use standard conversion
317448
const sanitizedName = this.sanitizeFileName(fileName);
318449
const inputPath = `/working/${sanitizedName}`;
319450
const outputPath = `${inputPath}.bin`;
320451

321452
// Write file to virtual file system
322453
this.x2tModule!.FS.writeFile(inputPath, data);
323454

324-
// Create conversion parameters
325-
const params = this.createConversionParams(inputPath, outputPath);
455+
// Create conversion parameters - no special params needed for non-CSV files
456+
const params = this.createConversionParams(inputPath, outputPath, '');
326457
this.x2tModule!.FS.writeFile('/working/params.xml', params);
327458

328459
// Execute conversion
@@ -343,6 +474,52 @@ class X2TConverter {
343474
}
344475
}
345476

477+
/**
478+
* Attempt to convert CSV directly using x2t (may fail)
479+
*/
480+
private async convertCsvDirectly(
481+
_file: File,
482+
data: Uint8Array,
483+
fileName: string,
484+
documentType: DocumentType,
485+
): Promise<ConversionResult> {
486+
// Handle UTF-8 BOM
487+
let fileData = data;
488+
const hasBOM = data.length >= 3 && data[0] === 0xEF && data[1] === 0xBB && data[2] === 0xBF;
489+
if (!hasBOM) {
490+
const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
491+
fileData = new Uint8Array(bom.length + data.length);
492+
fileData.set(bom, 0);
493+
fileData.set(data, bom.length);
494+
}
495+
496+
const sanitizedName = this.sanitizeFileName(fileName);
497+
const inputPath = `/working/${sanitizedName}`;
498+
const outputPath = `${inputPath}.bin`;
499+
500+
// Write file to virtual file system
501+
this.x2tModule!.FS.writeFile(inputPath, fileData);
502+
503+
// Try with format specification
504+
const additionalParams = '<m_nFormatFrom>260</m_nFormatFrom>';
505+
const params = this.createConversionParams(inputPath, outputPath, additionalParams);
506+
this.x2tModule!.FS.writeFile('/working/params.xml', params);
507+
508+
// Execute conversion - this will likely fail with error 89
509+
this.executeConversion('/working/params.xml');
510+
511+
// If we get here, conversion succeeded (unlikely for CSV)
512+
const result = this.x2tModule!.FS.readFile(outputPath);
513+
const media = this.readMediaFiles();
514+
515+
return {
516+
fileName: sanitizedName,
517+
type: documentType,
518+
bin: result,
519+
media,
520+
};
521+
}
522+
346523
/**
347524
* Convert bin format to specified format and download
348525
*/

0 commit comments

Comments
 (0)