diff options
Diffstat (limited to 'vendor/sabre/vobject/lib/Parser')
-rw-r--r-- | vendor/sabre/vobject/lib/Parser/Json.php | 197 | ||||
-rw-r--r-- | vendor/sabre/vobject/lib/Parser/MimeDir.php | 696 | ||||
-rw-r--r-- | vendor/sabre/vobject/lib/Parser/Parser.php | 80 | ||||
-rw-r--r-- | vendor/sabre/vobject/lib/Parser/XML.php | 428 | ||||
-rw-r--r-- | vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php | 70 |
5 files changed, 1471 insertions, 0 deletions
diff --git a/vendor/sabre/vobject/lib/Parser/Json.php b/vendor/sabre/vobject/lib/Parser/Json.php new file mode 100644 index 000000000..370c981a8 --- /dev/null +++ b/vendor/sabre/vobject/lib/Parser/Json.php @@ -0,0 +1,197 @@ +<?php + +namespace Sabre\VObject\Parser; + +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\ParseException; +use Sabre\VObject\EofException; + +/** + * Json Parser. + * + * This parser parses both the jCal and jCard formats. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Json extends Parser { + + /** + * The input data. + * + * @var array + */ + protected $input; + + /** + * Root component. + * + * @var Document + */ + protected $root; + + /** + * This method starts the parsing process. + * + * If the input was not supplied during construction, it's possible to pass + * it here instead. + * + * If either input or options are not supplied, the defaults will be used. + * + * @param resource|string|array|null $input + * @param int $options + * + * @return Sabre\VObject\Document + */ + function parse($input = null, $options = 0) { + + if (!is_null($input)) { + $this->setInput($input); + } + if (is_null($this->input)) { + throw new EofException('End of input stream, or no input supplied'); + } + + if (0 !== $options) { + $this->options = $options; + } + + switch ($this->input[0]) { + case 'vcalendar' : + $this->root = new VCalendar([], false); + break; + case 'vcard' : + $this->root = new VCard([], false); + break; + default : + throw new ParseException('The root component must either be a vcalendar, or a vcard'); + + } + foreach ($this->input[1] as $prop) { + $this->root->add($this->parseProperty($prop)); + } + if (isset($this->input[2])) foreach ($this->input[2] as $comp) { + $this->root->add($this->parseComponent($comp)); + } + + // Resetting the input so we can throw an feof exception the next time. + $this->input = null; + + return $this->root; + + } + + /** + * Parses a component. + * + * @param array $jComp + * + * @return \Sabre\VObject\Component + */ + function parseComponent(array $jComp) { + + // We can remove $self from PHP 5.4 onward. + $self = $this; + + $properties = array_map( + function($jProp) use ($self) { + return $self->parseProperty($jProp); + }, + $jComp[1] + ); + + if (isset($jComp[2])) { + + $components = array_map( + function($jComp) use ($self) { + return $self->parseComponent($jComp); + }, + $jComp[2] + ); + + } else $components = []; + + return $this->root->createComponent( + $jComp[0], + array_merge($properties, $components), + $defaults = false + ); + + } + + /** + * Parses properties. + * + * @param array $jProp + * + * @return \Sabre\VObject\Property + */ + function parseProperty(array $jProp) { + + list( + $propertyName, + $parameters, + $valueType + ) = $jProp; + + $propertyName = strtoupper($propertyName); + + // This is the default class we would be using if we didn't know the + // value type. We're using this value later in this function. + $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName); + + $parameters = (array)$parameters; + + $value = array_slice($jProp, 3); + + $valueType = strtoupper($valueType); + + if (isset($parameters['group'])) { + $propertyName = $parameters['group'] . '.' . $propertyName; + unset($parameters['group']); + } + + $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType); + $prop->setJsonValue($value); + + // We have to do something awkward here. FlatText as well as Text + // represents TEXT values. We have to normalize these here. In the + // future we can get rid of FlatText once we're allowed to break BC + // again. + if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') { + $defaultPropertyClass = 'Sabre\VObject\Property\Text'; + } + + // If the value type we received (e.g.: TEXT) was not the default value + // type for the given property (e.g.: BDAY), we need to add a VALUE= + // parameter. + if ($defaultPropertyClass !== get_class($prop)) { + $prop["VALUE"] = $valueType; + } + + return $prop; + + } + + /** + * Sets the input data. + * + * @param resource|string|array $input + * + * @return void + */ + function setInput($input) { + + if (is_resource($input)) { + $input = stream_get_contents($input); + } + if (is_string($input)) { + $input = json_decode($input); + } + $this->input = $input; + + } + +} diff --git a/vendor/sabre/vobject/lib/Parser/MimeDir.php b/vendor/sabre/vobject/lib/Parser/MimeDir.php new file mode 100644 index 000000000..6a7f9317b --- /dev/null +++ b/vendor/sabre/vobject/lib/Parser/MimeDir.php @@ -0,0 +1,696 @@ +<?php + +namespace Sabre\VObject\Parser; + +use Sabre\VObject\ParseException; +use Sabre\VObject\EofException; +use Sabre\VObject\Component; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\Document; + +/** + * MimeDir parser. + * + * This class parses iCalendar 2.0 and vCard 2.1, 3.0 and 4.0 files. This + * parser will return one of the following two objects from the parse method: + * + * Sabre\VObject\Component\VCalendar + * Sabre\VObject\Component\VCard + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MimeDir extends Parser { + + /** + * The input stream. + * + * @var resource + */ + protected $input; + + /** + * Root component. + * + * @var Component + */ + protected $root; + + /** + * By default all input will be assumed to be UTF-8. + * + * However, both iCalendar and vCard might be encoded using different + * character sets. The character set is usually set in the mime-type. + * + * If this is the case, use setEncoding to specify that a different + * encoding will be used. If this is set, the parser will automatically + * convert all incoming data to UTF-8. + * + * @var string + */ + protected $charset = 'UTF-8'; + + /** + * The list of character sets we support when decoding. + * + * This would be a const expression but for now we need to support PHP 5.5 + */ + protected static $SUPPORTED_CHARSETS = [ + 'UTF-8', + 'ISO-8859-1', + 'Windows-1252', + ]; + + /** + * Parses an iCalendar or vCard file. + * + * Pass a stream or a string. If null is parsed, the existing buffer is + * used. + * + * @param string|resource|null $input + * @param int $options + * + * @return Sabre\VObject\Document + */ + function parse($input = null, $options = 0) { + + $this->root = null; + + if (!is_null($input)) { + $this->setInput($input); + } + + if (0 !== $options) { + $this->options = $options; + } + + $this->parseDocument(); + + return $this->root; + + } + + /** + * By default all input will be assumed to be UTF-8. + * + * However, both iCalendar and vCard might be encoded using different + * character sets. The character set is usually set in the mime-type. + * + * If this is the case, use setEncoding to specify that a different + * encoding will be used. If this is set, the parser will automatically + * convert all incoming data to UTF-8. + * + * @param string $charset + */ + function setCharset($charset) { + + if (!in_array($charset, self::$SUPPORTED_CHARSETS)) { + throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: ' . implode(', ', self::$SUPPORTED_CHARSETS) . ')'); + } + $this->charset = $charset; + + } + + /** + * Sets the input buffer. Must be a string or stream. + * + * @param resource|string $input + * + * @return void + */ + function setInput($input) { + + // Resetting the parser + $this->lineIndex = 0; + $this->startLine = 0; + + if (is_string($input)) { + // Convering to a stream. + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $input); + rewind($stream); + $this->input = $stream; + } elseif (is_resource($input)) { + $this->input = $input; + } else { + throw new \InvalidArgumentException('This parser can only read from strings or streams.'); + } + + } + + /** + * Parses an entire document. + * + * @return void + */ + protected function parseDocument() { + + $line = $this->readLine(); + + // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF). + // It's 0xEF 0xBB 0xBF in UTF-8 hex. + if (3 <= strlen($line) + && ord($line[0]) === 0xef + && ord($line[1]) === 0xbb + && ord($line[2]) === 0xbf) { + $line = substr($line, 3); + } + + switch (strtoupper($line)) { + case 'BEGIN:VCALENDAR' : + $class = VCalendar::$componentMap['VCALENDAR']; + break; + case 'BEGIN:VCARD' : + $class = VCard::$componentMap['VCARD']; + break; + default : + throw new ParseException('This parser only supports VCARD and VCALENDAR files'); + } + + $this->root = new $class([], false); + + while (true) { + + // Reading until we hit END: + $line = $this->readLine(); + if (strtoupper(substr($line, 0, 4)) === 'END:') { + break; + } + $result = $this->parseLine($line); + if ($result) { + $this->root->add($result); + } + + } + + $name = strtoupper(substr($line, 4)); + if ($name !== $this->root->name) { + throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"'); + } + + } + + /** + * Parses a line, and if it hits a component, it will also attempt to parse + * the entire component. + * + * @param string $line Unfolded line + * + * @return Node + */ + protected function parseLine($line) { + + // Start of a new component + if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') { + + $component = $this->root->createComponent(substr($line, 6), [], false); + + while (true) { + + // Reading until we hit END: + $line = $this->readLine(); + if (strtoupper(substr($line, 0, 4)) === 'END:') { + break; + } + $result = $this->parseLine($line); + if ($result) { + $component->add($result); + } + + } + + $name = strtoupper(substr($line, 4)); + if ($name !== $component->name) { + throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"'); + } + + return $component; + + } else { + + // Property reader + $property = $this->readProperty($line); + if (!$property) { + // Ignored line + return false; + } + return $property; + + } + + } + + /** + * We need to look ahead 1 line every time to see if we need to 'unfold' + * the next line. + * + * If that was not the case, we store it here. + * + * @var null|string + */ + protected $lineBuffer; + + /** + * The real current line number. + */ + protected $lineIndex = 0; + + /** + * In the case of unfolded lines, this property holds the line number for + * the start of the line. + * + * @var int + */ + protected $startLine = 0; + + /** + * Contains a 'raw' representation of the current line. + * + * @var string + */ + protected $rawLine; + + /** + * Reads a single line from the buffer. + * + * This method strips any newlines and also takes care of unfolding. + * + * @throws \Sabre\VObject\EofException + * + * @return string + */ + protected function readLine() { + + if (!is_null($this->lineBuffer)) { + $rawLine = $this->lineBuffer; + $this->lineBuffer = null; + } else { + do { + $eof = feof($this->input); + + $rawLine = fgets($this->input); + + if ($eof || (feof($this->input) && $rawLine === false)) { + throw new EofException('End of document reached prematurely'); + } + if ($rawLine === false) { + throw new ParseException('Error reading from input stream'); + } + $rawLine = rtrim($rawLine, "\r\n"); + } while ($rawLine === ''); // Skipping empty lines + $this->lineIndex++; + } + $line = $rawLine; + + $this->startLine = $this->lineIndex; + + // Looking ahead for folded lines. + while (true) { + + $nextLine = rtrim(fgets($this->input), "\r\n"); + $this->lineIndex++; + if (!$nextLine) { + break; + } + if ($nextLine[0] === "\t" || $nextLine[0] === " ") { + $line .= substr($nextLine, 1); + $rawLine .= "\n " . substr($nextLine, 1); + } else { + $this->lineBuffer = $nextLine; + break; + } + + } + $this->rawLine = $rawLine; + return $line; + + } + + /** + * Reads a property or component from a line. + * + * @return void + */ + protected function readProperty($line) { + + if ($this->options & self::OPTION_FORGIVING) { + $propNameToken = 'A-Z0-9\-\._\\/'; + } else { + $propNameToken = 'A-Z0-9\-\.'; + } + + $paramNameToken = 'A-Z0-9\-'; + $safeChar = '^";:,'; + $qSafeChar = '^"'; + + $regex = "/ + ^(?P<name> [$propNameToken]+ ) (?=[;:]) # property name + | + (?<=:)(?P<propValue> .+)$ # property value + | + ;(?P<paramName> [$paramNameToken]+) (?=[=;:]) # parameter name + | + (=|,)(?P<paramValue> # parameter value + (?: [$safeChar]*) | + \"(?: [$qSafeChar]+)\" + ) (?=[;:,]) + /xi"; + + //echo $regex, "\n"; die(); + preg_match_all($regex, $line, $matches, PREG_SET_ORDER); + + $property = [ + 'name' => null, + 'parameters' => [], + 'value' => null + ]; + + $lastParam = null; + + /** + * Looping through all the tokens. + * + * Note that we are looping through them in reverse order, because if a + * sub-pattern matched, the subsequent named patterns will not show up + * in the result. + */ + foreach ($matches as $match) { + + if (isset($match['paramValue'])) { + if ($match['paramValue'] && $match['paramValue'][0] === '"') { + $value = substr($match['paramValue'], 1, -1); + } else { + $value = $match['paramValue']; + } + + $value = $this->unescapeParam($value); + + if (is_null($lastParam)) { + throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions'); + } + if (is_null($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam] = $value; + } elseif (is_array($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam][] = $value; + } else { + $property['parameters'][$lastParam] = [ + $property['parameters'][$lastParam], + $value + ]; + } + continue; + } + if (isset($match['paramName'])) { + $lastParam = strtoupper($match['paramName']); + if (!isset($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam] = null; + } + continue; + } + if (isset($match['propValue'])) { + $property['value'] = $match['propValue']; + continue; + } + if (isset($match['name']) && $match['name']) { + $property['name'] = strtoupper($match['name']); + continue; + } + + // @codeCoverageIgnoreStart + throw new \LogicException('This code should not be reachable'); + // @codeCoverageIgnoreEnd + + } + + if (is_null($property['value'])) { + $property['value'] = ''; + } + if (!$property['name']) { + if ($this->options & self::OPTION_IGNORE_INVALID_LINES) { + return false; + } + throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions'); + } + + // vCard 2.1 states that parameters may appear without a name, and only + // a value. We can deduce the value based on it's name. + // + // Our parser will get those as parameters without a value instead, so + // we're filtering these parameters out first. + $namedParameters = []; + $namelessParameters = []; + + foreach ($property['parameters'] as $name => $value) { + if (!is_null($value)) { + $namedParameters[$name] = $value; + } else { + $namelessParameters[] = $name; + } + } + + $propObj = $this->root->createProperty($property['name'], null, $namedParameters); + + foreach ($namelessParameters as $namelessParameter) { + $propObj->add(null, $namelessParameter); + } + + if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') { + $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue()); + } else { + $charset = $this->charset; + if ($this->root->getDocumentType() === Document::VCARD21 && isset($propObj['CHARSET'])) { + // vCard 2.1 allows the character set to be specified per property. + $charset = (string)$propObj['CHARSET']; + } + switch ($charset) { + case 'UTF-8' : + break; + case 'ISO-8859-1' : + $property['value'] = utf8_encode($property['value']); + break; + case 'Windows-1252' : + $property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset); + break; + default : + throw new ParseException('Unsupported CHARSET: ' . $propObj['CHARSET']); + } + $propObj->setRawMimeDirValue($property['value']); + } + + return $propObj; + + } + + /** + * Unescapes a property value. + * + * vCard 2.1 says: + * * Semi-colons must be escaped in some property values, specifically + * ADR, ORG and N. + * * Semi-colons must be escaped in parameter values, because semi-colons + * are also use to separate values. + * * No mention of escaping backslashes with another backslash. + * * newlines are not escaped either, instead QUOTED-PRINTABLE is used to + * span values over more than 1 line. + * + * vCard 3.0 says: + * * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be + * escaped, all time time. + * * Comma's are used for delimeters in multiple values + * * (rfc2426) Adds to to this that the semi-colon MUST also be escaped, + * as in some properties semi-colon is used for separators. + * * Properties using semi-colons: N, ADR, GEO, ORG + * * Both ADR and N's individual parts may be broken up further with a + * comma. + * * Properties using commas: NICKNAME, CATEGORIES + * + * vCard 4.0 (rfc6350) says: + * * Commas must be escaped. + * * Semi-colons may be escaped, an unescaped semi-colon _may_ be a + * delimiter, depending on the property. + * * Backslashes must be escaped + * * Newlines must be escaped as either \N or \n. + * * Some compound properties may contain multiple parts themselves, so a + * comma within a semi-colon delimited property may also be unescaped + * to denote multiple parts _within_ the compound property. + * * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP. + * * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID. + * + * Even though the spec says that commas must always be escaped, the + * example for GEO in Section 6.5.2 seems to violate this. + * + * iCalendar 2.0 (rfc5545) says: + * * Commas or semi-colons may be used as delimiters, depending on the + * property. + * * Commas, semi-colons, backslashes, newline (\N or \n) are always + * escaped, unless they are delimiters. + * * Colons shall not be escaped. + * * Commas can be considered the 'default delimiter' and is described as + * the delimiter in cases where the order of the multiple values is + * insignificant. + * * Semi-colons are described as the delimiter for 'structured values'. + * They are specifically used in Semi-colons are used as a delimiter in + * REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however. + * + * Now for the parameters + * + * If delimiter is not set (null) this method will just return a string. + * If it's a comma or a semi-colon the string will be split on those + * characters, and always return an array. + * + * @param string $input + * @param string $delimiter + * + * @return string|string[] + */ + static function unescapeValue($input, $delimiter = ';') { + + $regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )'; + if ($delimiter) { + $regex .= ' | (' . $delimiter . ')'; + } + $regex .= ') #x'; + + $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + $resultArray = []; + $result = ''; + + foreach ($matches as $match) { + + switch ($match) { + case '\\\\' : + $result .= '\\'; + break; + case '\N' : + case '\n' : + $result .= "\n"; + break; + case '\;' : + $result .= ';'; + break; + case '\,' : + $result .= ','; + break; + case $delimiter : + $resultArray[] = $result; + $result = ''; + break; + default : + $result .= $match; + break; + + } + + } + + $resultArray[] = $result; + return $delimiter ? $resultArray : $result; + + } + + /** + * Unescapes a parameter value. + * + * vCard 2.1: + * * Does not mention a mechanism for this. In addition, double quotes + * are never used to wrap values. + * * This means that parameters can simply not contain colons or + * semi-colons. + * + * vCard 3.0 (rfc2425, rfc2426): + * * Parameters _may_ be surrounded by double quotes. + * * If this is not the case, semi-colon, colon and comma may simply not + * occur (the comma used for multiple parameter values though). + * * If it is surrounded by double-quotes, it may simply not contain + * double-quotes. + * * This means that a parameter can in no case encode double-quotes, or + * newlines. + * + * vCard 4.0 (rfc6350) + * * Behavior seems to be identical to vCard 3.0 + * + * iCalendar 2.0 (rfc5545) + * * Behavior seems to be identical to vCard 3.0 + * + * Parameter escaping mechanism (rfc6868) : + * * This rfc describes a new way to escape parameter values. + * * New-line is encoded as ^n + * * ^ is encoded as ^^. + * * " is encoded as ^' + * + * @param string $input + * + * @return void + */ + private function unescapeParam($input) { + + return + preg_replace_callback( + '#(\^(\^|n|\'))#', + function($matches) { + switch ($matches[2]) { + case 'n' : + return "\n"; + case '^' : + return '^'; + case '\'' : + return '"'; + + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + }, + $input + ); + } + + /** + * Gets the full quoted printable value. + * + * We need a special method for this, because newlines have both a meaning + * in vCards, and in QuotedPrintable. + * + * This method does not do any decoding. + * + * @return string + */ + private function extractQuotedPrintableValue() { + + // We need to parse the raw line again to get the start of the value. + // + // We are basically looking for the first colon (:), but we need to + // skip over the parameters first, as they may contain one. + $regex = '/^ + (?: [^:])+ # Anything but a colon + (?: "[^"]")* # A parameter in double quotes + : # start of the value we really care about + (.*)$ + /xs'; + + preg_match($regex, $this->rawLine, $matches); + + $value = $matches[1]; + // Removing the first whitespace character from every line. Kind of + // like unfolding, but we keep the newline. + $value = str_replace("\n ", "\n", $value); + + // Microsoft products don't always correctly fold lines, they may be + // missing a whitespace. So if 'forgiving' is turned on, we will take + // those as well. + if ($this->options & self::OPTION_FORGIVING) { + while (substr($value, -1) === '=') { + // Reading the line + $this->readLine(); + // Grabbing the raw form + $value .= "\n" . $this->rawLine; + } + } + + return $value; + + } + +} diff --git a/vendor/sabre/vobject/lib/Parser/Parser.php b/vendor/sabre/vobject/lib/Parser/Parser.php new file mode 100644 index 000000000..ca8bc0add --- /dev/null +++ b/vendor/sabre/vobject/lib/Parser/Parser.php @@ -0,0 +1,80 @@ +<?php + +namespace Sabre\VObject\Parser; + +/** + * Abstract parser. + * + * This class serves as a base-class for the different parsers. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Parser { + + /** + * Turning on this option makes the parser more forgiving. + * + * In the case of the MimeDir parser, this means that the parser will + * accept slashes and underscores in property names, and it will also + * attempt to fix Microsoft vCard 2.1's broken line folding. + */ + 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; + + /** + * Bitmask of parser options. + * + * @var int + */ + protected $options; + + /** + * Creates the parser. + * + * Optionally, it's possible to parse the input stream here. + * + * @param mixed $input + * @param int $options Any parser options (OPTION constants). + * + * @return void + */ + function __construct($input = null, $options = 0) { + + if (!is_null($input)) { + $this->setInput($input); + } + $this->options = $options; + } + + /** + * This method starts the parsing process. + * + * If the input was not supplied during construction, it's possible to pass + * it here instead. + * + * If either input or options are not supplied, the defaults will be used. + * + * @param mixed $input + * @param int $options + * + * @return array + */ + abstract function parse($input = null, $options = 0); + + /** + * Sets the input data. + * + * @param mixed $input + * + * @return void + */ + abstract function setInput($input); + +} diff --git a/vendor/sabre/vobject/lib/Parser/XML.php b/vendor/sabre/vobject/lib/Parser/XML.php new file mode 100644 index 000000000..060a7fe2e --- /dev/null +++ b/vendor/sabre/vobject/lib/Parser/XML.php @@ -0,0 +1,428 @@ +<?php + +namespace Sabre\VObject\Parser; + +use Sabre\VObject\Component; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\EofException; +use Sabre\VObject\ParseException; +use Sabre\Xml as SabreXml; + +/** + * XML Parser. + * + * This parser parses both the xCal and xCard formats. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Ivan Enderlin + * @license http://sabre.io/license/ Modified BSD License + */ +class XML extends Parser { + + const XCAL_NAMESPACE = 'urn:ietf:params:xml:ns:icalendar-2.0'; + const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0'; + + /** + * The input data. + * + * @var array + */ + protected $input; + + /** + * A pointer/reference to the input. + * + * @var array + */ + private $pointer; + + /** + * Document, root component. + * + * @var Sabre\VObject\Document + */ + protected $root; + + /** + * Creates the parser. + * + * Optionally, it's possible to parse the input stream here. + * + * @param mixed $input + * @param int $options Any parser options (OPTION constants). + * + * @return void + */ + function __construct($input = null, $options = 0) { + + if (0 === $options) { + $options = parent::OPTION_FORGIVING; + } + + parent::__construct($input, $options); + + } + + /** + * Parse xCal or xCard. + * + * @param resource|string $input + * @param int $options + * + * @throws \Exception + * + * @return Sabre\VObject\Document + */ + function parse($input = null, $options = 0) { + + if (!is_null($input)) { + $this->setInput($input); + } + + if (0 !== $options) { + $this->options = $options; + } + + if (is_null($this->input)) { + throw new EofException('End of input stream, or no input supplied'); + } + + switch ($this->input['name']) { + + case '{' . self::XCAL_NAMESPACE . '}icalendar': + $this->root = new VCalendar([], false); + $this->pointer = &$this->input['value'][0]; + $this->parseVCalendarComponents($this->root); + break; + + case '{' . self::XCARD_NAMESPACE . '}vcards': + foreach ($this->input['value'] as &$vCard) { + + $this->root = new VCard(['version' => '4.0'], false); + $this->pointer = &$vCard; + $this->parseVCardComponents($this->root); + + // We just parse the first <vcard /> element. + break; + + } + break; + + default: + throw new ParseException('Unsupported XML standard'); + + } + + return $this->root; + } + + /** + * Parse a xCalendar component. + * + * @param Component $parentComponent + * + * @return void + */ + protected function parseVCalendarComponents(Component $parentComponent) { + + foreach ($this->pointer['value'] ?: [] as $children) { + + switch (static::getTagName($children['name'])) { + + case 'properties': + $this->pointer = &$children['value']; + $this->parseProperties($parentComponent); + break; + + case 'components': + $this->pointer = &$children; + $this->parseComponent($parentComponent); + break; + } + } + + } + + /** + * Parse a xCard component. + * + * @param Component $parentComponent + * + * @return void + */ + protected function parseVCardComponents(Component $parentComponent) { + + $this->pointer = &$this->pointer['value']; + $this->parseProperties($parentComponent); + + } + + /** + * Parse xCalendar and xCard properties. + * + * @param Component $parentComponent + * @param string $propertyNamePrefix + * + * @return void + */ + protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '') { + + foreach ($this->pointer ?: [] as $xmlProperty) { + + list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']); + + $propertyName = $tagName; + $propertyValue = []; + $propertyParameters = []; + $propertyType = 'text'; + + // A property which is not part of the standard. + if ($namespace !== self::XCAL_NAMESPACE + && $namespace !== self::XCARD_NAMESPACE) { + + $propertyName = 'xml'; + $value = '<' . $tagName . ' xmlns="' . $namespace . '"'; + + foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) { + $value .= ' ' . $attributeName . '="' . str_replace('"', '\"', $attributeValue) . '"'; + } + + $value .= '>' . $xmlProperty['value'] . '</' . $tagName . '>'; + + $propertyValue = [$value]; + + $this->createProperty( + $parentComponent, + $propertyName, + $propertyParameters, + $propertyType, + $propertyValue + ); + + continue; + } + + // xCard group. + if ($propertyName === 'group') { + + if (!isset($xmlProperty['attributes']['name'])) { + continue; + } + + $this->pointer = &$xmlProperty['value']; + $this->parseProperties( + $parentComponent, + strtoupper($xmlProperty['attributes']['name']) . '.' + ); + + continue; + + } + + // Collect parameters. + foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) { + + if (!is_array($xmlPropertyChild) + || 'parameters' !== static::getTagName($xmlPropertyChild['name'])) + continue; + + $xmlParameters = $xmlPropertyChild['value']; + + foreach ($xmlParameters as $xmlParameter) { + + $propertyParameterValues = []; + + foreach ($xmlParameter['value'] as $xmlParameterValues) { + $propertyParameterValues[] = $xmlParameterValues['value']; + } + + $propertyParameters[static::getTagName($xmlParameter['name'])] + = implode(',', $propertyParameterValues); + + } + + array_splice($xmlProperty['value'], $i, 1); + + } + + $propertyNameExtended = ($this->root instanceof VCalendar + ? 'xcal' + : 'xcard') . ':' . $propertyName; + + switch ($propertyNameExtended) { + + case 'xcal:geo': + $propertyType = 'float'; + $propertyValue['latitude'] = 0; + $propertyValue['longitude'] = 0; + + foreach ($xmlProperty['value'] as $xmlRequestChild) { + $propertyValue[static::getTagName($xmlRequestChild['name'])] + = $xmlRequestChild['value']; + } + break; + + case 'xcal:request-status': + $propertyType = 'text'; + + foreach ($xmlProperty['value'] as $xmlRequestChild) { + $propertyValue[static::getTagName($xmlRequestChild['name'])] + = $xmlRequestChild['value']; + } + break; + + case 'xcal:freebusy': + $propertyType = 'freebusy'; + // We don't break because we only want to set + // another property type. + + case 'xcal:categories': + case 'xcal:resources': + case 'xcal:exdate': + foreach ($xmlProperty['value'] as $specialChild) { + $propertyValue[static::getTagName($specialChild['name'])] + = $specialChild['value']; + } + break; + + case 'xcal:rdate': + $propertyType = 'date-time'; + + foreach ($xmlProperty['value'] as $specialChild) { + + $tagName = static::getTagName($specialChild['name']); + + if ('period' === $tagName) { + + $propertyParameters['value'] = 'PERIOD'; + $propertyValue[] = implode('/', $specialChild['value']); + + } + else { + $propertyValue[] = $specialChild['value']; + } + } + break; + + default: + $propertyType = static::getTagName($xmlProperty['value'][0]['name']); + + foreach ($xmlProperty['value'] as $value) { + $propertyValue[] = $value['value']; + } + + if ('date' === $propertyType) { + $propertyParameters['value'] = 'DATE'; + } + break; + } + + $this->createProperty( + $parentComponent, + $propertyNamePrefix . $propertyName, + $propertyParameters, + $propertyType, + $propertyValue + ); + + } + + } + + /** + * Parse a component. + * + * @param Component $parentComponent + * + * @return void + */ + protected function parseComponent(Component $parentComponent) { + + $components = $this->pointer['value'] ?: []; + + foreach ($components as $component) { + + $componentName = static::getTagName($component['name']); + $currentComponent = $this->root->createComponent( + $componentName, + null, + false + ); + + $this->pointer = &$component; + $this->parseVCalendarComponents($currentComponent); + + $parentComponent->add($currentComponent); + + } + + } + + /** + * Create a property. + * + * @param Component $parentComponent + * @param string $name + * @param array $parameters + * @param string $type + * @param mixed $value + * + * @return void + */ + protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value) { + + $property = $this->root->createProperty( + $name, + null, + $parameters, + $type + ); + $parentComponent->add($property); + $property->setXmlValue($value); + + } + + /** + * Sets the input data. + * + * @param resource|string $input + * + * @return void + */ + function setInput($input) { + + if (is_resource($input)) { + $input = stream_get_contents($input); + } + + if (is_string($input)) { + + $reader = new SabreXml\Reader(); + $reader->elementMap['{' . self::XCAL_NAMESPACE . '}period'] + = 'Sabre\VObject\Parser\XML\Element\KeyValue'; + $reader->elementMap['{' . self::XCAL_NAMESPACE . '}recur'] + = 'Sabre\VObject\Parser\XML\Element\KeyValue'; + $reader->xml($input); + $input = $reader->parse(); + + } + + $this->input = $input; + + } + + /** + * Get tag name from a Clark notation. + * + * @param string $clarkedTagName + * + * @return string + */ + protected static function getTagName($clarkedTagName) { + + list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName); + return $tagName; + + } +} diff --git a/vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php b/vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php new file mode 100644 index 000000000..14d798433 --- /dev/null +++ b/vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php @@ -0,0 +1,70 @@ +<?php + +namespace Sabre\VObject\Parser\XML\Element; + +use Sabre\Xml as SabreXml; + +/** + * Our own sabre/xml key-value element. + * + * It just removes the clark notation. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Ivan Enderlin + * @license http://sabre.io/license/ Modified BSD License + */ +class KeyValue extends SabreXml\Element\KeyValue { + + /** + * The deserialize method is called during xml parsing. + * + * This method is called staticly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param XML\Reader $reader + * + * @return mixed + */ + static function xmlDeserialize(SabreXml\Reader $reader) { + + // If there's no children, we don't do anything. + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + + $values = []; + $reader->read(); + + do { + + if ($reader->nodeType === SabreXml\Reader::ELEMENT) { + + $name = $reader->localName; + $values[$name] = $reader->parseCurrentElement()['value']; + + } else { + $reader->read(); + } + + } while ($reader->nodeType !== SabreXml\Reader::END_ELEMENT); + + $reader->read(); + + return $values; + + } + +} |