1<?php
2
3namespace SabreForRainLoop\VObject\Parser;
4
5use
6    SabreForRainLoop\VObject\Component\VCalendar,
7    SabreForRainLoop\VObject\Component\VCard,
8    SabreForRainLoop\VObject\ParseException,
9    SabreForRainLoop\VObject\EofException;
10
11/**
12 * Json Parser.
13 *
14 * This parser parses both the jCal and jCard formats.
15 *
16 * @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
17 * @author Evert Pot (http://evertpot.com/)
18 * @license http://code.google.com/p/sabredav/wiki/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 \SabreForRainLoop\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(function($jProp) use ($self) {
98            return $self->parseProperty($jProp);
99        }, $jComp[1]);
100
101        if (isset($jComp[2])) {
102
103            $components = array_map(function($jComp) use ($self) {
104                return $self->parseComponent($jComp);
105            }, $jComp[2]);
106
107        } else $components = array();
108
109        return $this->root->createComponent(
110            $jComp[0],
111            array_merge( $properties, $components),
112            $defaults = false
113        );
114
115    }
116
117    /**
118     * Parses properties.
119     *
120     * @param array $jProp
121     * @return \SabreForRainLoop\VObject\Property
122     */
123    public function parseProperty(array $jProp) {
124
125        list(
126            $propertyName,
127            $parameters,
128            $valueType
129        ) = $jProp;
130
131        $propertyName = strtoupper($propertyName);
132
133        // This is the default class we would be using if we didn't know the
134        // value type. We're using this value later in this function.
135        $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName);
136
137        $parameters = (array)$parameters;
138
139        $value = array_slice($jProp, 3);
140
141        $valueType = strtoupper($valueType);
142
143        if (isset($parameters['group'])) {
144            $propertyName = $parameters['group'] . '.' . $propertyName;
145            unset($parameters['group']);
146        }
147
148        $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType);
149        $prop->setJsonValue($value);
150
151        // We have to do something awkward here. FlatText as well as Text
152        // represents TEXT values. We have to normalize these here. In the
153        // future we can get rid of FlatText once we're allowed to break BC
154        // again.
155        if ($defaultPropertyClass === 'SabreForRainLoop\VObject\Property\FlatText') {
156            $defaultPropertyClass = 'SabreForRainLoop\VObject\Property\Text';
157        }
158
159        // If the value type we received (e.g.: TEXT) was not the default value
160        // type for the given property (e.g.: BDAY), we need to add a VALUE=
161        // parameter.
162        if ($defaultPropertyClass !== get_class($prop)) {
163            $prop["VALUE"] = $valueType;
164        }
165
166        return $prop;
167
168    }
169
170    /**
171     * Sets the input data
172     *
173     * @param resource|string|array $input
174     * @return void
175     */
176    public function setInput($input) {
177
178        if (is_resource($input)) {
179            $input = stream_get_contents($input);
180        }
181        if (is_string($input)) {
182            $input = json_decode($input);
183        }
184        $this->input = $input;
185
186    }
187
188}
189