3 import java.io.IOException;
4 import java.text.ParseException;
5 import java.util.ArrayList;
6 import java.util.Hashtable;
9 import java.util.Map.Entry;
11 import jalview.bin.Cache;
12 import jalview.datamodel.DBRefEntry;
13 import jalview.datamodel.DBRefSource;
14 import jalview.datamodel.FeatureProperties;
15 import jalview.datamodel.Sequence;
16 import jalview.datamodel.SequenceFeature;
17 import jalview.datamodel.SequenceI;
18 import jalview.util.DnaUtils;
19 import jalview.util.MappingUtils;
22 * A class that provides selective parsing of the EMBL flatfile format.
24 * The initial implementation is limited to extracting fields used by Jalview
25 * after fetching an EMBL or EMBLCDS entry:
28 * accession, version, sequence, xref
29 * and (for CDS feature) location, protein_id, product, codon_start, translation
32 * For a complete parser, it may be best to adopt that provided in
33 * https://github.com/enasequence/sequencetools/tree/master/src/main/java/uk/ac/ebi/embl/flatfile
34 * (but note this has a dependency on the Apache Commons library)
37 * @see ftp://ftp.ebi.ac.uk/pub/databases/ena/sequence/release/doc/usrman.txt
38 * @see ftp://ftp.ebi.ac.uk/pub/databases/embl/doc/FT_current.html
40 public class EmblFlatFile extends AlignFile // FileParse
42 private static final String WHITESPACE = "\\s+";
44 private String sourceDb;
47 * values parsed from the EMBL flatfile record
49 private String accession; // from ID (first token)
51 private String version; // from ID (second token)
53 private int length = 128; // from ID (7th token), with usable default
55 private List<DBRefEntry> dbrefs; // from DR and also CDS /db_xref qualifiers
57 private String sequenceString; // from SQ lines
59 private String translation; // from CDS feature /translation
61 private String cdsLocation; // CDS /location raw value
63 private int codonStart = 1; // from CDS /codon_start
65 private String proteinName; // from CDS /product
67 private String proteinId; // from CDS /protein_id
69 private Map<String, String> cdsProps; // CDS other qualifiers e.g. 'note'
77 public EmblFlatFile(FileParse fp, String sourceId) throws IOException
79 super(false, fp); // don't parse immediately
80 this.sourceDb = sourceId;
81 dbrefs = new ArrayList<>();
82 cdsProps = new Hashtable<>();
86 * Parses the flatfile, and if successful, saves as an annotated sequence
87 * which may be retrieved by calling {@code getSequence()}
91 public void parse() throws IOException
93 String line = nextLine();
96 if (line.startsWith("ID"))
98 line = processID(line);
100 else if (line.startsWith("DR"))
102 line = processDR(line);
104 else if (line.startsWith("SQ"))
108 else if (line.startsWith("FT"))
110 line = processFT(line);
121 * Extracts and saves the primary accession and version (SV value) from an ID
122 * line, or null if not found. Returns the next line after the one processed.
125 * @throws IOException
127 String processID(String line) throws IOException
129 String[] tokens = line.substring(2).split(";");
132 * first is primary accession
134 String token = tokens[0].trim();
135 if (!token.isEmpty())
137 this.accession = token;
141 * second token is 'SV versionNo'
143 if (tokens.length > 1)
145 token = tokens[1].trim();
146 if (token.startsWith("SV"))
148 String[] bits = token.trim().split(WHITESPACE);
149 this.version = bits[bits.length - 1];
154 * seventh token is 'length BP'
156 if (tokens.length > 6)
158 token = tokens[6].trim();
159 String[] bits = token.trim().split(WHITESPACE);
162 this.length = Integer.valueOf(bits[0]);
163 } catch (NumberFormatException e)
165 Cache.log.error("bad length read in flatfile, line: " + line);
173 * Processes one DR line and saves as a DBRefEntry cross-reference. Returns
174 * the line following the line processed.
177 * @throws IOException
179 String processDR(String line) throws IOException
181 String[] tokens = line.substring(2).split(";");
182 if (tokens.length > 1)
184 String db = tokens[0].trim();
185 String acc = tokens[1].trim();
186 if (acc.endsWith("."))
188 acc = acc.substring(0, acc.length() - 1);
190 this.dbrefs.add(new DBRefEntry(db, "0", acc));
197 * Reads and saves the sequence, read from the lines following the SQ line.
198 * Whitespace and position counters are discarded. Returns the next line
199 * following the sequence data (the next line that doesn't start with
202 * @throws IOException
204 String processSQ() throws IOException
206 StringBuilder sb = new StringBuilder(this.length);
207 String line = nextLine();
208 while (line != null && line.startsWith(" "))
211 String[] blocks = line.split(WHITESPACE);
214 * omit the last block (position counter) on each line
216 for (int i = 0; i < blocks.length - 1; i++)
218 sb.append(blocks[i]);
222 this.sequenceString = sb.toString();
228 * Processes an FT line. If it declares a feature type of interest (currently,
229 * only CDS is processed), processes all of the associated lines (feature
230 * qualifiers), and returns the next line after that, otherwise simply returns
235 * @throws IOException
237 String processFT(String line) throws IOException
239 String[] tokens = line.split(WHITESPACE);
240 if (tokens.length < 3 || !"CDS".equals(tokens[1]))
245 this.cdsLocation = tokens[2];
247 while ((line = nextLine()) != null)
249 if (!line.startsWith("FT ")) // 4 spaces
251 // e.g. start of next feature "FT source..."
256 * extract qualifier, e.g. FT /protein_id="CAA37824.1"
258 int slashPos = line.indexOf('/');
261 Cache.log.error("Unexpected EMBL line ignored: " + line);
264 int eqPos = line.indexOf('=', slashPos + 1);
267 Cache.log.error("Unexpected EMBL line ignored: " + line);
270 String qualifier = line.substring(slashPos + 1, eqPos);
271 String value = line.substring(eqPos + 1);
272 if (value.startsWith("\"") && value.endsWith("\""))
274 value = value.substring(1, value.length() - 1);
277 if ("protein_id".equals(qualifier))
281 else if ("codon_start".equals(qualifier))
285 codonStart = Integer.parseInt(value.trim());
286 } catch (NumberFormatException e)
288 Cache.log.error("Invalid codon_start in XML for " + this.accession
289 + ": " + e.getMessage());
292 else if ("product".equals(qualifier))
294 // sometimes name is returned e.g. for V00488
297 else if ("translation".equals(qualifier))
299 line = readTranslation(value);
301 else if (!"".equals(value))
303 // throw anything else into the additional properties hash
304 cdsProps.put(qualifier, value);
312 * Reads and saves the CDS translation from one or more lines of the file, and
313 * returns the next line after that
316 * the first line of the translation (likely quoted)
318 * @throws IOException
320 String readTranslation(String value) throws IOException
322 StringBuilder sb = new StringBuilder(this.length / 3 + 1);
323 sb.append(value.replace("\"", ""));
326 while ((line = nextLine()) != null)
328 if (!line.startsWith("FT "))
330 break; // reached next feature or other input line
332 String[] tokens = line.split(WHITESPACE);
333 if (tokens.length < 2)
335 Cache.log.error("Ignoring bad EMBL line: " + line);
338 if (tokens[1].startsWith("/"))
340 break; // next feature qualifier
342 sb.append(tokens[1].replace("\"", ""));
345 return sb.toString();
349 * Processes the parsed CDS feature data to
351 * <li>add a CDS feature to the sequence for each CDS start-end range</li>
352 * <li>create a protein product sequence for the translation</li>
353 * <li>create a cross-reference to protein with mapping from dna</li>
354 * <li>add any CDS dbrefs to the sequence and to the protein product</li>
356 * @param SequenceI dna
358 void processCDS(SequenceI dna)
361 * parse location into a list of [start, end, start, end] positions
363 int[] exons = getCdsRanges(this.accession, this.cdsLocation);
366 for (int xint = 0; exons != null
367 && xint < exons.length - 1; xint += 2)
369 int exonStart = exons[xint];
370 int exonEnd = exons[xint + 1];
371 int begin = Math.min(exonStart, exonEnd);
372 int end = Math.max(exonStart, exonEnd);
374 String desc = String.format("Exon %d for protein EMBLCDS:%s",
375 exonNumber, proteinId);
377 SequenceFeature sf = new SequenceFeature("CDS", desc, begin, end, this.sourceDb);
378 if (!cdsProps.isEmpty())
380 for (Entry<String, String> val : cdsProps.entrySet())
382 sf.setValue(val.getKey(), val.getValue());
386 sf.setEnaLocation(this.cdsLocation);
387 boolean forwardStrand = exonStart <= exonEnd;
388 sf.setStrand(forwardStrand ? "+" : "-");
389 sf.setPhase(String.valueOf(codonStart - 1));
390 sf.setValue(FeatureProperties.EXONPOS, exonNumber);
391 sf.setValue(FeatureProperties.EXONPRODUCT, proteinName);
393 dna.addSequenceFeature(sf);
398 * Constructs and saves the sequence from parsed components
400 void assembleSequence()
402 String name = this.accession;
403 if (this.sourceDb != null)
405 name = this.sourceDb + "|" + name;
407 SequenceI seq = new Sequence(name, this.sequenceString);
408 for (DBRefEntry dbref : this.dbrefs)
414 seq.deriveSequence();
420 * Output (print) is not implemented for EMBL flat file format
423 public String print(SequenceI[] seqs, boolean jvsuffix)
429 * Returns the CDS location as a single array of [start, end, start, end...]
430 * positions. If on the reverse strand, these will be in descending order.
436 protected int[] getCdsRanges(String accession, String location)
438 if (location == null)
445 List<int[]> ranges = DnaUtils.parseLocation(location);
446 return MappingUtils.listToArray(ranges);
447 } catch (ParseException e)
450 String.format("Not parsing inexact CDS location %s in ENA %s",
451 location, accession));