UNPKG

@gmod/jbrowse

Version:

JBrowse - client-side genome browser

223 lines (197 loc) 6.94 kB
import gff from '@gmod/gff' define([ 'dojo/_base/declare', 'dojo/_base/lang', 'dojo/_base/array', 'JBrowse/View/Export' ], function( declare, lang, array, ExportBase ) { return declare( ExportBase, /** * @lends JBrowse.View.Export.GFF3.prototype */ { /** * Data export driver for GFF3 format. * @constructs */ constructor: function( args ) { this._idCounter = 0; this.lastSync = 0; }, gff3_field_names: [ 'seq_id', 'source', 'type', 'start', 'end', 'score', 'strand', 'phase', 'attributes' ], gff3_reserved_attributes: [ 'ID', 'Name', 'Alias', 'Parent', 'Target', 'Gap', 'Derives_from', 'Note', 'Dbxref', 'Ontology_term', 'Is_circular' ], /** * @returns false if the field goes in tabular portion of gff3, true otherwise * @private */ _is_not_gff3_tab_field: function( fieldname ) { if( ! this._gff3_fields_by_name ) { var fields = {}; dojo.forEach( this.gff3_field_names, function(f) { fields[f] = true; }); this._gff3_fields_by_name = fields; } return ! this._gff3_fields_by_name[ fieldname.toLowerCase() ]; }, /** * @returns the capitalized attribute name if the given field name * corresponds to a GFF3 reserved attribute * @private */ _gff3_reserved_attribute: function( fieldname ) { if( ! this._gff3_reserved_attributes_by_lcname ) { var fields = {}; dojo.forEach( this.gff3_reserved_attributes, function(f) { fields[f.toLowerCase()] = f; }); this._gff3_reserved_attributes_by_lcname = fields; } return this._gff3_reserved_attributes_by_lcname[ fieldname.toLowerCase() ]; }, exportRegion(region, callback) { this.print( "##gff-version 3\n"); this.print( `##sequence-region ${region.ref} ${region.start+1} ${region.end}\n` ); this.inherited(arguments) }, /** * Format a feature into a string. * @param {Object} feature feature object (like those returned from JBrowse/Store/SeqFeature/*) * @returns {String} GFF3 string representation of the feature */ formatFeature: function( feature, parentID ) { var fields = dojo.map( [ feature.get('seq_id') || this.refSeq.name ] .concat( dojo.map( this.gff3_field_names.slice(1,8), function(field) { return feature.get( field ); },this) ), function( data ) { var dt = typeof data; return gff.util.escapeColumn( dt == 'string' || dt == 'number' ? data : '.' ); }, this ); // convert back from interbase if( typeof parseInt(fields[3]) == 'number' ) fields[3]++; // normalize the strand field fields[6] = { '1': '+', '-1': '-', '0': '.' }[ fields[6] ] || fields[6]; // format the attributes var attr = this._gff3_attributes( feature ); if( parentID ) attr.Parent = parentID; else delete attr.Parent; var subfeatures = array.map( feature.get('subfeatures') || [], function(feat) { if( ! attr.ID ) { attr.ID = ++this._idCounter; } return this.formatFeature( feat, attr.ID ); }, this); // need to format the attrs after doing the subfeatures, // because the subfeature formatting might have autocreated an // ID for the parent fields[8] = this._gff3_format_attributes( attr ); var fl = fields.join("\t")+"\n"; subfeatures.unshift( fl ); return subfeatures.join(''); }, /** * Write the feature to the GFF3 under construction. * @returns nothing */ writeFeature: function(feature) { var fmt = this.formatFeature(feature); this.print( fmt ); // avoid printing sync marks more than every 10 lines if( this.lastSync >= 9 ) { this.lastSync = 0; this.print( "###\n" ); } else { this.lastSync += fmt.length || 1; } }, /** * Extract a key-value object of gff3 attributes from the given * feature. Attribute names will have proper capitalization. * @private */ _gff3_attributes: function(feature) { var tags = array.filter( feature.tags(), dojo.hitch(this, function(f) { f = f.toLowerCase(); return this._is_not_gff3_tab_field(f) && f != 'subfeatures'; })); var attrs = {}; array.forEach( tags, function(tag) { var val = feature.get(tag); var valtype = typeof val; if( valtype == 'boolean' ) val = val ? 1 : 0; else if( valtype == 'undefined' ) return; tag = this._gff3_reserved_attribute(tag) || this._ensure_non_reserved( tag ); attrs[tag] = val; },this); return attrs; }, // ensure that an attribute name is not reserved. currently does // this by adding a leading underscore to attribute names that // have initial capital letters. _ensure_non_reserved: function( str ) { return str.replace(/^[A-Z]/,function() { return '_'+str[0]; }); }, /** * @private * @returns {String} formatted attribute string */ _gff3_format_attributes: function( attrs ) { var attrOrder = []; for( var tag in attrs ) { var val = attrs[tag]; if(!val) { continue; } var valstring = val.hasOwnProperty( 'toString' ) ? gff.util.escape( val.toString() ) : val instanceof Array ? array.map( val, s => gff.util.escape( s ) ).join(',') : val instanceof Object ? gff.util.escape(JSON.stringify(val)) : val.values ? val instanceof Array ? array.map( val, s => gff.util.escape( s ) ).join(',') : gff.util.escape( val ) : gff.util.escape(val) attrOrder.push( gff.util.escape( tag )+'='+valstring); } return attrOrder.join(';') || '.'; } }); });