@gmod/vcf
Version:
High performance streaming Variant Call Format (VCF) parser in pure JavaScript
104 lines • 3.65 kB
JavaScript
import { Variant } from "./Variant.js";
import { parseStructuredMetaVal } from "./parseMetaString.js";
import vcfReserved from "./vcfReserved.js";
export { Variant } from "./Variant.js";
export default class VCFParser {
metadata;
strict;
samples;
constructor({ header, strict = true }) {
if (!header.length) {
throw new Error('empty header received');
}
const headerLines = header.split(/[\r\n]+/).filter(Boolean);
if (!headerLines.length) {
throw new Error('no non-empty header lines specified');
}
this.strict = strict;
this.metadata = {
INFO: { ...vcfReserved.InfoFields },
FORMAT: { ...vcfReserved.GenotypeFields },
ALT: { ...vcfReserved.AltTypes },
FILTER: { ...vcfReserved.FilterTypes },
};
let lastLine;
for (let i = 0; i < headerLines.length; i++) {
const line = headerLines[i] ?? '';
if (!line.startsWith('#')) {
throw new Error(`Bad line in header:\n${line}`);
}
else if (line.startsWith('##')) {
this.parseMetadata(line);
}
else {
lastLine = line;
}
}
if (!lastLine) {
throw new Error('No format line found in header');
}
const fields = lastLine.trim().split('\t');
const thisHeader = fields.slice(0, 8);
const correctHeader = [
'#CHROM',
'POS',
'ID',
'REF',
'ALT',
'QUAL',
'FILTER',
'INFO',
];
if (fields.length < 8) {
throw new Error(`VCF header missing columns:\n${lastLine}`);
}
else if (thisHeader.length !== correctHeader.length ||
!thisHeader.every((value, index) => value === correctHeader[index])) {
throw new Error(`VCF column headers not correct:\n${lastLine}`);
}
this.samples = fields.slice(9);
}
parseMetadata(line) {
const match = /^##(.+?)=(.*)/.exec(line.trim());
if (!match) {
throw new Error(`Line is not a valid metadata line: ${line}`);
}
const r = match[1] ?? '';
const metaVal = match[2];
if (metaVal?.startsWith('<')) {
const existing = this.metadata[r];
const section = existing && typeof existing === 'object' ? existing : {};
const [id, keyVals] = parseStructuredMetaVal(metaVal);
if (typeof id === 'string') {
// ##INFO=<ID=AF_ESP,...>
section[id] = keyVals;
this.metadata[r] = section;
}
else {
// ##ID=<Description="ClinVar Variation ID">
this.metadata[r] = keyVals;
}
}
else {
this.metadata[r] = metaVal;
}
}
getMetadata(...args) {
let filteredMetadata = this.metadata;
for (const arg of args) {
if (typeof filteredMetadata !== 'object' || filteredMetadata === null) {
return undefined;
}
filteredMetadata = filteredMetadata[arg];
if (filteredMetadata === undefined) {
return undefined;
}
}
return filteredMetadata;
}
// SAMPLES() and GENOTYPES() on the returned Variant are lazily evaluated.
parseLine(line) {
return new Variant(line, this.metadata.INFO, this.metadata.FORMAT, this.samples, this.strict);
}
}
//# sourceMappingURL=parse.js.map