Passed
Branch new-version (72fcef)
by Jeroen
03:10
created

VcfParser::splitIntoVCardsContent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
1
<?php
2
3
namespace JeroenDesloovere\VCard\Parser;
4
5
use JeroenDesloovere\VCard\Exception\ParserException;
6
use JeroenDesloovere\VCard\Parser\Property\NodeParserInterface;
7
use JeroenDesloovere\VCard\VCard;
8
9
final class VcfParser implements ParserInterface
10
{
11
    /**
12
     * @var array - Structure = [node => NodeParserInterface]
13
     */
14
    private $parsers = [];
15
16
    /**
17
     * @param string $content
18
     * @return VCard[]
19
     * @throws ParserException
20
     */
21
    public function getVCards(string $content): array
22
    {
23
        // Set possible parsers
24
        foreach (VCard::POSSIBLE_VALUES as $propertyClass) {
25
            $this->parsers[($propertyClass)::getNode()] = ($propertyClass)::getParser();
26
        }
27
28
        $vCards = [];
29
30
        foreach ($this->splitIntoVCardsContent($content) as $vCardContent) {
31
            $vCard = $this->parseVCard($vCardContent);
32
33
            if ($vCard instanceof VCard) {
34
                $vCards[] = $vCard;
35
            }
36
        }
37
38
        return $vCards;
39
    }
40
41
    private function parseParameters(?string $parameters): array
42
    {
43
        if ($parameters === null) {
44
            return [];
45
        }
46
47
        $parsedParameters = [];
48
        $parameters = explode(';', $parameters);
49
        foreach ($parameters as $parameter) {
50
            @list($node, $value) = explode('=', $parameter, 2);
51
52
            if (array_key_exists($node, $this->parsers)) {
53
                $parsedParameters[$node] = $this->parsers[$node]->parseLine($value);
54
            }
55
        }
56
57
        return $parsedParameters;
58
    }
59
60
    protected function parseVCard(string $content): VCard
61
    {
62
        $vCard = new VCard();
63
        $lines = explode("\n", $content);
64
65
        foreach ($lines as $line) {
66
            // Strip grouping information. We don't use the group names. We
67
            // simply use a list for entries that have multiple values.
68
            // As per RFC, group names are alphanumerical, and end with a
69
            // period (.).
70
            $line = preg_replace('/^\w+\./', '', trim($line));
71
72
            @list($node, $value) = explode(':', $line, 2);
73
            @list($node, $parameters) = explode(';', $node, 2);
74
75
            if (!array_key_exists($node, $this->parsers)) {
76
                // @todo: add this line to "not converted" errors. Can be useful to improve the parser.
77
78
                continue;
79
            }
80
81
            $parameters = $this->parseParameters($parameters);
0 ignored issues
show
Bug introduced by
$parameters of type array is incompatible with the type null|string expected by parameter $parameters of JeroenDesloovere\VCard\P...rser::parseParameters(). ( Ignorable by Annotation )

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

81
            $parameters = $this->parseParameters(/** @scrutinizer ignore-type */ $parameters);
Loading history...
82
83
            try {
84
                /**
85
                 * @var NodeParserInterface $this->parsers[$node]
86
                 */
87
                $vCard->add($this->parsers[$node]->parseLine($value, $parameters));
88
            } catch (\Exception $e) {
89
                // @todo: fetch errors when setting properties that are already set.
90
            }
91
        }
92
93
        return $vCard;
94
    }
95
96
    protected function splitIntoVCardsContent(string $content): array
97
    {
98
        // Normalize new lines.
99
        $content = str_replace(["\r\n", "\r"], "\n", $content);
100
101
        $content = trim($content);
102
103
        if (!preg_match('/^BEGIN:VCARD[\s\S]+END:VCARD$/', $content)) {
104
            throw ParserException::forUnreadableVCard($content);
105
        }
106
107
        // Remove first BEGIN:VCARD and last END:VCARD
108
        $content = substr($content, 12, -10);
109
110
        // RFC2425 5.8.1. Line delimiting and folding
111
        // Unfolding is accomplished by regarding CRLF immediately followed by
112
        // a white space character (namely HTAB ASCII decimal 9 or. SPACE ASCII
113
        // decimal 32) as equivalent to no characters at all (i.e., the CRLF
114
        // and single white space character are removed).
115
        $content = preg_replace("/\n(?:[ \t])/", '', $content);
116
117
        // If multiple vcards split per vcard
118
        return preg_split('/\nEND:VCARD\s+BEGIN:VCARD\n/', $content);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_split('/\nEN...IN:VCARD\n/', $content) could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
119
    }
120
}
121