Passed
Branch 2.0.0-dev (2821a4)
by Jeroen
06:26 queued 04:08
created

VcfParser   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 132
rs 10
c 0
b 0
f 0
wmc 15

6 Methods

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