mirror of
https://github.com/OpenXE-org/OpenXE.git
synced 2024-12-27 07:00:29 +01:00
386 lines
11 KiB
PHP
386 lines
11 KiB
PHP
<?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)
|
|
*/
|
|
public 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
|
|
*/
|
|
public 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
|
|
*/
|
|
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
|
|
*/
|
|
protected function parseVCardComponents(Component $parentComponent)
|
|
{
|
|
$this->pointer = &$this->pointer['value'];
|
|
$this->parseProperties($parentComponent);
|
|
}
|
|
|
|
/**
|
|
* Parse xCalendar and xCard properties.
|
|
*
|
|
* @param Component $parentComponent
|
|
* @param string $propertyNamePrefix
|
|
*/
|
|
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 (self::XCAL_NAMESPACE !== $namespace
|
|
&& self::XCARD_NAMESPACE !== $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 ('group' === $propertyName) {
|
|
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.
|
|
|
|
// no break
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
public 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;
|
|
}
|
|
}
|