Passed
Push — master ( 1b54b9...ff455f )
by Sam
10:10 queued 11s
created

Normaliser::removeWhitespace()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

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