Completed
Branch 2.0.0-dev (4220a4)
by Jeroen
03:47
created

VcfParser::parseVCardContentLine()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 29
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 29
rs 10
cc 3
nc 3
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace JeroenDesloovere\VCard\Parser;
6
7
use JeroenDesloovere\VCard\Exception\ParserException;
8
use JeroenDesloovere\VCard\Formatter\VcfFormatter;
9
use JeroenDesloovere\VCard\Parser\Property\NodeParserInterface;
10
use JeroenDesloovere\VCard\Property\NodeInterface;
11
use JeroenDesloovere\VCard\VCard;
12
use JeroenDesloovere\VCard\Property\Parameter\Version;
13
use JeroenDesloovere\VCard\Property\Parameter\Kind;
14
15
final class VcfParser implements ParserInterface
16
{
17
    /** @var NodeParserInterface[] - f.e. ['ADR' => JeroenDesloovere\VCard\Parser\Property\AddressParser] */
18
    private $parsers = [];
19
20
    public function __construct()
21
    {
22
        /**
23
         * We define all possible node parsers
24
         *
25
         * @var NodeInterface $node
26
         */
27
        foreach (VCard::POSSIBLE_VALUES as $node) {
28
            $this->parsers[$node::getNode()] = $node::getParser();
29
        }
30
    }
31
32
    /**
33
     * Returns all found vCard objects
34
     *
35
     * @param string $content
36
     * @return VCard[]
37
     * @throws ParserException
38
     */
39
    public function getVCards(string $content): array
40
    {
41
        return array_map(function ($vCardContent) {
42
            return $this->parseVCard($vCardContent);
43
        }, $this->splitIntoVCards($content));
44
    }
45
46
    private function parseParameters(?string $parameters): array
47
    {
48
        if ($parameters === null) {
49
            return [];
50
        }
51
52
        /** @var string[] $parametersArray */
53
        $parametersArray = explode(';', $parameters);
54
        $parsedParameters = [];
55
        foreach ($parametersArray as $parameter) {
56
            /**
57
             * @var string $node
58
             * @var string $value
59
             */
60
            @list($node, $value) = explode('=', $parameter, 2);
61
62
            if (array_key_exists($node, $this->parsers)) {
63
                $parsedParameters[$node] = $this->parsers[$node]->parseVcfString($value);
64
            }
65
        }
66
67
        return $parsedParameters;
68
    }
69
70
    private function parseVCard(string $content): VCard
71
    {
72
        $vCard = $this->createVcardObjectWithProperties($content);
73
74
        $lines = explode("\n", $content);
75
        foreach ($lines as $line) {
76
            $this->parseVCardContentLine($line, $vCard);
77
        }
78
79
        return $vCard;
80
    }
81
82
    private function createVcardObjectWithProperties(string $content): VCard
83
    {
84
        $vcardProperties = array(
85
          Kind::getNode() => null,
86
          Version::getNode() => null);
87
88
        $lines = explode("\n", $content);
89
        foreach ($lines as $line) {
90
            /**
91
             * @var string $node
92
             * @var string $value
93
             */
94
            @list($node, $value) = explode(':', $line, 2);
95
            if (array_key_exists($node, $this->parsers)) {
96
                // Only check on either Kind or Version node
97
                if ($node == Kind::getNode() || $node == Version::getNode()) {
98
                    $vcardProperties[$node] = $this->parsers[$node]->parseVcfString($value);
99
                }
100
            }
101
        }
102
103
        return new VCard($vcardProperties[Kind::getNode()], $vcardProperties[Version::getNode()]);
104
    }
105
106
    private function parseVCardContentLine(string $line, VCard &$vCard): void
107
    {
108
        // Strip grouping information. We don't use the group names. We
109
        // simply use a list for entries that have multiple values.
110
        // As per RFC, group names are alphanumerical, and end with a
111
        // period (.).
112
        $line = preg_replace('/^\w+\./', '', trim($line));
113
114
        /**
115
         * @var string $node
116
         * @var string $value
117
         */
118
        @list($node, $value) = explode(':', $line, 2);
119
120
        /**
121
         * @var string $node
122
         * @var string|null $parameterContent
123
         */
124
        @list($node, $parameterContent) = explode(';', $node, 2);
125
126
        // Skip parameters that we can not parse yet, because the property/parser does not exist yet.
127
        // Feel free to create a PR to add a new Property Parser
128
        if (!array_key_exists($node, $this->parsers)) {
129
            return;
130
        }
131
132
        try {
133
            $vCard->add($this->parsers[$node]->parseVcfString($value, $this->parseParameters($parameterContent)));
134
        } catch (\Exception $e) {
135
            // Ignoring properties that throw error. F.e. if they are allowed only once.
136
        }
137
    }
138
139
    /**
140
     * Split string into array, each array item contains vCard content.
141
     *
142
     * @param string $content - The full content from the .vcf file.
143
     * @return array - Is an array with the content for all possible vCards.
144
     * @throws ParserException
145
     */
146
    private function splitIntoVCards(string $content): array
147
    {
148
        // Normalize new lines.
149
        $content = trim(str_replace(["\r\n", "\r"], "\n", $content));
150
151
        if (!preg_match('/^BEGIN:VCARD[\s\S]+END:VCARD$/', $content)) {
152
            throw ParserException::forUnreadableVCard($content);
153
        }
154
155
        // Remove first BEGIN:VCARD and last END:VCARD
156
        $content = substr($content, 12, -10);
157
158
        // RFC2425 5.8.1. Line delimiting and folding
159
        // Unfolding is accomplished by regarding CRLF immediately followed by
160
        // a white space character (namely HTAB ASCII decimal 9 or. SPACE ASCII
161
        // decimal 32) as equivalent to no characters at all (i.e., the CRLF
162
        // and single white space character are removed).
163
        $content = preg_replace("/\n(?:[ \t])/", '', $content);
164
165
        // If multiple vcards split per vcard
166
        $contentPerVCard = preg_split(
167
            '/\n' . VcfFormatter::VCARD_END . '\s+' . VcfFormatter::VCARD_BEGIN . '\n/',
168
            $content
169
        );
170
171
        return is_array($contentPerVCard) ? $contentPerVCard : [];
0 ignored issues
show
introduced by
The condition is_array($contentPerVCard) is always true.
Loading history...
172
    }
173
}
174