Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
72.73% covered (warning)
72.73%
8 / 11
CRAP
94.90% covered (success)
94.90%
93 / 98
MgfReader
0.00% covered (danger)
0.00%
0 / 1
72.73% covered (warning)
72.73%
8 / 11
35.16
94.90% covered (success)
94.90%
93 / 98
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 current
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 key
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 next
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 rewind
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
 valid
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 getLine
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 peekLine
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 parseEntry
0.00% covered (danger)
0.00%
0 / 1
9
96.43% covered (success)
96.43%
27 / 28
 parseMeta
0.00% covered (danger)
0.00%
0 / 1
9.12
88.46% covered (warning)
88.46%
23 / 26
 parseFragments
0.00% covered (danger)
0.00%
0 / 1
4.00
93.75% covered (success)
93.75%
15 / 16
<?php
/**
 * Copyright 2018 University of Liverpool
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
namespace pgb_liv\php_ms\Reader;
use pgb_liv\php_ms\Core\Spectra\PrecursorIon;
use pgb_liv\php_ms\Core\Spectra\FragmentIon;
/**
 * An MGF reader that creates a new iterable object that will return a raw
 * entry on each iteration.
 *
 * @author Andrew Collins
 */
class MgfReader implements \Iterator
{
    private $filePath;
    private $fileHandle;
    private $filePeek;
    private $current;
    private $key = 0;
    private $massCharge;
    private $charge;
    public function __construct($filePath)
    {
        $this->filePath = $filePath;
    }
    /**
     *
     * {@inheritdoc}
     * @return PrecursorIon
     */
    public function current()
    {
        return $this->current;
    }
    /**
     *
     * {@inheritdoc}
     * @return int
     */
    public function key()
    {
        return $this->key;
    }
    /**
     *
     * {@inheritdoc}
     */
    public function next()
    {
        $this->current = null;
        if (! feof($this->fileHandle)) {
            $this->current = $this->parseEntry();
        }
    }
    /**
     *
     * {@inheritdoc}
     */
    public function rewind()
    {
        // Reset file parsing to start
        if ($this->fileHandle != null) {
            fclose($this->fileHandle);
        }
        $this->fileHandle = fopen($this->filePath, 'r');
        $this->key = 0;
        $this->current = $this->parseEntry();
    }
    /**
     *
     * {@inheritdoc}
     * @return bool
     */
    public function valid()
    {
        if ($this->current instanceof PrecursorIon) {
            return true;
        }
        return false;
    }
    /**
     * Gets the next line and increments the file iterator
     *
     * @return string The next line in the file
     */
    private function getLine()
    {
        if ($this->filePeek == null) {
            return fgets($this->fileHandle);
        }
        $ret = $this->filePeek;
        $this->filePeek = null;
        return $ret;
    }
    /**
     * Gets the next line, though does not move the file iterator
     *
     * @return string The next line in the file
     */
    private function peekLine()
    {
        if ($this->filePeek == null) {
            $this->filePeek = fgets($this->fileHandle);
        }
        return $this->filePeek;
    }
    private function parseEntry()
    {
        $entry = new PrecursorIon();
        // Scan to BEGIN IONS
        $isFound = false;
        while ($line = $this->getLine()) {
            $line = trim($line);
            if (strpos($line, 'BEGIN IONS') !== 0) {
                continue;
            }
            $isFound = true;
            break;
        }
        if (! $isFound) {
            return null;
        }
        $this->massCharge = null;
        $this->charge = 1;
        // Scan for key=value pairs
        while ($line = $this->peekLine()) {
            if (strpos($line, '=') === false) {
                break;
            }
            $this->parseMeta($entry);
        }
        // TODO: Better support required for charge-less data
        if (! is_null($this->massCharge)) {
            $entry->setMonoisotopicMassCharge($this->massCharge, $this->charge);
        } else {
            $entry->setCharge($this->charge);
        }
        // Scan for [m/z] [intensity] [charge]
        while ($line = $this->peekLine()) {
            if (strpos($line, 'END IONS') !== false) {
                break;
            }
            $this->parseFragments($entry);
        }
        $this->key ++;
        return $entry;
    }
    /**
     * Parses the meta information from the scan and writes it to the precursor entry
     *
     * @param PrecursorIon $precursor
     *            The precursor to append to
     */
    private function parseMeta(PrecursorIon $precursor)
    {
        $line = trim($this->getLine());
        $pair = explode('=', $line, 2);
        $value = $pair[1];
        if (is_numeric($value)) {
            $value += 0;
        }
        if ($pair[0] == 'TITLE') {
            $precursor->setTitle($pair[1]);
        } elseif ($pair[0] == 'PEPMASS') {
            $chunks = explode(' ', $pair[1]);
            if (count($chunks) > 1) {
                $precursor->setIntensity((float) $chunks[1] + 0);
            }
            $this->massCharge = (float) $chunks[0] + 0;
        } elseif ($pair[0] == 'CHARGE') {
            $this->charge = (int) $pair[1];
        } elseif ($pair[0] == 'SCANS') {
            $precursor->setScan((int) $pair[1] + 0);
        } elseif ($pair[0] == 'RTINSECONDS') {
            $window = explode(',', $pair[1]);
            if (count($window) == 1) {
                $precursor->setRetentionTime((float) $window[0] + 0);
            } else {
                $precursor->setRetentionTimeWindow((float) $window[0] + 0, (float) $window[1] + 0);
            }
        }
    }
    /**
     * Parses the fragment information from the scan and writes it to the precursor entry
     *
     * @param PrecursorIon $precursor
     *            The precursor to append to
     */
    private function parseFragments(PrecursorIon $precursor)
    {
        $line = trim($this->getLine());
        if (strlen($line) == 0) {
            return;
        }
        $pair = preg_split('/\\s/', $line, 3);
        $ion = new FragmentIon();
        $fragmentMz = (float) $pair[0];
        $fragmentCharge = 1;
        if (count($pair) > 1) {
            $ion->setIntensity((float) $pair[1]);
        }
        if (count($pair) > 2) {
            $fragmentCharge = (int) $pair[2];
        }
        $ion->setMonoisotopicMassCharge($fragmentMz, $fragmentCharge);
        $precursor->addFragmentIon($ion);
    }
}