Completed
Branch 2.0.0-dev (4ad962)
by Jeroen
08:43
created

VcfParser::splitIntoVCards()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 21
rs 9.3142
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\Property\NodeInterface;
8
use JeroenDesloovere\VCard\VCard;
9
10
final class VcfParser implements ParserInterface
11
{
12
    /** @var NodeParserInterface[] - f.e. ['ADR' => JeroenDesloovere\VCard\Parser\Property\AddressParser] */
13
    private $parsers = [];
14
15
    public function __construct()
16
    {
17
        /**
18
         * We define all possible node parsers
19
         *
20
         * @var NodeInterface $node
21
         */
22
        foreach (VCard::POSSIBLE_VALUES as $node) {
23
            $this->parsers[$node::getNode()] = $node::getParser();
24
        }
25
    }
26
27
    /**
28
     * Returns all found vCard objects
29
     *
30
     * @param string $content
31
     * @return VCard[]
32
     * @throws ParserException
33
     */
34
    public function getVCards(string $content): array
35
    {
36
        return array_map(function ($vCardContent) {
37
            return $this->parseVCard($vCardContent);
38
        }, $this->splitIntoVCards($content));
39
    }
40
41
    private function parseParameters(?string $parameters): array
42
    {
43
        if ($parameters === null) {
44
            return [];
45
        }
46
47
        /** @var string[] $parametersArray */
48
        $parametersArray = explode(';', $parameters);
49
        $parsedParameters = [];
50
        foreach ($parametersArray as $parameter) {
51
            /**
52
             * @var string $node
53
             * @var string $value
54
             */
55
            @list($node, $value) = explode('=', $parameter, 2);
56
57
            if (array_key_exists($node, $this->parsers)) {
58
                $parsedParameters[$node] = $this->parsers[$node]->parseLine($value);
59
            }
60
        }
61
62
        return $parsedParameters;
63
    }
64
65
    private function parseVCard(string $content): VCard
66
    {
67
        $vCard = new VCard();
68
        $lines = explode("\n", $content);
69
        foreach ($lines as $line) {
70
            // Strip grouping information. We don't use the group names. We
71
            // simply use a list for entries that have multiple values.
72
            // As per RFC, group names are alphanumerical, and end with a
73
            // period (.).
74
            $line = preg_replace('/^\w+\./', '', trim($line));
75
76
            /**
77
             * @var string $node
78
             * @var string $value
79
             */
80
            @list($node, $value) = explode(':', $line, 2);
81
82
            /**
83
             * @var string $node
84
             * @var string|null $parameterContent
85
             */
86
            @list($node, $parameterContent) = explode(';', $node, 2);
87
88
            // Skip parameters that we can not parse yet, because the property/parser does not exist yet.
89
            // Feel free to create a PR to add a new Property Parser
90
            if (!array_key_exists($node, $this->parsers)) {
91
                continue;
92
            }
93
94
            try {
95
                $vCard->add($this->parsers[$node]->parseLine($value, $this->parseParameters($parameterContent)));
96
            } catch (\Exception $e) {
97
                // Ignoring properties that are already set.
98
            }
99
        }
100
101
        return $vCard;
102
    }
103
104
    /**
105
     * Split string into array, each array item contains vCard content.
106
     *
107
     * @param string $content - The full content from the .vcf file.
108
     * @return array - Is an array with the content for all possible vCards.
109
     * @throws ParserException
110
     */
111
    private function splitIntoVCards(string $content): array
112
    {
113
        // Normalize new lines.
114
        $content = trim(str_replace(["\r\n", "\r"], "\n", $content));
115
116
        if (!preg_match('/^BEGIN:VCARD[\s\S]+END:VCARD$/', $content)) {
117
            throw ParserException::forUnreadableVCard($content);
118
        }
119
120
        // Remove first BEGIN:VCARD and last END:VCARD
121
        $content = substr($content, 12, -10);
122
123
        // RFC2425 5.8.1. Line delimiting and folding
124
        // Unfolding is accomplished by regarding CRLF immediately followed by
125
        // a white space character (namely HTAB ASCII decimal 9 or. SPACE ASCII
126
        // decimal 32) as equivalent to no characters at all (i.e., the CRLF
127
        // and single white space character are removed).
128
        $content = preg_replace("/\n(?:[ \t])/", '', $content);
129
130
        // If multiple vcards split per vcard
131
        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...
132
    }
133
}
134