UNPKG

read-gedcom

Version:
124 lines 5.32 kB
import { detectCharset } from './decoder'; import { BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE, BOM_UTF8, decodeAnsel, decodeCp1252, decodeCp850, decodeMacintosh, decodeUtf, } from './decoding'; import { ErrorInvalidFileType, ErrorTreeStructure, ErrorUnsupportedCharset } from './error'; import { indexTree } from './indexer'; import { buildTree } from './structurer'; import { tokenize } from './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 */ export const parseGedcom = (buffer, options = {}) => { checkMagicHeader(buffer); const charset = options.forcedCharset == null ? 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 = decodeUtf(buffer, decodingCallback); } else if (charset === "Cp1252" /* FileEncoding.Cp1252 */) { input = decodeCp1252(buffer, decodingCallback); } else if (charset === "ANSEL" /* FileEncoding.Ansel */) { input = decodeAnsel(buffer, decodingCallback, !!options.doStrictDecoding); } else if (charset === "Macintosh" /* FileEncoding.Macintosh */) { input = decodeMacintosh(buffer, decodingCallback); } else if (charset === "Cp850" /* FileEncoding.Cp850 */) { input = decodeCp850(buffer, decodingCallback); } else { throw new ErrorUnsupportedCharset(`Unsupported charset: ${charset}`, charset); } const totalChars = input.length; const tokensIterator = tokenize(input); const rootNode = buildTree(tokensIterator, !!options.noInlineContinuations, callback ? charsRead => callback(1 /* GedcomReadingPhase.TokenizationAndStructuring */, charsRead / totalChars) : null); checkTreeStructure(rootNode); if (!options.noIndex) { 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; }; /** * 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(BOM_UTF8.concat(head))) { // Not ASCII nor any of its extensions, including UTF-8 const headUtf16 = head.map(b => [0, b]); if (!startsWith(BOM_UTF16_BE.concat(headUtf16.flat())) && !startsWith(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(BOM_UTF32_BE.concat(headUtf32.flat())) && !startsWith(BOM_UTF32_LE.concat(headUtf32.map(bs => bs.slice().reverse()).flat()))) { // Not UTF-32 throw new 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 ErrorTreeStructure(`First node is not a header (got ${header.tag})`); } const trailer = root[root.length - 1]; if (trailer.tag !== "TRLR" /* Tag.Trailer */) { throw new 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