gff-nostream 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/api.ts CHANGED
@@ -43,15 +43,14 @@ export function parseStringSyncJBrowse(str: string): JBrowseFeature[] {
43
43
  function stringToRecords(str: string) {
44
44
  const lines = str.split(/\r?\n/)
45
45
  const records: LineRecord[] = []
46
- for (let i = 0; i < lines.length; i++) {
47
- const line = lines[i]!
48
- if (line.length === 0 || line[0] === '#') {
46
+ for (const line of lines) {
47
+ if (line.length === 0 || line.startsWith('#')) {
49
48
  if (line.startsWith('##FASTA')) {
50
49
  break
51
50
  }
52
51
  continue
53
52
  }
54
- if (line[0] === '>') {
53
+ if (line.startsWith('>')) {
55
54
  break
56
55
  }
57
56
  records.push({
@@ -76,8 +75,7 @@ export function parseRecords(records: LineRecord[]): GFF3Feature[] {
76
75
  const byId = new Map<string, GFF3Feature>()
77
76
  const orphans = new Map<string, GFF3Feature[]>()
78
77
 
79
- for (let i = 0; i < records.length; i++) {
80
- const record = records[i]!
78
+ for (const record of records) {
81
79
  const featureLine = (
82
80
  record.hasEscapes
83
81
  ? parseFeature(record.line)
@@ -87,9 +85,7 @@ export function parseRecords(records: LineRecord[]): GFF3Feature[] {
87
85
  featureLine.derived_features = []
88
86
 
89
87
  if (record.lineHash !== undefined) {
90
- if (!featureLine.attributes) {
91
- featureLine.attributes = {}
92
- }
88
+ featureLine.attributes ??= {}
93
89
  featureLine.attributes._lineHash = [String(record.lineHash)]
94
90
  }
95
91
 
@@ -104,7 +100,7 @@ export function parseRecords(records: LineRecord[]): GFF3Feature[] {
104
100
 
105
101
  let feature: GFF3Feature
106
102
  if (ids) {
107
- const id = ids[0]!
103
+ const id = ids[0]
108
104
  const existing = byId.get(id)
109
105
  if (existing) {
110
106
  existing.push(featureLine)
@@ -117,8 +113,8 @@ export function parseRecords(records: LineRecord[]): GFF3Feature[] {
117
113
  byId.set(id, feature)
118
114
  const waiting = orphans.get(id)
119
115
  if (waiting) {
120
- for (let j = 0; j < waiting.length; j++) {
121
- featureLine.child_features.push(waiting[j]!)
116
+ for (const w of waiting) {
117
+ featureLine.child_features.push(w)
122
118
  }
123
119
  orphans.delete(id)
124
120
  }
@@ -128,12 +124,11 @@ export function parseRecords(records: LineRecord[]): GFF3Feature[] {
128
124
  }
129
125
 
130
126
  if (parents) {
131
- for (let j = 0; j < parents.length; j++) {
132
- const parentId = parents[j]!
127
+ for (const parentId of parents) {
133
128
  const parent = byId.get(parentId)
134
129
  if (parent) {
135
- for (let k = 0; k < parent.length; k++) {
136
- parent[k]!.child_features.push(feature)
130
+ for (const p of parent) {
131
+ p.child_features.push(feature)
137
132
  }
138
133
  } else {
139
134
  let arr = orphans.get(parentId)
@@ -162,8 +157,7 @@ export function parseRecordsJBrowse(records: LineRecord[]): JBrowseFeature[] {
162
157
  const byId = new Map<string, JBrowseFeature>()
163
158
  const orphans = new Map<string, JBrowseFeature[]>()
164
159
 
165
- for (let i = 0; i < records.length; i++) {
166
- const record = records[i]!
160
+ for (const record of records) {
167
161
  const feature = record.hasEscapes
168
162
  ? parseFeatureJBrowse(record.line)
169
163
  : parseFeatureJBrowseNoUnescape(record.line)
@@ -189,8 +183,8 @@ export function parseRecordsJBrowse(records: LineRecord[]): JBrowseFeature[] {
189
183
  byId.set(id, feature)
190
184
  const waiting = orphans.get(id)
191
185
  if (waiting) {
192
- for (let j = 0; j < waiting.length; j++) {
193
- feature.subfeatures.push(waiting[j]!)
186
+ for (const w of waiting) {
187
+ feature.subfeatures.push(w)
194
188
  }
195
189
  orphans.delete(id)
196
190
  }
@@ -199,8 +193,7 @@ export function parseRecordsJBrowse(records: LineRecord[]): JBrowseFeature[] {
199
193
 
200
194
  if (parent) {
201
195
  const parents = Array.isArray(parent) ? parent : [parent]
202
- for (let j = 0; j < parents.length; j++) {
203
- const parentId = parents[j]!
196
+ for (const parentId of parents) {
204
197
  const parentFeature = byId.get(parentId)
205
198
  if (parentFeature) {
206
199
  parentFeature.subfeatures.push(feature)
package/src/util.ts CHANGED
@@ -1,17 +1,12 @@
1
1
  // Fast, low-level functions for parsing and formatting GFF3.
2
2
  // JavaScript port of Robert Buels's Bio::GFF3::LowLevel Perl module.
3
3
 
4
- const escapeRegex = /%([0-9A-Fa-f]{2})/g
5
4
  const directiveRegex = /^\s*##\s*(\S+)\s*(.*)/
6
5
  const lineEndRegex = /\r?\n$/
7
6
  const whitespaceRegex = /\s+/
8
7
  const nonDigitRegex = /\D/g
9
- // eslint-disable-next-line no-control-regex
10
- const attrEscapeRegex = /[\n;\r\t=%&,\u0000-\u001f\u007f-\u00ff]/g
11
- // eslint-disable-next-line no-control-regex
12
- const columnEscapeRegex = /[\n\r\t%\u0000-\u001f\u007f-\u00ff]/g
13
8
 
14
- const HEX_LOOKUP: Record<string, string> = {}
9
+ const HEX_LOOKUP: Record<string, string | undefined> = {}
15
10
  for (let i = 0; i < 256; i++) {
16
11
  const hex = i.toString(16).toUpperCase().padStart(2, '0')
17
12
  HEX_LOOKUP[hex] = String.fromCharCode(i)
@@ -55,13 +50,10 @@ export function unescape(stringVal: string) {
55
50
  return result + stringVal.slice(lastIdx)
56
51
  }
57
52
 
58
- /**
59
- * Parse the 9th column (attributes) of a GFF3 feature line.
60
- *
61
- * @param attrString - String of GFF3 9th column
62
- * @returns Parsed attributes
63
- */
64
- export function parseAttributes(attrString: string): GFF3Attributes {
53
+ function parseAttributesImpl(
54
+ attrString: string,
55
+ shouldUnescape: boolean,
56
+ ): GFF3Attributes {
65
57
  if (attrString.length === 0 || attrString === '.') {
66
58
  return {}
67
59
  }
@@ -99,7 +91,7 @@ export function parseAttributes(attrString: string): GFF3Attributes {
99
91
  }
100
92
  if (commaIdx > valStart) {
101
93
  const val = attrString.slice(valStart, commaIdx)
102
- arec.push(unescape(val))
94
+ arec.push(shouldUnescape ? unescape(val) : val)
103
95
  }
104
96
  valStart = commaIdx + 1
105
97
  }
@@ -110,6 +102,16 @@ export function parseAttributes(attrString: string): GFF3Attributes {
110
102
  return attrs
111
103
  }
112
104
 
105
+ /**
106
+ * Parse the 9th column (attributes) of a GFF3 feature line.
107
+ *
108
+ * @param attrString - String of GFF3 9th column
109
+ * @returns Parsed attributes
110
+ */
111
+ export function parseAttributes(attrString: string): GFF3Attributes {
112
+ return parseAttributesImpl(attrString, true)
113
+ }
114
+
113
115
  /**
114
116
  * Parse the 9th column (attributes) of a GFF3 feature line without unescaping.
115
117
  * Fast path for data known to contain no escaped characters.
@@ -118,59 +120,42 @@ export function parseAttributes(attrString: string): GFF3Attributes {
118
120
  * @returns Parsed attributes
119
121
  */
120
122
  export function parseAttributesNoUnescape(attrString: string): GFF3Attributes {
121
- if (attrString.length === 0 || attrString === '.') {
122
- return {}
123
- }
124
-
125
- const attrs: GFF3Attributes = {}
126
- let len = attrString.length
127
-
128
- if (attrString[len - 1] === '\n') {
129
- len = attrString[len - 2] === '\r' ? len - 2 : len - 1
130
- attrString = attrString.slice(0, len)
131
- }
132
-
133
- let start = 0
134
- while (start < len) {
135
- let semiIdx = attrString.indexOf(';', start)
136
- if (semiIdx === -1) {
137
- semiIdx = len
138
- }
139
-
140
- if (semiIdx > start) {
141
- const eqIdx = attrString.indexOf('=', start)
142
- if (eqIdx !== -1 && eqIdx < semiIdx && eqIdx + 1 < semiIdx) {
143
- const tag = attrString.slice(start, eqIdx)
144
- let arec = attrs[tag]
145
- if (!arec) {
146
- arec = []
147
- attrs[tag] = arec
148
- }
123
+ return parseAttributesImpl(attrString, false)
124
+ }
149
125
 
150
- let valStart = eqIdx + 1
151
- while (valStart < semiIdx) {
152
- let commaIdx = attrString.indexOf(',', valStart)
153
- if (commaIdx === -1 || commaIdx > semiIdx) {
154
- commaIdx = semiIdx
155
- }
156
- if (commaIdx > valStart) {
157
- arec.push(attrString.slice(valStart, commaIdx))
158
- }
159
- valStart = commaIdx + 1
160
- }
161
- }
162
- }
163
- start = semiIdx + 1
126
+ function normImpl(s: string, shouldUnescape: boolean) {
127
+ if (s.length === 0 || s === '.') {
128
+ return null
164
129
  }
165
- return attrs
130
+ return shouldUnescape ? unescape(s) : s
166
131
  }
167
132
 
168
- function normUnescape(s: string) {
169
- return s.length === 0 || s === '.' ? null : unescape(s)
170
- }
133
+ function parseFeatureImpl(
134
+ line: string,
135
+ shouldUnescape: boolean,
136
+ ): GFF3FeatureLine {
137
+ const f = line.split('\t')
138
+ const startStr = f[3]
139
+ const endStr = f[4]
140
+ const scoreStr = f[5]
141
+ const strand = f[6]
142
+ const phase = f[7]
143
+ const attrString = f[8]
171
144
 
172
- function norm(s: string) {
173
- return s.length === 0 || s === '.' ? null : s
145
+ return {
146
+ seq_id: normImpl(f[0], shouldUnescape),
147
+ source: normImpl(f[1], shouldUnescape),
148
+ type: normImpl(f[2], shouldUnescape),
149
+ start: startStr.length === 0 || startStr === '.' ? null : +startStr,
150
+ end: endStr.length === 0 || endStr === '.' ? null : +endStr,
151
+ score: scoreStr.length === 0 || scoreStr === '.' ? null : +scoreStr,
152
+ strand: normImpl(strand, false),
153
+ phase: normImpl(phase, false),
154
+ attributes:
155
+ attrString.length === 0 || attrString === '.'
156
+ ? null
157
+ : parseAttributesImpl(attrString, shouldUnescape),
158
+ }
174
159
  }
175
160
 
176
161
  /**
@@ -180,28 +165,7 @@ function norm(s: string) {
180
165
  * @returns The parsed feature
181
166
  */
182
167
  export function parseFeature(line: string): GFF3FeatureLine {
183
- const f = line.split('\t')
184
- const seq_id = f[0]!
185
- const source = f[1]!
186
- const type = f[2]!
187
- const startStr = f[3]!
188
- const endStr = f[4]!
189
- const scoreStr = f[5]!
190
- const strand = f[6]!
191
- const phase = f[7]!
192
- const attrString = f[8]!
193
-
194
- return {
195
- seq_id: normUnescape(seq_id),
196
- source: normUnescape(source),
197
- type: normUnescape(type),
198
- start: startStr.length === 0 || startStr === '.' ? null : +startStr,
199
- end: endStr.length === 0 || endStr === '.' ? null : +endStr,
200
- score: scoreStr.length === 0 || scoreStr === '.' ? null : +scoreStr,
201
- strand: norm(strand),
202
- phase: norm(phase),
203
- attributes: attrString.length === 0 || attrString === '.' ? null : parseAttributes(attrString),
204
- }
168
+ return parseFeatureImpl(line, true)
205
169
  }
206
170
 
207
171
  /**
@@ -212,38 +176,12 @@ export function parseFeature(line: string): GFF3FeatureLine {
212
176
  * @returns The parsed feature
213
177
  */
214
178
  export function parseFeatureNoUnescape(line: string): GFF3FeatureLine {
215
- const f = line.split('\t')
216
- const seq_id = f[0]!
217
- const source = f[1]!
218
- const type = f[2]!
219
- const startStr = f[3]!
220
- const endStr = f[4]!
221
- const scoreStr = f[5]!
222
- const strand = f[6]!
223
- const phase = f[7]!
224
- const attrString = f[8]!
225
-
226
- return {
227
- seq_id: norm(seq_id),
228
- source: norm(source),
229
- type: norm(type),
230
- start: startStr.length === 0 || startStr === '.' ? null : +startStr,
231
- end: endStr.length === 0 || endStr === '.' ? null : +endStr,
232
- score: scoreStr.length === 0 || scoreStr === '.' ? null : +scoreStr,
233
- strand: norm(strand),
234
- phase: norm(phase),
235
- attributes: attrString.length === 0 || attrString === '.' ? null : parseAttributesNoUnescape(attrString),
236
- }
179
+ return parseFeatureImpl(line, false)
237
180
  }
238
181
 
239
- /**
240
- * Parse a GFF3 feature from a pre-split fields array
241
- *
242
- * @param f - Array of 9 GFF3 column values (use null or '.' for empty values)
243
- * @returns The parsed feature
244
- */
245
- export function parseFieldsArray(
182
+ function parseFieldsArrayImpl(
246
183
  f: (string | null | undefined)[],
184
+ shouldUnescape: boolean,
247
185
  ): GFF3FeatureLine {
248
186
  const seq_id = f[0]
249
187
  const source = f[1]
@@ -256,18 +194,33 @@ export function parseFieldsArray(
256
194
  const attrString = f[8]
257
195
 
258
196
  return {
259
- seq_id: seq_id ? normUnescape(seq_id) : null,
260
- source: source ? normUnescape(source) : null,
261
- type: type ? normUnescape(type) : null,
197
+ seq_id: seq_id ? normImpl(seq_id, shouldUnescape) : null,
198
+ source: source ? normImpl(source, shouldUnescape) : null,
199
+ type: type ? normImpl(type, shouldUnescape) : null,
262
200
  start: !startStr || startStr === '.' ? null : +startStr,
263
201
  end: !endStr || endStr === '.' ? null : +endStr,
264
202
  score: !scoreStr || scoreStr === '.' ? null : +scoreStr,
265
203
  strand: strand && strand !== '.' ? strand : null,
266
204
  phase: phase && phase !== '.' ? phase : null,
267
- attributes: !attrString || attrString === '.' ? null : parseAttributes(attrString),
205
+ attributes:
206
+ !attrString || attrString === '.'
207
+ ? null
208
+ : parseAttributesImpl(attrString, shouldUnescape),
268
209
  }
269
210
  }
270
211
 
212
+ /**
213
+ * Parse a GFF3 feature from a pre-split fields array
214
+ *
215
+ * @param f - Array of 9 GFF3 column values (use null or '.' for empty values)
216
+ * @returns The parsed feature
217
+ */
218
+ export function parseFieldsArray(
219
+ f: (string | null | undefined)[],
220
+ ): GFF3FeatureLine {
221
+ return parseFieldsArrayImpl(f, true)
222
+ }
223
+
271
224
  /**
272
225
  * Parse a GFF3 feature from a pre-split fields array without unescaping.
273
226
  * Fast path for data known to contain no escaped characters.
@@ -278,27 +231,7 @@ export function parseFieldsArray(
278
231
  export function parseFieldsArrayNoUnescape(
279
232
  f: (string | null | undefined)[],
280
233
  ): GFF3FeatureLine {
281
- const seq_id = f[0]
282
- const source = f[1]
283
- const type = f[2]
284
- const startStr = f[3]
285
- const endStr = f[4]
286
- const scoreStr = f[5]
287
- const strand = f[6]
288
- const phase = f[7]
289
- const attrString = f[8]
290
-
291
- return {
292
- seq_id: seq_id && seq_id !== '.' ? seq_id : null,
293
- source: source && source !== '.' ? source : null,
294
- type: type && type !== '.' ? type : null,
295
- start: !startStr || startStr === '.' ? null : +startStr,
296
- end: !endStr || endStr === '.' ? null : +endStr,
297
- score: !scoreStr || scoreStr === '.' ? null : +scoreStr,
298
- strand: strand && strand !== '.' ? strand : null,
299
- phase: phase && phase !== '.' ? phase : null,
300
- attributes: !attrString || attrString === '.' ? null : parseAttributesNoUnescape(attrString),
301
- }
234
+ return parseFieldsArrayImpl(f, false)
302
235
  }
303
236
 
304
237
  /**
@@ -323,27 +256,26 @@ export function parseDirective(
323
256
  let [, , contents] = match
324
257
 
325
258
  const parsed: GFF3Directive = { directive: name }
326
- if (contents!.length) {
327
- contents = contents!.replace(lineEndRegex, '')
259
+ if (contents.length) {
260
+ contents = contents.replace(lineEndRegex, '')
328
261
  parsed.value = contents
329
262
  }
330
263
 
331
- // do a little additional parsing for sequence-region and genome-build directives
332
264
  if (name === 'sequence-region') {
333
- const c = contents!.split(whitespaceRegex, 3)
265
+ const c = contents.split(whitespaceRegex, 3)
334
266
  return {
335
267
  ...parsed,
336
268
  seq_id: c[0],
337
269
  start: c[1]?.replaceAll(nonDigitRegex, ''),
338
270
  end: c[2]?.replaceAll(nonDigitRegex, ''),
339
- } as GFF3SequenceRegionDirective
271
+ }
340
272
  } else if (name === 'genome-build') {
341
- const [source, buildName] = contents!.split(whitespaceRegex, 2)
273
+ const [source, buildName] = contents.split(whitespaceRegex, 2)
342
274
  return {
343
275
  ...parsed,
344
276
  source,
345
277
  buildName,
346
- } as GFF3GenomeBuildDirective
278
+ }
347
279
  }
348
280
 
349
281
  return parsed
@@ -451,6 +383,30 @@ const JBROWSE_DEFAULT_FIELDS = new Set([
451
383
  'strand',
452
384
  ])
453
385
 
386
+ // Pre-computed lowercase for common GFF3 spec attribute names to avoid
387
+ // toLowerCase() calls in the hot path
388
+ const COMMON_ATTRS: Record<string, string | undefined> = {
389
+ ID: 'id',
390
+ Name: 'name',
391
+ Parent: 'parent',
392
+ Note: 'note',
393
+ Dbxref: 'dbxref',
394
+ Ontology_term: 'ontology_term',
395
+ Is_circular: 'is_circular',
396
+ Alias: 'alias',
397
+ Target: 'target',
398
+ Gap: 'gap',
399
+ Derives_from: 'derives_from',
400
+ id: 'id',
401
+ name: 'name',
402
+ parent: 'parent',
403
+ note: 'note',
404
+ dbxref: 'dbxref',
405
+ alias: 'alias',
406
+ target: 'target',
407
+ gap: 'gap',
408
+ }
409
+
454
410
  export interface JBrowseFeature {
455
411
  start: number
456
412
  end: number
@@ -464,22 +420,10 @@ export interface JBrowseFeature {
464
420
  [key: string]: unknown
465
421
  }
466
422
 
467
- function parseStrand(s: string) {
468
- if (s === '+') {
469
- return 1
470
- }
471
- if (s === '-') {
472
- return -1
473
- }
474
- if (s === '.') {
475
- return 0
476
- }
477
- return undefined
478
- }
479
-
480
- export function parseAttributesJBrowse(
423
+ function parseAttributesJBrowseImpl(
481
424
  attrString: string,
482
425
  result: Record<string, unknown>,
426
+ shouldUnescape: boolean,
483
427
  ) {
484
428
  if (attrString.length === 0 || attrString === '.') {
485
429
  return
@@ -507,9 +451,12 @@ export function parseAttributesJBrowse(
507
451
  continue
508
452
  }
509
453
 
510
- let key = tag.toLowerCase()
511
- if (JBROWSE_DEFAULT_FIELDS.has(key)) {
512
- key += '2'
454
+ let key = COMMON_ATTRS[tag]
455
+ if (key === undefined) {
456
+ key = tag.toLowerCase()
457
+ if (JBROWSE_DEFAULT_FIELDS.has(key)) {
458
+ key += '2'
459
+ }
513
460
  }
514
461
 
515
462
  const values: string[] = []
@@ -521,7 +468,7 @@ export function parseAttributesJBrowse(
521
468
  }
522
469
  if (commaIdx > valStart) {
523
470
  const val = attrString.slice(valStart, commaIdx)
524
- values.push(unescape(val))
471
+ values.push(shouldUnescape ? unescape(val) : val)
525
472
  }
526
473
  valStart = commaIdx + 1
527
474
  }
@@ -533,113 +480,71 @@ export function parseAttributesJBrowse(
533
480
  }
534
481
  }
535
482
 
536
- export function parseAttributesJBrowseNoUnescape(
483
+ export function parseAttributesJBrowse(
537
484
  attrString: string,
538
485
  result: Record<string, unknown>,
539
486
  ) {
540
- if (attrString.length === 0 || attrString === '.') {
541
- return
542
- }
543
-
544
- let len = attrString.length
545
- if (attrString[len - 1] === '\n') {
546
- len = attrString[len - 2] === '\r' ? len - 2 : len - 1
547
- attrString = attrString.slice(0, len)
548
- }
549
-
550
- let start = 0
551
- while (start < len) {
552
- let semiIdx = attrString.indexOf(';', start)
553
- if (semiIdx === -1) {
554
- semiIdx = len
555
- }
556
-
557
- if (semiIdx > start) {
558
- const eqIdx = attrString.indexOf('=', start)
559
- if (eqIdx !== -1 && eqIdx < semiIdx && eqIdx + 1 < semiIdx) {
560
- const tag = attrString.slice(start, eqIdx)
561
- if (tag === '_lineHash') {
562
- start = semiIdx + 1
563
- continue
564
- }
565
-
566
- let key = tag.toLowerCase()
567
- if (JBROWSE_DEFAULT_FIELDS.has(key)) {
568
- key += '2'
569
- }
570
-
571
- const values: string[] = []
572
- let valStart = eqIdx + 1
573
- while (valStart < semiIdx) {
574
- let commaIdx = attrString.indexOf(',', valStart)
575
- if (commaIdx === -1 || commaIdx > semiIdx) {
576
- commaIdx = semiIdx
577
- }
578
- if (commaIdx > valStart) {
579
- values.push(attrString.slice(valStart, commaIdx))
580
- }
581
- valStart = commaIdx + 1
582
- }
487
+ parseAttributesJBrowseImpl(attrString, result, true)
488
+ }
583
489
 
584
- result[key] = values.length === 1 ? values[0] : values
585
- }
586
- }
587
- start = semiIdx + 1
588
- }
490
+ export function parseAttributesJBrowseNoUnescape(
491
+ attrString: string,
492
+ result: Record<string, unknown>,
493
+ ) {
494
+ parseAttributesJBrowseImpl(attrString, result, false)
589
495
  }
590
496
 
591
- export function parseFeatureJBrowse(line: string): JBrowseFeature {
497
+ function parseFeatureJBrowseImpl(
498
+ line: string,
499
+ shouldUnescape: boolean,
500
+ ): JBrowseFeature {
592
501
  const f = line.split('\t')
593
- const seq_id = f[0]!
594
- const source = f[1]!
595
- const type = f[2]!
596
- const startStr = f[3]!
597
- const endStr = f[4]!
598
- const scoreStr = f[5]!
599
- const strand = f[6]!
600
- const phase = f[7]!
601
- const attrString = f[8]!
502
+ const seq_id = f[0]
503
+ const source = f[1]
504
+ const type = f[2]
505
+ const startStr = f[3]
506
+ const endStr = f[4]
507
+ const scoreStr = f[5]
508
+ const strand = f[6]
509
+ const phase = f[7]
510
+ const attrString = f[8]
602
511
 
603
512
  const result: JBrowseFeature = {
604
- refName: seq_id.length === 0 || seq_id === '.' ? '' : unescape(seq_id),
605
- source: source.length === 0 || source === '.' ? null : unescape(source),
606
- type: type.length === 0 || type === '.' ? null : unescape(type),
513
+ refName:
514
+ seq_id.length === 0 || seq_id === '.'
515
+ ? ''
516
+ : shouldUnescape
517
+ ? unescape(seq_id)
518
+ : seq_id,
519
+ source:
520
+ source.length === 0 || source === '.'
521
+ ? null
522
+ : shouldUnescape
523
+ ? unescape(source)
524
+ : source,
525
+ type:
526
+ type.length === 0 || type === '.'
527
+ ? null
528
+ : shouldUnescape
529
+ ? unescape(type)
530
+ : type,
607
531
  start: startStr.length === 0 || startStr === '.' ? 0 : +startStr - 1,
608
532
  end: endStr.length === 0 || endStr === '.' ? 0 : +endStr,
609
533
  score: scoreStr.length === 0 || scoreStr === '.' ? undefined : +scoreStr,
610
- strand: parseStrand(strand),
534
+ strand:
535
+ strand === '+' ? 1 : strand === '-' ? -1 : strand === '.' ? 0 : undefined,
611
536
  phase: phase.length === 0 || phase === '.' ? undefined : +phase,
612
537
  subfeatures: [],
613
538
  }
614
539
 
615
- parseAttributesJBrowse(attrString, result)
540
+ parseAttributesJBrowseImpl(attrString, result, shouldUnescape)
616
541
  return result
617
542
  }
618
543
 
619
- export function parseFeatureJBrowseNoUnescape(line: string): JBrowseFeature {
620
- const f = line.split('\t')
621
- const seq_id = f[0]!
622
- const source = f[1]!
623
- const type = f[2]!
624
- const startStr = f[3]!
625
- const endStr = f[4]!
626
- const scoreStr = f[5]!
627
- const strand = f[6]!
628
- const phase = f[7]!
629
- const attrString = f[8]!
630
-
631
- const result: JBrowseFeature = {
632
- refName: seq_id.length === 0 || seq_id === '.' ? '' : seq_id,
633
- source: source.length === 0 || source === '.' ? null : source,
634
- type: type.length === 0 || type === '.' ? null : type,
635
- start: startStr.length === 0 || startStr === '.' ? 0 : +startStr - 1,
636
- end: endStr.length === 0 || endStr === '.' ? 0 : +endStr,
637
- score: scoreStr.length === 0 || scoreStr === '.' ? undefined : +scoreStr,
638
- strand: parseStrand(strand),
639
- phase: phase.length === 0 || phase === '.' ? undefined : +phase,
640
- subfeatures: [],
641
- }
544
+ export function parseFeatureJBrowse(line: string): JBrowseFeature {
545
+ return parseFeatureJBrowseImpl(line, true)
546
+ }
642
547
 
643
- parseAttributesJBrowseNoUnescape(attrString, result)
644
- return result
548
+ export function parseFeatureJBrowseNoUnescape(line: string): JBrowseFeature {
549
+ return parseFeatureJBrowseImpl(line, false)
645
550
  }