Parser::skipToNextLevel()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
dl 0
loc 11
c 1
b 0
f 0
rs 10
cc 2
nc 2
nop 1
1
<?php
2
/**
3
 * php-gedcom.
4
 *
5
 * php-gedcom is a library for parsing, manipulating, importing and exporting
6
 * GEDCOM 5.5 files in PHP 5.3+.
7
 *
8
 * @author          Kristopher Wilson <[email protected]>
9
 * @copyright       Copyright (c) 2010-2013, Kristopher Wilson
10
 * @license         MIT
11
 *
12
 * @link            http://github.com/mrkrstphr/php-gedcom
13
 */
14
15
namespace Gedcom;
16
17
class Parser
18
{
19
    protected $_file;
20
21
    protected $_gedcom;
22
23
    protected $_errorLog = [];
24
25
    protected $_linesParsed = 0;
26
27
    protected $_line = '';
28
29
    protected $_lineRecord;
30
31
    protected $_linePieces = 0;
32
33
    protected $_returnedLine = '';
34
35
    public function __construct(Gedcom $gedcom = null)
36
    {
37
        if (!is_null($gedcom)) {
38
            $this->_gedcom = $gedcom;
39
        } else {
40
            $this->_gedcom = new Gedcom();
41
        }
42
    }
43
44
    public function forward()
45
    {
46
        // if there was a returned line by back(), set that as our current
47
        // line and blank out the returnedLine variable, otherwise grab
48
        // the next line from the file
49
50
        if (!empty($this->_returnedLine)) {
51
            $this->_line = $this->_returnedLine;
52
            $this->_returnedLine = '';
53
        } else {
54
            $this->_line = fgets($this->_file);
55
            $this->_lineRecord = null;
56
            $this->_linesParsed++;
57
        }
58
59
        return $this;
60
    }
61
62
    public function back()
63
    {
64
        // our parser object encountered a line it wasn't meant to parse
65
        // store this line for the previous parser to analyze
66
67
        $this->_returnedLine = $this->_line;
68
69
        return $this;
70
    }
71
72
    /**
73
     * Jump to the next level in the GEDCOM that is <= $level. This will leave the parser at the line above
74
     * this level, such that calling $parser->forward() will result in landing at the correct level.
75
     *
76
     * @param int $level
77
     */
78
    public function skipToNextLevel($level)
79
    {
80
        $currentDepth = 999;
81
82
        while ($currentDepth > $level) {
83
            $this->forward();
84
            $record = $this->getCurrentLineRecord();
85
            $currentDepth = (int) $record[0];
86
        }
87
88
        $this->back();
89
    }
90
91
    public function getGedcom()
92
    {
93
        return $this->_gedcom;
94
    }
95
96
    public function eof()
97
    {
98
        return feof($this->_file);
99
    }
100
101
    /**
102
     * @return string
103
     */
104
    public function parseMultiLineRecord()
105
    {
106
        $record = $this->getCurrentLineRecord();
107
108
        $depth = (int) $record[0];
109
        $data = isset($record[2]) ? trim($record[2]) : '';
110
111
        $this->forward();
112
113
        while (!$this->eof()) {
114
            $record = $this->getCurrentLineRecord();
115
            $recordType = strtoupper(trim($record[1]));
116
            $currentDepth = (int) $record[0];
117
118
            if ($currentDepth <= $depth) {
119
                $this->back();
120
                break;
121
            }
122
123
            switch ($recordType) {
124
            case 'CONT':
125
                $data .= "\n";
126
127
                if (isset($record[2])) {
128
                    $data .= trim($record[2]);
129
                }
130
                break;
131
            case 'CONC':
132
                if (isset($record[2])) {
133
                    $data .= ' '.trim($record[2]);
134
                }
135
                break;
136
            default:
137
                $this->back();
138
                break 2;
139
            }
140
141
            $this->forward();
142
        }
143
144
        return $data;
145
    }
146
147
    /**
148
     * @return string The current line
149
     */
150
    public function getCurrentLine()
151
    {
152
        return $this->_line;
153
    }
154
155
    public function getCurrentLineRecord($pieces = 3)
156
    {
157
        if (!is_null($this->_lineRecord)) {
158
            if ($this->_linePieces == $pieces) {
159
                return $this->_lineRecord;
160
            }
161
        }
162
163
        if (empty($this->_line)) {
164
            return false;
165
        }
166
167
        $line = trim($this->_line);
168
169
        $this->_lineRecord = explode(' ', $line, $pieces);
170
        $this->_linePieces = $pieces;
171
172
        return $this->_lineRecord;
173
    }
174
175
    protected function logError($error)
176
    {
177
        $this->_errorLog[] = $error;
178
    }
179
180
    public function logUnhandledRecord($additionalInfo = '')
181
    {
182
        $this->logError(
183
            $this->_linesParsed.': (Unhandled) '.trim(implode('|', $this->getCurrentLineRecord())).
0 ignored issues
show
Bug introduced by
It seems like $this->getCurrentLineRecord() can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

183
            $this->_linesParsed.': (Unhandled) '.trim(implode('|', /** @scrutinizer ignore-type */ $this->getCurrentLineRecord())).
Loading history...
184
            (!empty($additionalInfo) ? ' - '.$additionalInfo : '')
185
        );
186
    }
187
188
    public function logSkippedRecord($additionalInfo = '')
189
    {
190
        $this->logError(
191
            $this->_linesParsed.': (Skipping) '.trim(implode('|', $this->getCurrentLineRecord())).
0 ignored issues
show
Bug introduced by
It seems like $this->getCurrentLineRecord() can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

191
            $this->_linesParsed.': (Skipping) '.trim(implode('|', /** @scrutinizer ignore-type */ $this->getCurrentLineRecord())).
Loading history...
192
            (!empty($additionalInfo) ? ' - '.$additionalInfo : '')
193
        );
194
    }
195
196
    public function getErrors()
197
    {
198
        return $this->_errorLog;
199
    }
200
201
    public function normalizeIdentifier($identifier)
202
    {
203
        $identifier = trim($identifier);
204
205
        return trim($identifier, '@');
206
    }
207
208
    /**
209
     * @param string $fileName
210
     *
211
     * @return Gedcom
212
     */
213
    public function parse($fileName)
214
    {
215
        $this->_file = fopen($fileName, 'r'); //explode("\n", mb_convert_encoding($contents, 'UTF-8'));
216
217
        if (!$this->_file) {
218
            return null;
219
        }
220
221
        $this->forward();
222
223
        while (!$this->eof()) {
224
            $record = $this->getCurrentLineRecord();
225
226
            if ($record === false) {
227
                continue;
228
            }
229
230
            $depth = (int) $record[0];
231
232
            // We only process 0 level records here. Sub levels are processed
233
            // in methods for those data types (individuals, sources, etc)
234
235
            if ($depth == 0) {
236
                // Although not always an identifier (HEAD,TRLR):
237
                if (isset($record[1])) {
238
                    $this->normalizeIdentifier($record[1]);
239
                }
240
241
                if (isset($record[1]) && trim($record[1]) == 'HEAD') {
242
                    \Gedcom\Parser\Head::parse($this);
243
                } elseif (isset($record[2]) && trim($record[2]) == 'SUBN') {
244
                    \Gedcom\Parser\Subn::parse($this);
245
                } elseif (isset($record[2]) && trim($record[2]) == 'SUBM') {
246
                    \Gedcom\Parser\Subm::parse($this);
247
                } elseif (isset($record[2]) && $record[2] == 'SOUR') {
248
                    \Gedcom\Parser\Sour::parse($this);
249
                } elseif (isset($record[2]) && $record[2] == 'INDI') {
250
                    \Gedcom\Parser\Indi::parse($this);
251
                } elseif (isset($record[2]) && $record[2] == 'FAM') {
252
                    \Gedcom\Parser\Fam::parse($this);
253
                } elseif (isset($record[2]) && substr(trim($record[2]), 0, 4) == 'NOTE') {
254
                    \Gedcom\Parser\Note::parse($this);
255
                } elseif (isset($record[2]) && $record[2] == 'REPO') {
256
                    \Gedcom\Parser\Repo::parse($this);
257
                } elseif (isset($record[2]) && $record[2] == 'OBJE') {
258
                    \Gedcom\Parser\Obje::parse($this);
259
                } elseif (isset($record[1]) && trim($record[1]) == 'TRLR') {
260
                    // EOF
261
                    break;
262
                } else {
263
                    $this->logUnhandledRecord(self::class.' @ '.__LINE__);
264
                }
265
            } else {
266
                $this->logUnhandledRecord(self::class.' @ '.__LINE__);
267
            }
268
269
            $this->forward();
270
        }
271
272
        return $this->getGedcom();
273
    }
274
}
275