1<?php
2
3namespace Sabre\VObject\Parser;
4
5use
6    Sabre\VObject\Component\VCalendar,
7    Sabre\VObject\Component\VCard,
8    Sabre\VObject\ParseException,
9    Sabre\VObject\EofException;
10
11/**
12 * Json Parser.
13 *
14 * This parser parses both the jCal and jCard formats.
15 *
16 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17 * @author Evert Pot (http://evertpot.com/)
18 * @license http://sabre.io/license/ Modified BSD License
19 */
20class Json extends Parser {
21
22    /**
23     * The input data
24     *
25     * @var array
26     */
27    protected $input;
28
29    /**
30     * Root component
31     *
32     * @var Document
33     */
34    protected $root;
35
36    /**
37     * This method starts the parsing process.
38     *
39     * If the input was not supplied during construction, it's possible to pass
40     * it here instead.
41     *
42     * If either input or options are not supplied, the defaults will be used.
43     *
44     * @param resource|string|array|null $input
45     * @param int|null $options
46     * @return array
47     */
48    public function parse($input = null, $options = null) {
49
50        if (!is_null($input)) {
51            $this->setInput($input);
52        }
53        if (is_null($this->input)) {
54            throw new EofException('End of input stream, or no input supplied');
55        }
56
57        if (!is_null($options)) {
58            $this->options = $options;
59        }
60
61        switch($this->input[0]) {
62            case 'vcalendar' :
63                $this->root = new VCalendar(array(), false);
64                break;
65            case 'vcard' :
66                $this->root = new VCard(array(), false);
67                break;
68            default :
69                throw new ParseException('The root component must either be a vcalendar, or a vcard');
70
71        }
72        foreach($this->input[1] as $prop) {
73            $this->root->add($this->parseProperty($prop));
74        }
75        if (isset($this->input[2])) foreach($this->input[2] as $comp) {
76            $this->root->add($this->parseComponent($comp));
77        }
78
79        // Resetting the input so we can throw an feof exception the next time.
80        $this->input = null;
81
82        return $this->root;
83
84    }
85
86    /**
87     * Parses a component
88     *
89     * @param array $jComp
90     * @return \Sabre\VObject\Component
91     */
92    public function parseComponent(array $jComp) {
93
94        // We can remove $self from PHP 5.4 onward.
95        $self = $this;
96
97        $properties = array_map(
98            function($jProp) use ($self) {
99                return $self->parseProperty($jProp);
100            },
101            $jComp[1]
102        );
103
104        if (isset($jComp[2])) {
105
106            $components = array_map(
107                function($jComp) use ($self) {
108                    return $self->parseComponent($jComp);
109                },
110                $jComp[2]
111            );
112
113        } else $components = array();
114
115        return $this->root->createComponent(
116            $jComp[0],
117            array_merge($properties, $components),
118            $defaults = false
119        );
120
121    }
122
123    /**
124     * Parses properties.
125     *
126     * @param array $jProp
127     * @return \Sabre\VObject\Property
128     */
129    public function parseProperty(array $jProp) {
130
131        list(
132            $propertyName,
133            $parameters,
134            $valueType
135        ) = $jProp;
136
137        $propertyName = strtoupper($propertyName);
138
139        // This is the default class we would be using if we didn't know the
140        // value type. We're using this value later in this function.
141        $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName);
142
143        $parameters = (array)$parameters;
144
145        $value = array_slice($jProp, 3);
146
147        $valueType = strtoupper($valueType);
148
149        if (isset($parameters['group'])) {
150            $propertyName = $parameters['group'] . '.' . $propertyName;
151            unset($parameters['group']);
152        }
153
154        $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType);
155        $prop->setJsonValue($value);
156
157        // We have to do something awkward here. FlatText as well as Text
158        // represents TEXT values. We have to normalize these here. In the
159        // future we can get rid of FlatText once we're allowed to break BC
160        // again.
161        if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') {
162            $defaultPropertyClass = 'Sabre\VObject\Property\Text';
163        }
164
165        // If the value type we received (e.g.: TEXT) was not the default value
166        // type for the given property (e.g.: BDAY), we need to add a VALUE=
167        // parameter.
168        if ($defaultPropertyClass !== get_class($prop)) {
169            $prop["VALUE"] = $valueType;
170        }
171
172        return $prop;
173
174    }
175
176    /**
177     * Sets the input data
178     *
179     * @param resource|string|array $input
180     * @return void
181     */
182    public function setInput($input) {
183
184        if (is_resource($input)) {
185            $input = stream_get_contents($input);
186        }
187        if (is_string($input)) {
188            $input = json_decode($input);
189        }
190        $this->input = $input;
191
192    }
193
194}
195