comparison plugins/libcalendaring/lib/Sabre/VObject/Reader.php @ 4:888e774ee983

libcalendar plugin as distributed
author Charlie Root
date Sat, 13 Jan 2018 08:57:56 -0500
parents
children
comparison
equal deleted inserted replaced
3:f6fe4b6ae66a 4:888e774ee983
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 }