Passed
Branch master (8940db)
by Sam
02:38
created

Normaliser::handleTxt()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 11
c 1
b 0
f 0
nc 7
nop 0
dl 0
loc 23
ccs 12
cts 12
cp 1
crap 6
rs 9.2222
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 30
    public function __construct(string $zone, int $commentOptions = Comments::NONE)
52
    {
53
        //Remove Windows line feeds and tabs
54 30
        $zone = str_replace([Tokens::CARRIAGE_RETURN, Tokens::TAB], ['', Tokens::SPACE], $zone);
55
56 30
        $this->string = new StringIterator($zone);
57 30
        $this->commentOptions = $commentOptions;
58 30
    }
59
60
    /**
61
     * @param string $zone
62
     * @param int    $includeComments
63
     *
64
     * @return string
65
     *
66
     * @throws ParseException
67
     */
68 30
    public static function normalise(string $zone, int $includeComments = Comments::NONE): string
69
    {
70 30
        return (new self($zone, $includeComments))->process();
71
    }
72
73
    /**
74
     * @return string
75
     *
76
     * @throws ParseException
77
     */
78 30
    public function process(): string
79
    {
80 30
        while ($this->string->valid()) {
81 30
            $this->handleTxt();
82 30
            $this->handleMultiline();
83 30
            $this->handleComment(Comments::END_OF_ENTRY | Comments::ORPHAN);
84 30
            $this->append();
85
        }
86
87 27
        $this->removeWhitespace();
88
89 27
        return $this->normalisedString;
90
    }
91
92
    /**
93
     * Parses the comments.
94
     *
95
     * @param int $condition
96
     */
97 30
    private function handleComment($condition = Comments::ALL): void
98
    {
99 30
        if ($this->string->isNot(Tokens::SEMICOLON)) {
100 30
            return;
101
        }
102
103 17
        $this->string->next();
104
105 17
        while ($this->string->isNot(Tokens::LINE_FEED) && $this->string->valid()) {
106 17
            if ($this->commentOptions & $condition) {
107 8
                if ($condition & Comments::MULTILINE) {
108 4
                    $this->multilineComments .= $this->string->current();
109
                } else {
110 8
                    $this->comment .= $this->string->current();
111
                }
112
            }
113 17
            $this->string->next();
114
        }
115 17
    }
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 30
    private function handleTxt(): void
124
    {
125 30
        if ($this->string->isNot(Tokens::DOUBLE_QUOTES)) {
126 30
            return;
127
        }
128
129 17
        $this->append();
130
131 17
        while ($this->string->isNot(Tokens::DOUBLE_QUOTES)) {
132 17
            if (!$this->string->valid()) {
133 1
                throw new ParseException('Unbalanced double quotation marks. End of file reached.');
134
            }
135
136
            //If escape character
137 17
            if ($this->string->is(Tokens::BACKSLASH)) {
138 9
                $this->append();
139
            }
140
141 17
            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 17
            $this->append();
146
        }
147 15
    }
148
149
    /**
150
     * Move multi-line records onto single line.
151
     *
152
     * @throws ParseException
153
     */
154 30
    private function handleMultiline(): void
155
    {
156 30
        if ($this->string->isNot(Tokens::OPEN_BRACKET)) {
157 30
            return;
158
        }
159
160 13
        $this->string->next();
161 13
        while ($this->string->valid()) {
162 13
            $this->handleTxt();
163 13
            $this->handleComment(Comments::MULTILINE);
164
165 13
            if ($this->string->is(Tokens::LINE_FEED)) {
166 13
                $this->string->next();
167 13
                continue;
168
            }
169
170 13
            if ($this->string->is(Tokens::CLOSE_BRACKET)) {
171 12
                $this->string->next();
172
173 12
                $this->process();
174
175 12
                return;
176
            }
177
178 13
            $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 27
    private function removeWhitespace(): void
190
    {
191 27
        $string = preg_replace('/ {2,}/', Tokens::SPACE, $this->normalisedString);
192
193 27
        if (!is_string($string)) {
0 ignored issues
show
introduced by
The condition is_string($string) is always true.
Loading history...
194
            throw new \UnexpectedValueException('Unexpected value returned from \preg_replace()/.');
195
        }
196
197 27
        $lines = [];
198
199 27
        foreach (explode(Tokens::LINE_FEED, $string) as $line) {
200 27
            if ('' !== $line = trim($line)) {
201 27
                $lines[] = $line;
202
            }
203
        }
204 27
        $this->normalisedString = implode(Tokens::LINE_FEED, $lines);
205 27
    }
206
207
    /**
208
     * Add current entry to normalisedString and moves iterator to next entry.
209
     */
210 30
    private function append(): void
211
    {
212 30
        if (($this->string->is(Tokens::LINE_FEED) || !$this->string->valid()) &&
213 30
            $this->commentOptions &&
214 30
            ('' !== $this->comment || '' !== $this->multilineComments)) {
215 8
            $this->appendComment();
216
        }
217
218 30
        $this->normalisedString .= $this->string->current();
219 30
        $this->string->next();
220 30
    }
221
222 8
    private function appendComment(): void
223
    {
224 8
        $zone = rtrim($this->normalisedString, ' ');
225
226
        //If there is no Resource Record on the line
227 8
        if ((Tokens::LINE_FEED === substr($zone, -1, 0) || 0 === strlen($zone))) {
228 1
            if ($this->commentOptions & Comments::ORPHAN) {
229 1
                $this->normalisedString = sprintf('%s;%s', $zone, trim($this->comment));
230
            }
231 1
            $this->comment = '';
232 1
            $this->multilineComments = '';
233
234 1
            return;
235
        }
236
237 8
        $comments = '';
238
239 8
        if ($this->commentOptions & Comments::MULTILINE && '' !== $this->multilineComments) {
240 4
            $comments .= $this->multilineComments;
241
        }
242
243 8
        if ($this->commentOptions & Comments::END_OF_ENTRY && '' !== $this->comment) {
244 8
            $comments .= $this->comment;
245
        }
246
247 8
        if ('' !== $comments = trim($comments)) {
248 8
            $this->normalisedString = sprintf('%s;%s', $zone, $comments);
249
        }
250
251 8
        $this->comment = '';
252 8
        $this->multilineComments = '';
253 8
    }
254
}
255