UNPKG

p6-xer

Version:

A TypeScript module for parsing and processing Primavera P6 XER files

261 lines (260 loc) 9.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.XerParser = exports.EncodingError = exports.XerParserError = void 0; const fs = __importStar(require("fs")); const chardet = __importStar(require("chardet")); const iconv = __importStar(require("iconv-lite")); const XLSX = __importStar(require("xlsx")); class XerParserError extends Error { constructor(message) { super(message); this.name = 'XerParserError'; } } exports.XerParserError = XerParserError; class EncodingError extends XerParserError { constructor(message) { super(message); this.name = 'EncodingError'; } } exports.EncodingError = EncodingError; class XerParser { constructor(options = {}) { // Alias for parseAsync for backward compatibility this.parse = this.parseAsync; // Alias for exportToXlsxAsync for backward compatibility this.exportToXlsx = this.exportToXlsxAsync; this.options = { encoding: options.encoding || 'utf8', skipEmptyTables: options.skipEmptyTables || false, }; } handleTableStart(currentTable, values, data) { if (currentTable && (!this.options.skipEmptyTables || currentTable.rows.length > 0)) { data.tables.push(currentTable); } return { name: values[0], fields: [], rows: [] }; } handleFieldDefinition(currentTable, values) { if (currentTable) { currentTable.fields = values; } } handleDataRow(currentTable, values) { if (currentTable && currentTable.fields.length > 0) { const row = {}; currentTable.fields.forEach((field, index) => { row[field] = values[index] || ''; }); currentTable.rows.push(row); } } handleTableEnd(currentTable, data) { if (currentTable) { if (!this.options.skipEmptyTables || currentTable.rows.length > 0) { data.tables.push(currentTable); } return null; } return currentTable; } parseErmhdrLine(line) { const parts = line.split('\t'); if (parts[0] !== 'ERMHDR' || parts.length < 9) { return undefined; } return { version: parts[1], date: parts[2], project: parts[3], id: parts[4], user: parts[5], database: parts[6], system: parts[7], currency: parts[8] }; } parseContent(content) { const lines = content.split(/\r?\n/); const data = { tables: [] }; // Check for ERMHDR line at the start if (lines.length > 0) { data.header = this.parseErmhdrLine(lines[0]); } let currentTable = null; // Start from line 1 if we found a header, otherwise start from line 0 for (let i = data.header ? 1 : 0; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; const [type, ...values] = line.split('\t'); switch (type) { case '%T': currentTable = this.handleTableStart(currentTable, values, data); break; case '%F': this.handleFieldDefinition(currentTable, values); break; case '%R': this.handleDataRow(currentTable, values); break; case '%E': currentTable = this.handleTableEnd(currentTable, data); break; } } return data; } parseSync(filePath) { try { const buffer = fs.readFileSync(filePath); const encoding = this.detectEncoding(buffer); const content = iconv.decode(buffer, encoding); return this.parseContent(content); } catch (error) { if (error instanceof Error) { throw new XerParserError(error.message); } throw new XerParserError('Unknown error occurred while parsing XER file'); } } async parseAsync(filePath) { try { const buffer = await fs.promises.readFile(filePath); const encoding = this.detectEncoding(buffer); const content = iconv.decode(buffer, encoding); return this.parseContent(content); } catch (error) { if (error instanceof Error) { throw new XerParserError(error.message); } throw new XerParserError('Unknown error occurred while parsing XER file'); } } detectEncoding(buffer) { var _a; try { const detected = chardet.analyse(buffer); const encoding = ((_a = detected[0]) === null || _a === void 0 ? void 0 : _a.name) || 'UTF-8'; if (!iconv.encodingExists(encoding)) { throw new EncodingError(`Unsupported encoding: ${encoding}`); } return encoding; } catch (error) { if (error instanceof EncodingError) { throw error; } throw new EncodingError('Failed to detect file encoding'); } } exportToWorkbook(data, options) { const workbook = XLSX.utils.book_new(); // Add header information if available if (data.header) { const headerData = [{ Field: 'Version', Value: data.header.version }, { Field: 'Date', Value: data.header.date }, { Field: 'Project', Value: data.header.project }, { Field: 'ID', Value: data.header.id }, { Field: 'User', Value: data.header.user }, { Field: 'Database', Value: data.header.database }, { Field: 'System', Value: data.header.system }, { Field: 'Currency', Value: data.header.currency }]; const headerSheet = XLSX.utils.json_to_sheet(headerData); XLSX.utils.book_append_sheet(workbook, headerSheet, 'Header'); } // Add each table as a separate sheet data.tables.forEach(table => { const sheetName = options.sheetNamePrefix ? `${options.sheetNamePrefix}_${table.name}`.substring(0, 31) : table.name.substring(0, 31); // Process rows to handle long text const processedRows = table.rows.map(row => { const processedRow = {}; for (const [key, value] of Object.entries(row)) { // Truncate values that are too long for Excel processedRow[key] = value.length > 32000 ? value.substring(0, 32000) + '... (truncated)' : value; } return processedRow; }); const worksheet = XLSX.utils.json_to_sheet(processedRows); XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); }); // If no sheets were added, add an empty sheet if (workbook.SheetNames.length === 0) { const emptySheet = XLSX.utils.json_to_sheet([]); XLSX.utils.book_append_sheet(workbook, emptySheet, 'Empty'); } return workbook; } exportToXlsxSync(data, options) { const workbook = this.exportToWorkbook(data, options); XLSX.writeFile(workbook, options.outputPath); } async exportToXlsxAsync(data, options) { const workbook = this.exportToWorkbook(data, options); await fs.promises.writeFile(options.outputPath, XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' })); } } exports.XerParser = XerParser;