Completed
Pull Request — master (#64)
by Frederik
04:51
created

AlternativeText::updateLists()   C

Complexity

Conditions 11
Paths 24

Size

Total Lines 70

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 11.1806

Importance

Changes 0
Metric Value
dl 0
loc 70
ccs 31
cts 35
cp 0.8857
rs 6.5077
c 0
b 0
f 0
cc 11
nc 24
nop 1
crap 11.1806

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail;
5
6
final class AlternativeText
7
{
8
    /**
9
     * @var string
10
     */
11
    private $text;
12
13
    /**
14
     * @param string $text
15
     */
16 35
    public function __construct(string $text)
17
    {
18 35
        $this->text = $text;
19 35
    }
20
21
    /**
22
     * @return bool
23
     */
24 20
    public function isEmpty(): bool
25
    {
26 20
        return $this->text === '';
27
    }
28
29
    /**
30
     * @return string
31
     */
32 11
    public function getRaw(): string
33
    {
34 11
        return $this->text;
35
    }
36
37
    /**
38
     * @return string
39
     */
40 25
    public function __toString(): string
41
    {
42 25
        return $this->normalizeSpace($this->text);
43
    }
44
45
    /**
46
     * @param string $string
47
     * @return string
48
     */
49 25
    private function normalizeSpace(string $string): string
50
    {
51 25
        return $this->wrap(
52 25
            \str_replace(
53 25
                ["  ", "\n ", " \n", " \r\n", "\t"],
54 25
                [" ", "\n", "\n", "\r\n", "    "],
55 25
                \trim($string)
56
            )
57
        );
58
    }
59
60
    /**
61
     * @param string $html AlternativeText
62
     * @return AlternativeText
63
     */
64 35
    public static function fromHtml(string $html): AlternativeText
65
    {
66 35
        if ($html === '') {
67 31
            return new self($html);
68
        }
69
70 19
        $html = \preg_replace('/\h\h+/', ' ', (string)$html);
71 19
        $html = \preg_replace('/\v/', '', (string)$html);
72 19
        $text = new self((string)$html);
73
74
        try {
75 19
            $document = new \DOMDocument();
76 19
            $result = @$document->loadHTML($text->text);
77 19
            if ($result === false) {
78
                throw new \DOMException('Incorrect HTML');
79
            }
80
81 19
            $text->wrapSymbols($document);
82 19
            $text->updateHorizontalRule($document);
83 19
            $text->updateLists($document);
84 19
            $text->updateImages($document);
85 19
            $text->updateLinks($document);
86 19
            $text->removeHead($document);
87 19
            $text->updateParagraphsAndBreaksToNewLine($document);
88
89 19
            $text->text = $document->textContent;
90
        } catch (\DOMException $e) {
91
            $text->text = \strip_tags($text->text);
92
        }
93
94 19
        return $text;
95
    }
96
97
    /**
98
     * @param \DOMDocument $document
99
     */
100 19
    private function updateParagraphsAndBreaksToNewLine(\DOMDocument $document): void
101
    {
102 19
        $xpath = new \DOMXPath($document);
103
        $break = [
104 19
            'br' => "\r\n",
105
            'ul' => "\r\n",
106
            'ol' => "\r\n",
107
            'dl' => "\r\n",
108
        ];
109
110
        $query = $xpath->query('//p|//br|//h1|//h2|//h3|//h4|//h5|//h6|//ul|//ol|//dl|//hr');
111 19
        if ($query) {
112 16
            /** @var \DOMElement $element */
113 2
            foreach ($query as $element) {
114
                if (isset($break[$element->nodeName])) {
115 16
                    $textNode = $document->createTextNode($break[$element->nodeName]);
116
                } else {
117
                    $textNode = $document->createTextNode("\r\n\r\n");
118 16
                }
119
120 19
                $element->appendChild($textNode);
121
            }
122
        }
123
    }
124
125 19
    /**
126
     * @param \DOMDocument $document
127 19
     */
128
    private function wrapSymbols(\DOMDocument $document): void
129 19
    {
130
        $xpath = new \DOMXPath($document);
131
        $wrap = [
132
            'h1' => "*",
133
            'h2' => "**",
134
            'h3' => "***",
135
            'h4' => "****",
136
            'h5' => "*****",
137
            'h6' => "******",
138
            'strong' => "*",
139
            'b' => "*",
140
            'em' => "*",
141
            'i' => "*",
142 19
        ];
143 1
144 1
        $query = $xpath->query('//h1|//h2|//h3|//h4|//h5|//h6|//strong|//b|//em|//i');
145
        if ($query) {
146
            /** @var \DOMElement $element */
147 1
            foreach ($query as $element) {
148 1
                $element->appendChild(
149 1
                    $document->createTextNode($wrap[$element->nodeName])
150 1
                );
151
152
                if ($element->firstChild !== null) {
153
                    $element->insertBefore(
154 19
                        $document->createTextNode($wrap[$element->nodeName]),
155
                        $element->firstChild
156
                    );
157
                }
158
            }
159 19
        }
160
    }
161 19
162
    /**
163
     * @param \DOMDocument $document
164 19
     */
165 1
    private function updateLists(\DOMDocument $document): void
166 1
    {
167 1
        $xpath = new \DOMXPath($document);
168 1
169
        $query = $xpath->query('//ul/li');
170
        if ($query) {
171
            /** @var \DOMElement $element */
172
            foreach ($query as $element) {
173
                if ($element->firstChild !== null) {
174
                    $element->insertBefore(
175
                        $document->createTextNode("\t- "),
176 1
                        $element->firstChild
177 1
                    );
178
                } else {
179
                    $element->appendChild(
180
                        $document->createTextNode("\t- ")
181
                    );
182 19
                }
183 1
184 1
                $element->appendChild(
185 1
                    $document->createTextNode("\r\n")
186
                );
187 1
            }
188 1
        }
189 1
190 1
        $query = $xpath->query('//ol/li');
191
        if ($query) {
192
            /** @var \DOMElement $element */
193
            foreach ($query as $element) {
194
                $itemPath = new \DOMXPath($document);
195
                $itemNumber = (int)$itemPath->evaluate('string(count(preceding-sibling::li))', $element) + 1;
196
                $text = \sprintf("\t%d. ", $itemNumber);
197
198 1
                if ($element->firstChild !== null) {
199 1
                    $element->insertBefore(
200
                        $document->createTextNode($text),
201
                        $element->firstChild
202
                    );
203
                } else {
204 19
                    $element->appendChild(
205 1
                        $document->createTextNode($text)
206 1
                    );
207
                }
208
209
                $element->appendChild(
210
                    $document->createTextNode("\r\n")
211 19
                );
212 1
            }
213 1
        }
214
215
        $query = $xpath->query('//dl/dt');
216 19
        if ($query) {
217
            /** @var \DOMElement $element */
218
            foreach ($query as $element) {
219
                $element->appendChild(
220
                    $document->createTextNode(': ')
221 19
                );
222
            }
223 19
        }
224
225
        $query = $xpath->query('//dl/dd');
226 19
        if ($query) {
227 1
            /** @var \DOMElement $element */
228 1
            foreach ($query as $element) {
229 1
                $element->appendChild(
230 1
                    $document->createTextNode("\r\n")
231
                );
232 19
            }
233
        }
234
    }
235
236
    /**
237 19
     * @param \DOMDocument $document
238
     */
239 19
    private function updateImages(\DOMDocument $document): void
240
    {
241
        $xpath = new \DOMXPath($document);
242 19
        $query = $xpath->query('//img[@src and @alt]');
243 1
244
        if ($query) {
245 19
            /** @var \DOMElement $element */
246
            foreach ($query as $element) {
247
                $link = $document->createElement('a');
248
                $link->setAttribute('href', $element->getAttribute('src'));
249
                $link->textContent = $element->getAttribute('alt');
250 19
                $parent = $element->parentNode;
251
                if ($parent) {
252 19
                    $parent->replaceChild($link, $element);
253
                }
254
            }
255 19
        }
256 1
    }
257 1
258 1
    /**
259
     * @param \DOMDocument $document
260
     */
261 1
    private function updateHorizontalRule(\DOMDocument $document): void
262 1
    {
263 1
        $xpath = new \DOMXPath($document);
264 1
        $query = $xpath->query('//hr');
265 1
266
        if ($query) {
267
            /** @var \DOMElement $element */
268
            foreach ($query as $element) {
269
                $element->textContent = \str_repeat('=', 78);
270 19
            }
271
        }
272
    }
273
274
    /**
275 19
     * @param \DOMDocument $document
276
     */
277 19
    private function updateLinks(\DOMDocument $document): void
278 19
    {
279 5
        $xpath = new \DOMXPath($document);
280 5
        $query = $xpath->query('//a[@href and @href != .]');
281
282 19
        if ($query) {
283
            /** @var \DOMElement $element */
284
            foreach ($query as $element) {
285
                if ($element->firstChild) {
286
                    $element->insertBefore(
287
                        $document->createTextNode('>> '),
288
                        $element->firstChild
289 25
                    );
290
                }
291 25
292 25
                $element->appendChild(
293 25
                    $document->createTextNode(
294 25
                        \sprintf(
295 25
                            " <%s>",
296
                            $element->getAttribute('href')
297 25
                        )
298 25
                    )
299 25
                );
300 23
            }
301 3
        }
302 3
    }
303 3
304 23
    /**
305
     * @param \DOMDocument $document
306 23
     */
307 11
    private function removeHead(\DOMDocument $document): void
308 11
    {
309
        $heads = $document->getElementsByTagName('head');
310
        while ($heads->length > 0) {
311 11
            /** @var \DOMElement $head */
312 11
            $head = $heads->item(0);
313 11
            /** @var \DOMElement $parent */
314
            $parent = $head->parentNode;
315
            $parent->removeChild($head);
316 23
        }
317 23
    }
318
319
    /**
320 23
     * @param string $unwrappedText
321 2
     * @param int $width
322 2
     * @return string
323 2
     */
324 2
    private function wrap(string $unwrappedText, int $width = 75): string
325
    {
326
        $result = [];
327 23
        $carriageReturn = false;
328 23
        $lineChars = -1;
329
        $quote = false;
330 23
        $quoteLength = 0;
331 12
332 12
        $iterator = \IntlBreakIterator::createCharacterInstance(\Locale::getDefault());
333 23
        $iterator->setText($unwrappedText);
334 3
        foreach ($iterator->getPartsIterator() as $char) {
335
            if ($char === "\r\n") {
336 23
                $lineChars = -1;
337
                $quoteLength = 0;
338
                $quote = false;
339
            } elseif ($char === "\r") {
340 25
                $carriageReturn = true;
341
            } elseif ($char === "\n") {
342
                if (!$carriageReturn) {
343
                    $char = "\r\n";
344
                }
345
346
                $lineChars = -1;
347
                $quoteLength = 0;
348
                $quote = false;
349
            }
350
351
            if ($char !== "\r") {
352
                $carriageReturn = false;
353
            }
354
355
            if ($lineChars >= $width && \IntlChar::isWhitespace($char)) {
356
                $char = "\r\n" . \str_pad('', $quoteLength, '>');
357
                $lineChars = -1;
358
                $quoteLength = 0;
359
                $quote = false;
360
            }
361
362
            $result[] = $char;
363
            $lineChars++;
364
365
            if ($lineChars === 1 && $char === ">") {
366
                $quote = true;
367
                $quoteLength = 1;
368
            } elseif ($quote && $char === ">") {
369
                $quoteLength++;
370
            } else {
371
                $quote = false;
372
            }
373
        }
374
375
        return \implode('', $result);
376
    }
377
}
378