UNPKG

gff-nostream

Version:
120 lines 4.34 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractType = extractType; exports.parseStringSync = parseStringSync; exports.parseRecords = parseRecords; const util_ts_1 = require("./util.js"); /** Extract the GFF3 feature type (column 3) from a raw line without a full split. */ function extractType(line) { const t1 = line.indexOf('\t'); const t2 = line.indexOf('\t', t1 + 1); const t3 = line.indexOf('\t', t2 + 1); return line.slice(t2 + 1, t3); } /** Append a value to the array stored under key, creating the array if absent. */ function appendOrphan(orphans, key, value) { const arr = orphans.get(key); if (arr) { arr.push(value); } else { orphans.set(key, [value]); } } /** * The parser collapses single-element attribute arrays to scalars, so a raw * ID/Parent value can be a string, a string array, or absent. These coerce * those `unknown` values without typecasts. */ function firstString(value) { const v = Array.isArray(value) ? value[0] : value; return typeof v === 'string' ? v : undefined; } function toStringArray(value) { if (Array.isArray(value)) { return value.filter((v) => typeof v === 'string'); } return typeof value === 'string' ? [value] : []; } /** * Register a feature's ID and attach it to its parent(s), building the * subfeature tree in `byId`/`orphans`. Returns true when the feature is * top-level (has no Parent) and the caller should collect it. */ function linkFeature(feature, byId, orphans) { const id = firstString(feature.id); const parents = toStringArray(feature.parent); // Register the id only the first time it is seen. Continuation lines // (multi-location features such as a CDS spanning several segments share one // ID across lines) skip registration but must still be attached to their // parent below, so this is independent of the parent handling. if (id && !byId.has(id)) { byId.set(id, feature); const waiting = orphans.get(id); if (waiting) { for (const w of waiting) { feature.subfeatures.push(w); } orphans.delete(id); } } for (const parentId of parents) { const parentFeature = byId.get(parentId); if (parentFeature) { parentFeature.subfeatures.push(feature); } else { appendOrphan(orphans, parentId, feature); } } // Every line of a top-level discontinuous feature (e.g. cDNA_match spanning // several segments under one shared ID, with no Parent) is its own top-level // item, so this is independent of whether the id was just registered. return parents.length === 0; } /** * Synchronously parse a string containing GFF3 and return an array of the * parsed features. Comments, directives, and `##FASTA` sections are ignored. * * @param str - GFF3 string * @returns array of parsed features */ function parseStringSync(str) { const items = []; const byId = new Map(); const orphans = new Map(); for (const line of str.split(/\r?\n/)) { if (line.startsWith('##FASTA') || line.startsWith('>')) { break; } if (line.length !== 0 && !line.startsWith('#')) { const feature = (0, util_ts_1.parseFeature)(line); if (linkFeature(feature, byId, orphans)) { items.push(feature); } } } return items; } /** * Parse an array of records wrapping raw GFF3 lines, resolving parent/child * relationships into `subfeatures`. Returns each top-level feature paired with * the record it came from, so callers can attach their own identity (e.g. a * byte offset) without the parser stamping anything onto the feature. * * @param records - Array of records, each carrying a raw GFF3 `line` * @returns top-level features, each paired with its originating record */ function parseRecords(records) { const items = []; const byId = new Map(); const orphans = new Map(); for (const record of records) { const feature = (0, util_ts_1.parseFeature)(record.line); if (linkFeature(feature, byId, orphans)) { items.push({ feature, record }); } } return items; } //# sourceMappingURL=api.js.map