gff-nostream
Version:
utilities to read GFF3 data
120 lines • 4.34 kB
JavaScript
;
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