UNPKG

read-gedcom

Version:
128 lines 5.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseGedcom = void 0; const decoder_1 = require("./decoder"); const decoding_1 = require("./decoding"); const error_1 = require("./error"); const indexer_1 = require("./indexer"); const structurer_1 = require("./structurer"); const tokenizer_1 = require("./tokenizer"); /** * Reads a Gedcom file and returns it as a tree representation. * @param buffer The content of the file * @param options Optional parameters * @throws ErrorParse If the file cannot be interpreted correctly * @category Gedcom parser */ const parseGedcom = (buffer, options = {}) => { checkMagicHeader(buffer); const charset = options.forcedCharset == null ? (0, decoder_1.detectCharset)(buffer) : options.forcedCharset; const callback = options.progressCallback; const totalBytes = buffer.byteLength; const decodingCallback = callback ? (bytesRead) => callback(0 /* GedcomReadingPhase.Decoding */, bytesRead / totalBytes) : undefined; let input; if (charset === "UTF-8" /* FileEncoding.Utf8 */ || charset === "UTF-16be" /* FileEncoding.Utf16be */ || charset === "UTF-16le" /* FileEncoding.Utf16le */) { input = (0, decoding_1.decodeUtf)(buffer, decodingCallback); } else if (charset === "Cp1252" /* FileEncoding.Cp1252 */) { input = (0, decoding_1.decodeCp1252)(buffer, decodingCallback); } else if (charset === "ANSEL" /* FileEncoding.Ansel */) { input = (0, decoding_1.decodeAnsel)(buffer, decodingCallback, !!options.doStrictDecoding); } else if (charset === "Macintosh" /* FileEncoding.Macintosh */) { input = (0, decoding_1.decodeMacintosh)(buffer, decodingCallback); } else if (charset === "Cp850" /* FileEncoding.Cp850 */) { input = (0, decoding_1.decodeCp850)(buffer, decodingCallback); } else { throw new error_1.ErrorUnsupportedCharset(`Unsupported charset: ${charset}`, charset); } const totalChars = input.length; const tokensIterator = (0, tokenizer_1.tokenize)(input); const rootNode = (0, structurer_1.buildTree)(tokensIterator, !!options.noInlineContinuations, callback ? charsRead => callback(1 /* GedcomReadingPhase.TokenizationAndStructuring */, charsRead / totalChars) : null); checkTreeStructure(rootNode); if (!options.noIndex) { (0, indexer_1.indexTree)(rootNode, !!options.noBackwardsReferencesIndex, !!options.doHideIndex, callback ? () => callback(2 /* GedcomReadingPhase.Indexing */, null) : null); } if (options.doFreeze) { if (callback) { callback(3 /* GedcomReadingPhase.Freezing */, 0); } deepFreeze(rootNode); if (callback) { callback(3 /* GedcomReadingPhase.Freezing */, 1); } } return rootNode; }; exports.parseGedcom = parseGedcom; /** * A simple and fast testing procedure to eliminate files that are clearly not Gedcoms. * @param buffer The content of the file */ const checkMagicHeader = (buffer) => { const headStr = '0 HEAD'; const head = []; for (let i = 0; i < headStr.length; i++) { head.push(headStr.charCodeAt(i)); } const byteBuffer = new Uint8Array(buffer); const startsWith = (bytes) => { if (byteBuffer.byteLength < bytes.length) { return false; } for (let i = 0; i < bytes.length; i++) { if (bytes[i] !== byteBuffer[i]) { return false; } } return true; }; if (!startsWith(head) && !startsWith(decoding_1.BOM_UTF8.concat(head))) { // Not ASCII nor any of its extensions, including UTF-8 const headUtf16 = head.map(b => [0, b]); if (!startsWith(decoding_1.BOM_UTF16_BE.concat(headUtf16.flat())) && !startsWith(decoding_1.BOM_UTF16_LE.concat(headUtf16.map(bs => bs.slice().reverse()).flat()))) { // Not UTF-16 const headUtf32 = head.map(b => [0, 0, 0, b]); if (!startsWith(decoding_1.BOM_UTF32_BE.concat(headUtf32.flat())) && !startsWith(decoding_1.BOM_UTF32_LE.concat(headUtf32.map(bs => bs.slice().reverse()).flat()))) { // Not UTF-32 throw new error_1.ErrorInvalidFileType('Probably not a Gedcom file'); } } } }; /** * A simple procedure verifying that the root node starts with a {@link Tag.Header} and ends with a {@link Tag.Trailer}. * @param rootNode The root node */ const checkTreeStructure = (rootNode) => { const root = rootNode.children; // Assumes that `root.length > 0` const header = root[0]; if (header.tag !== "HEAD" /* Tag.Header */) { throw new error_1.ErrorTreeStructure(`First node is not a header (got ${header.tag})`); } const trailer = root[root.length - 1]; if (trailer.tag !== "TRLR" /* Tag.Trailer */) { throw new error_1.ErrorTreeStructure(`Last node is not a trailer (got ${trailer.tag})`); } }; /** * Freezes this object to prevent further modifications. * @param object The object to be frozen */ const deepFreeze = (object) => { let queue = [object]; while (queue.length > 0) { const next = []; queue.forEach(obj => { if (obj != null && typeof obj === 'object' && !Object.isFrozen(obj)) { Object.freeze(obj); Object.getOwnPropertyNames(obj).forEach(property => next.push(obj[property])); } }); queue = next; } }; //# sourceMappingURL=reader.js.map