4
|
1 <?php
|
|
2
|
|
3 namespace Sabre\VObject;
|
|
4
|
|
5 /**
|
|
6 * VCALENDAR/VCARD reader
|
|
7 *
|
|
8 * This class reads the vobject file, and returns a full element tree.
|
|
9 *
|
|
10 * TODO: this class currently completely works 'statically'. This is pointless,
|
|
11 * and defeats OOP principals. Needs refactoring in a future version.
|
|
12 *
|
|
13 * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
|
14 * @author Evert Pot (http://evertpot.com/)
|
|
15 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
|
16 */
|
|
17 class Reader {
|
|
18
|
|
19 /**
|
|
20 * If this option is passed to the reader, it will be less strict about the
|
|
21 * validity of the lines.
|
|
22 *
|
|
23 * Currently using this option just means, that it will accept underscores
|
|
24 * in property names.
|
|
25 */
|
|
26 const OPTION_FORGIVING = 1;
|
|
27
|
|
28 /**
|
|
29 * If this option is turned on, any lines we cannot parse will be ignored
|
|
30 * by the reader.
|
|
31 */
|
|
32 const OPTION_IGNORE_INVALID_LINES = 2;
|
|
33
|
|
34 /**
|
|
35 * Parses the file and returns the top component
|
|
36 *
|
|
37 * The options argument is a bitfield. Pass any of the OPTIONS constant to
|
|
38 * alter the parsers' behaviour.
|
|
39 *
|
|
40 * @param string $data
|
|
41 * @param int $options
|
|
42 * @return Node
|
|
43 */
|
|
44 static function read($data, $options = 0) {
|
|
45
|
|
46 // Normalizing newlines
|
|
47 $data = str_replace(array("\r","\n\n"), array("\n","\n"), $data);
|
|
48
|
|
49 $lines = explode("\n", $data);
|
|
50
|
|
51 // Unfolding lines
|
|
52 $lines2 = array();
|
|
53 foreach($lines as $line) {
|
|
54
|
|
55 // Skipping empty lines
|
|
56 if (!$line) continue;
|
|
57
|
|
58 if ($line[0]===" " || $line[0]==="\t") {
|
|
59 $lines2[count($lines2)-1].=substr($line,1);
|
|
60 } else {
|
|
61 $lines2[] = $line;
|
|
62 }
|
|
63
|
|
64 }
|
|
65
|
|
66 unset($lines);
|
|
67
|
|
68 reset($lines2);
|
|
69
|
|
70 return self::readLine($lines2, $options);
|
|
71
|
|
72 }
|
|
73
|
|
74 /**
|
|
75 * Reads and parses a single line.
|
|
76 *
|
|
77 * This method receives the full array of lines. The array pointer is used
|
|
78 * to traverse.
|
|
79 *
|
|
80 * This method returns null if an invalid line was encountered, and the
|
|
81 * IGNORE_INVALID_LINES option was turned on.
|
|
82 *
|
|
83 * @param array $lines
|
|
84 * @param int $options See the OPTIONS constants.
|
|
85 * @return Node
|
|
86 */
|
|
87 static private function readLine(&$lines, $options = 0) {
|
|
88
|
|
89 $line = current($lines);
|
|
90 $lineNr = key($lines);
|
|
91 next($lines);
|
|
92
|
|
93 // Components
|
|
94 if (strtoupper(substr($line,0,6)) === "BEGIN:") {
|
|
95
|
|
96 $componentName = strtoupper(substr($line,6));
|
|
97 $obj = Component::create($componentName);
|
|
98
|
|
99 $nextLine = current($lines);
|
|
100
|
|
101 while(strtoupper(substr($nextLine,0,4))!=="END:") {
|
|
102
|
|
103 $parsedLine = self::readLine($lines, $options);
|
|
104 $nextLine = current($lines);
|
|
105
|
|
106 if (is_null($parsedLine)) {
|
|
107 continue;
|
|
108 }
|
|
109 $obj->add($parsedLine);
|
|
110
|
|
111 if ($nextLine===false)
|
|
112 throw new ParseException('Invalid VObject. Document ended prematurely.');
|
|
113
|
|
114 }
|
|
115
|
|
116 // Checking component name of the 'END:' line.
|
|
117 if (substr($nextLine,4)!==$obj->name) {
|
|
118 throw new ParseException('Invalid VObject, expected: "END:' . $obj->name . '" got: "' . $nextLine . '"');
|
|
119 }
|
|
120 next($lines);
|
|
121
|
|
122 return $obj;
|
|
123
|
|
124 }
|
|
125
|
|
126 // Properties
|
|
127 //$result = preg_match('/(?P<name>[A-Z0-9-]+)(?:;(?P<parameters>^(?<!:):))(.*)$/',$line,$matches);
|
|
128
|
|
129 if ($options & self::OPTION_FORGIVING) {
|
|
130 $token = '[A-Z0-9-\._]+';
|
|
131 } else {
|
|
132 $token = '[A-Z0-9-\.]+';
|
|
133 }
|
|
134 $parameters = "(?:;(?P<parameters>([^:^\"]|\"([^\"]*)\")*))?";
|
|
135 $regex = "/^(?P<name>$token)$parameters:(?P<value>.*)$/i";
|
|
136
|
|
137 $result = preg_match($regex,$line,$matches);
|
|
138
|
|
139 if (!$result) {
|
|
140 if ($options & self::OPTION_IGNORE_INVALID_LINES) {
|
|
141 return null;
|
|
142 } else {
|
|
143 throw new ParseException('Invalid VObject, line ' . ($lineNr+1) . ' did not follow the icalendar/vcard format');
|
|
144 }
|
|
145 }
|
|
146
|
|
147 $propertyName = strtoupper($matches['name']);
|
|
148 $propertyValue = preg_replace_callback('#(\\\\(\\\\|N|n))#',function($matches) {
|
|
149 if ($matches[2]==='n' || $matches[2]==='N') {
|
|
150 return "\n";
|
|
151 } else {
|
|
152 return $matches[2];
|
|
153 }
|
|
154 }, $matches['value']);
|
|
155
|
|
156 $obj = Property::create($propertyName, $propertyValue);
|
|
157
|
|
158 if ($matches['parameters']) {
|
|
159
|
|
160 foreach(self::readParameters($matches['parameters']) as $param) {
|
|
161 $obj->add($param);
|
|
162 }
|
|
163
|
|
164 }
|
|
165
|
|
166 return $obj;
|
|
167
|
|
168
|
|
169 }
|
|
170
|
|
171 /**
|
|
172 * Reads a parameter list from a property
|
|
173 *
|
|
174 * This method returns an array of Parameter
|
|
175 *
|
|
176 * @param string $parameters
|
|
177 * @return array
|
|
178 */
|
|
179 static private function readParameters($parameters) {
|
|
180
|
|
181 $token = '[A-Z0-9-]+';
|
|
182
|
|
183 $paramValue = '(?P<paramValue>[^\"^;]*|"[^"]*")';
|
|
184
|
|
185 $regex = "/(?<=^|;)(?P<paramName>$token)(=$paramValue(?=$|;))?/i";
|
|
186 preg_match_all($regex, $parameters, $matches, PREG_SET_ORDER);
|
|
187
|
|
188 $params = array();
|
|
189 foreach($matches as $match) {
|
|
190
|
|
191 if (!isset($match['paramValue'])) {
|
|
192
|
|
193 $value = null;
|
|
194
|
|
195 } else {
|
|
196
|
|
197 $value = $match['paramValue'];
|
|
198
|
|
199 if (isset($value[0]) && $value[0]==='"') {
|
|
200 // Stripping quotes, if needed
|
|
201 $value = substr($value,1,strlen($value)-2);
|
|
202 }
|
|
203
|
|
204 $value = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) {
|
|
205 if ($matches[2]==='n' || $matches[2]==='N') {
|
|
206 return "\n";
|
|
207 } else {
|
|
208 return $matches[2];
|
|
209 }
|
|
210 }, $value);
|
|
211
|
|
212 }
|
|
213
|
|
214 $params[] = new Parameter($match['paramName'], $value);
|
|
215
|
|
216 }
|
|
217
|
|
218 return $params;
|
|
219
|
|
220 }
|
|
221
|
|
222
|
|
223 }
|