Completed
Pull Request — master (#47)
by Sam
01:28
created

Normaliser::appendComment()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 18
cts 18
cp 1
rs 8.0555
c 0
b 0
f 0
cc 9
nc 10
nop 0
crap 9
1
<?php
2
3
/*
4
 * This file is part of Badcow DNS Library.
5
 *
6
 * (c) Samuel Williams <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Badcow\DNS\Parser;
13
14
class Normaliser
15
{
16
    const COMMENTS_NONE = 0;
17
    const COMMENTS_END_OF_RECORD_ENTRY = 1;
18
    const COMMENTS_WITHIN_MULTILINE = 2;
19
    const COMMENTS_WITHOUT_RECORD_ENTRY = 4;
20
    const COMMENTS_ALL = 7;
21
22
    /**
23
     * @var StringIterator
24
     */
25
    private $string;
26
27
    /**
28
     * @var string
29
     */
30
    private $normalisedString = '';
31
32
    /**
33
     * @var int
34
     */
35
    private $commentOptions;
36
37
    /**
38
     * @var string
39
     */
40
    private $comment = '';
41
42
    /**
43
     * Comments that are within a multiline context, i.e. between brackets.
44
     *
45
     * @var string
46
     */
47
    private $multilineComments = '';
48
49
    /**
50
     * Normaliser constructor.
51
     *
52
     * @param string $zone
53
     * @param int    $commentOptions
54
     */
55 27
    public function __construct(string $zone, int $commentOptions = self::COMMENTS_NONE)
56
    {
57
        //Remove Windows line feeds and tabs
58 27
        $zone = str_replace([Tokens::CARRIAGE_RETURN, Tokens::TAB], ['', Tokens::SPACE], $zone);
59
60 27
        $this->string = new StringIterator($zone);
61 27
        $this->commentOptions = $commentOptions;
62 27
    }
63
64
    /**
65
     * @param string $zone
66
     * @param int    $includeComments
67
     *
68
     * @return string
69
     *
70
     * @throws ParseException
71
     */
72 27
    public static function normalise(string $zone, int $includeComments = self::COMMENTS_NONE): string
73
    {
74 27
        return (new self($zone, $includeComments))->process();
75
    }
76
77
    /**
78
     * @return string
79
     *
80
     * @throws ParseException
81
     */
82 27
    public function process(): string
83
    {
84 27
        while ($this->string->valid()) {
85 27
            $this->handleTxt();
86 27
            $this->handleMultiline();
87 27
            $this->handleComment(self::COMMENTS_END_OF_RECORD_ENTRY | self::COMMENTS_WITHOUT_RECORD_ENTRY);
88 27
            $this->append();
89
        }
90
91 24
        $this->removeWhitespace();
92
93 24
        return $this->normalisedString;
94
    }
95
96
    /**
97
     * Parses the comments.
98
     *
99
     * @param int $condition
100
     */
101 27
    private function handleComment($condition = self::COMMENTS_ALL): void
102
    {
103 27
        if ($this->string->isNot(Tokens::SEMICOLON)) {
104 27
            return;
105
        }
106
107 17
        $this->string->next();
108
109 17
        while ($this->string->isNot(Tokens::LINE_FEED) && $this->string->valid()) {
110 17
            if ($this->commentOptions & $condition) {
111 8
                if ($condition & self::COMMENTS_WITHIN_MULTILINE) {
112 4
                    $this->multilineComments .= $this->string->current();
113
                } else {
114 8
                    $this->comment .= $this->string->current();
115
                }
116
            }
117 17
            $this->string->next();
118
        }
119 17
    }
120
121
    /**
122
     * Handle text inside of double quotations. When this function is called, the String pointer MUST be at the
123
     * double quotation mark.
124
     *
125
     * @throws ParseException
126
     */
127 27
    private function handleTxt(): void
128
    {
129 27
        if ($this->string->isNot(Tokens::DOUBLE_QUOTES)) {
130 27
            return;
131
        }
132
133 16
        $this->append();
134
135 16
        while ($this->string->isNot(Tokens::DOUBLE_QUOTES)) {
136 16
            if (!$this->string->valid()) {
137 1
                throw new ParseException('Unbalanced double quotation marks. End of file reached.');
138
            }
139
140
            //If escape character
141 16
            if ($this->string->is(Tokens::BACKSLASH)) {
142 9
                $this->append();
143
            }
144
145 16
            if ($this->string->is(Tokens::LINE_FEED)) {
146 1
                throw new ParseException('Line Feed found within double quotation marks context.', $this->string);
147
            }
148
149 16
            $this->append();
150
        }
151 14
    }
152
153
    /**
154
     * Move multi-line records onto single line.
155
     *
156
     * @throws ParseException
157
     */
158 27
    private function handleMultiline(): void
159
    {
160 27
        if ($this->string->isNot(Tokens::OPEN_BRACKET)) {
161 27
            return;
162
        }
163
164 13
        $this->string->next();
165 13
        while ($this->string->valid()) {
166 13
            $this->handleTxt();
167 13
            $this->handleComment(self::COMMENTS_WITHIN_MULTILINE);
168
169 13
            if ($this->string->is(Tokens::LINE_FEED)) {
170 13
                $this->string->next();
171 13
                continue;
172
            }
173
174 13
            if ($this->string->is(Tokens::CLOSE_BRACKET)) {
175 12
                $this->string->next();
176
177 12
                $this->process();
178
179 12
                return;
180
            }
181
182 13
            $this->append();
183
        }
184
185 1
        throw new ParseException('End of file reached. Unclosed bracket.');
186
    }
187
188
    /**
189
     * Remove superfluous whitespace characters from string.
190
     *
191
     * @throws \UnexpectedValueException
192
     */
193 24
    private function removeWhitespace(): void
194
    {
195 24
        $string = preg_replace('/ {2,}/', Tokens::SPACE, $this->normalisedString);
196
197 24
        if (!is_string($string)) {
198
            throw new \UnexpectedValueException('Unexpected value returned from \preg_replace()/.');
199
        }
200
201 24
        $lines = [];
202
203 24
        foreach (explode(Tokens::LINE_FEED, $string) as $line) {
204 24
            if ('' !== $line = trim($line)) {
205 24
                $lines[] = $line;
206
            }
207
        }
208 24
        $this->normalisedString = implode(Tokens::LINE_FEED, $lines);
209 24
    }
210
211
    /**
212
     * Add current entry to normalisedString and moves iterator to next entry.
213
     */
214 27
    private function append(): void
215
    {
216 27
        if (($this->string->is(Tokens::LINE_FEED) || !$this->string->valid()) &&
217 27
            $this->commentOptions &&
218 27
            ('' !== $this->comment || '' !== $this->multilineComments)) {
219 8
            $this->appendComment();
220
        }
221
222 27
        $this->normalisedString .= $this->string->current();
223 27
        $this->string->next();
224 27
    }
225
226 8
    private function appendComment(): void
227
    {
228 8
        $zone = rtrim($this->normalisedString, ' ');
229
230
        //If there is no Resource Record on the line
231 8
        if ((Tokens::LINE_FEED === substr($zone, -1, 0) || 0 === strlen($zone))) {
232 1
            if ($this->commentOptions & self::COMMENTS_WITHOUT_RECORD_ENTRY) {
233 1
                $this->normalisedString = sprintf('%s;%s', $zone, trim($this->comment));
234
            }
235 1
            $this->comment = '';
236 1
            $this->multilineComments = '';
237
238 1
            return;
239
        }
240
241 8
        $comments = '';
242
243 8
        if ($this->commentOptions & self::COMMENTS_WITHIN_MULTILINE && '' !== $this->multilineComments) {
244 4
            $comments .= $this->multilineComments;
245
        }
246
247 8
        if ($this->commentOptions & self::COMMENTS_END_OF_RECORD_ENTRY && '' !== $this->comment) {
248 8
            $comments .= $this->comment;
249
        }
250
251 8
        if ('' !== $comments = trim($comments)) {
252 8
            $this->normalisedString = sprintf('%s;%s', $zone, $comments);
253
        }
254
255 8
        $this->comment = '';
256 8
        $this->multilineComments = '';
257 8
    }
258
}
259