Mercurial > hg > rc1
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/libcalendaring/lib/Sabre/VObject/Reader.php Sat Jan 13 08:57:56 2018 -0500 @@ -0,0 +1,223 @@ +<?php + +namespace Sabre\VObject; + +/** + * VCALENDAR/VCARD reader + * + * This class reads the vobject file, and returns a full element tree. + * + * TODO: this class currently completely works 'statically'. This is pointless, + * and defeats OOP principals. Needs refactoring in a future version. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Reader { + + /** + * If this option is passed to the reader, it will be less strict about the + * validity of the lines. + * + * Currently using this option just means, that it will accept underscores + * in property names. + */ + const OPTION_FORGIVING = 1; + + /** + * If this option is turned on, any lines we cannot parse will be ignored + * by the reader. + */ + const OPTION_IGNORE_INVALID_LINES = 2; + + /** + * Parses the file and returns the top component + * + * The options argument is a bitfield. Pass any of the OPTIONS constant to + * alter the parsers' behaviour. + * + * @param string $data + * @param int $options + * @return Node + */ + static function read($data, $options = 0) { + + // Normalizing newlines + $data = str_replace(array("\r","\n\n"), array("\n","\n"), $data); + + $lines = explode("\n", $data); + + // Unfolding lines + $lines2 = array(); + foreach($lines as $line) { + + // Skipping empty lines + if (!$line) continue; + + if ($line[0]===" " || $line[0]==="\t") { + $lines2[count($lines2)-1].=substr($line,1); + } else { + $lines2[] = $line; + } + + } + + unset($lines); + + reset($lines2); + + return self::readLine($lines2, $options); + + } + + /** + * Reads and parses a single line. + * + * This method receives the full array of lines. The array pointer is used + * to traverse. + * + * This method returns null if an invalid line was encountered, and the + * IGNORE_INVALID_LINES option was turned on. + * + * @param array $lines + * @param int $options See the OPTIONS constants. + * @return Node + */ + static private function readLine(&$lines, $options = 0) { + + $line = current($lines); + $lineNr = key($lines); + next($lines); + + // Components + if (strtoupper(substr($line,0,6)) === "BEGIN:") { + + $componentName = strtoupper(substr($line,6)); + $obj = Component::create($componentName); + + $nextLine = current($lines); + + while(strtoupper(substr($nextLine,0,4))!=="END:") { + + $parsedLine = self::readLine($lines, $options); + $nextLine = current($lines); + + if (is_null($parsedLine)) { + continue; + } + $obj->add($parsedLine); + + if ($nextLine===false) + throw new ParseException('Invalid VObject. Document ended prematurely.'); + + } + + // Checking component name of the 'END:' line. + if (substr($nextLine,4)!==$obj->name) { + throw new ParseException('Invalid VObject, expected: "END:' . $obj->name . '" got: "' . $nextLine . '"'); + } + next($lines); + + return $obj; + + } + + // Properties + //$result = preg_match('/(?P<name>[A-Z0-9-]+)(?:;(?P<parameters>^(?<!:):))(.*)$/',$line,$matches); + + if ($options & self::OPTION_FORGIVING) { + $token = '[A-Z0-9-\._]+'; + } else { + $token = '[A-Z0-9-\.]+'; + } + $parameters = "(?:;(?P<parameters>([^:^\"]|\"([^\"]*)\")*))?"; + $regex = "/^(?P<name>$token)$parameters:(?P<value>.*)$/i"; + + $result = preg_match($regex,$line,$matches); + + if (!$result) { + if ($options & self::OPTION_IGNORE_INVALID_LINES) { + return null; + } else { + throw new ParseException('Invalid VObject, line ' . ($lineNr+1) . ' did not follow the icalendar/vcard format'); + } + } + + $propertyName = strtoupper($matches['name']); + $propertyValue = preg_replace_callback('#(\\\\(\\\\|N|n))#',function($matches) { + if ($matches[2]==='n' || $matches[2]==='N') { + return "\n"; + } else { + return $matches[2]; + } + }, $matches['value']); + + $obj = Property::create($propertyName, $propertyValue); + + if ($matches['parameters']) { + + foreach(self::readParameters($matches['parameters']) as $param) { + $obj->add($param); + } + + } + + return $obj; + + + } + + /** + * Reads a parameter list from a property + * + * This method returns an array of Parameter + * + * @param string $parameters + * @return array + */ + static private function readParameters($parameters) { + + $token = '[A-Z0-9-]+'; + + $paramValue = '(?P<paramValue>[^\"^;]*|"[^"]*")'; + + $regex = "/(?<=^|;)(?P<paramName>$token)(=$paramValue(?=$|;))?/i"; + preg_match_all($regex, $parameters, $matches, PREG_SET_ORDER); + + $params = array(); + foreach($matches as $match) { + + if (!isset($match['paramValue'])) { + + $value = null; + + } else { + + $value = $match['paramValue']; + + if (isset($value[0]) && $value[0]==='"') { + // Stripping quotes, if needed + $value = substr($value,1,strlen($value)-2); + } + + $value = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) { + if ($matches[2]==='n' || $matches[2]==='N') { + return "\n"; + } else { + return $matches[2]; + } + }, $value); + + } + + $params[] = new Parameter($match['paramName'], $value); + + } + + return $params; + + } + + +}