XML.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. namespace Sabre\VObject\Parser;
  3. use Sabre\VObject\Component;
  4. use Sabre\VObject\Component\VCalendar;
  5. use Sabre\VObject\Component\VCard;
  6. use Sabre\VObject\EofException;
  7. use Sabre\VObject\ParseException;
  8. use Sabre\Xml as SabreXml;
  9. /**
  10. * XML Parser.
  11. *
  12. * This parser parses both the xCal and xCard formats.
  13. *
  14. * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
  15. * @author Ivan Enderlin
  16. * @license http://sabre.io/license/ Modified BSD License
  17. */
  18. class XML extends Parser
  19. {
  20. const XCAL_NAMESPACE = 'urn:ietf:params:xml:ns:icalendar-2.0';
  21. const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0';
  22. /**
  23. * The input data.
  24. *
  25. * @var array
  26. */
  27. protected $input;
  28. /**
  29. * A pointer/reference to the input.
  30. *
  31. * @var array
  32. */
  33. private $pointer;
  34. /**
  35. * Document, root component.
  36. *
  37. * @var \Sabre\VObject\Document
  38. */
  39. protected $root;
  40. /**
  41. * Creates the parser.
  42. *
  43. * Optionally, it's possible to parse the input stream here.
  44. *
  45. * @param mixed $input
  46. * @param int $options any parser options (OPTION constants)
  47. */
  48. public function __construct($input = null, $options = 0)
  49. {
  50. if (0 === $options) {
  51. $options = parent::OPTION_FORGIVING;
  52. }
  53. parent::__construct($input, $options);
  54. }
  55. /**
  56. * Parse xCal or xCard.
  57. *
  58. * @param resource|string $input
  59. * @param int $options
  60. *
  61. * @throws \Exception
  62. *
  63. * @return \Sabre\VObject\Document
  64. */
  65. public function parse($input = null, $options = 0)
  66. {
  67. if (!is_null($input)) {
  68. $this->setInput($input);
  69. }
  70. if (0 !== $options) {
  71. $this->options = $options;
  72. }
  73. if (is_null($this->input)) {
  74. throw new EofException('End of input stream, or no input supplied');
  75. }
  76. switch ($this->input['name']) {
  77. case '{'.self::XCAL_NAMESPACE.'}icalendar':
  78. $this->root = new VCalendar([], false);
  79. $this->pointer = &$this->input['value'][0];
  80. $this->parseVCalendarComponents($this->root);
  81. break;
  82. case '{'.self::XCARD_NAMESPACE.'}vcards':
  83. foreach ($this->input['value'] as &$vCard) {
  84. $this->root = new VCard(['version' => '4.0'], false);
  85. $this->pointer = &$vCard;
  86. $this->parseVCardComponents($this->root);
  87. // We just parse the first <vcard /> element.
  88. break;
  89. }
  90. break;
  91. default:
  92. throw new ParseException('Unsupported XML standard');
  93. }
  94. return $this->root;
  95. }
  96. /**
  97. * Parse a xCalendar component.
  98. */
  99. protected function parseVCalendarComponents(Component $parentComponent)
  100. {
  101. foreach ($this->pointer['value'] ?: [] as $children) {
  102. switch (static::getTagName($children['name'])) {
  103. case 'properties':
  104. $this->pointer = &$children['value'];
  105. $this->parseProperties($parentComponent);
  106. break;
  107. case 'components':
  108. $this->pointer = &$children;
  109. $this->parseComponent($parentComponent);
  110. break;
  111. }
  112. }
  113. }
  114. /**
  115. * Parse a xCard component.
  116. */
  117. protected function parseVCardComponents(Component $parentComponent)
  118. {
  119. $this->pointer = &$this->pointer['value'];
  120. $this->parseProperties($parentComponent);
  121. }
  122. /**
  123. * Parse xCalendar and xCard properties.
  124. *
  125. * @param string $propertyNamePrefix
  126. */
  127. protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '')
  128. {
  129. foreach ($this->pointer ?: [] as $xmlProperty) {
  130. list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
  131. $propertyName = $tagName;
  132. $propertyValue = [];
  133. $propertyParameters = [];
  134. $propertyType = 'text';
  135. // A property which is not part of the standard.
  136. if (self::XCAL_NAMESPACE !== $namespace
  137. && self::XCARD_NAMESPACE !== $namespace) {
  138. $propertyName = 'xml';
  139. $value = '<'.$tagName.' xmlns="'.$namespace.'"';
  140. foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) {
  141. $value .= ' '.$attributeName.'="'.str_replace('"', '\"', $attributeValue).'"';
  142. }
  143. $value .= '>'.$xmlProperty['value'].'</'.$tagName.'>';
  144. $propertyValue = [$value];
  145. $this->createProperty(
  146. $parentComponent,
  147. $propertyName,
  148. $propertyParameters,
  149. $propertyType,
  150. $propertyValue
  151. );
  152. continue;
  153. }
  154. // xCard group.
  155. if ('group' === $propertyName) {
  156. if (!isset($xmlProperty['attributes']['name'])) {
  157. continue;
  158. }
  159. $this->pointer = &$xmlProperty['value'];
  160. $this->parseProperties(
  161. $parentComponent,
  162. strtoupper($xmlProperty['attributes']['name']).'.'
  163. );
  164. continue;
  165. }
  166. // Collect parameters.
  167. foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) {
  168. if (!is_array($xmlPropertyChild)
  169. || 'parameters' !== static::getTagName($xmlPropertyChild['name'])) {
  170. continue;
  171. }
  172. $xmlParameters = $xmlPropertyChild['value'];
  173. foreach ($xmlParameters as $xmlParameter) {
  174. $propertyParameterValues = [];
  175. foreach ($xmlParameter['value'] as $xmlParameterValues) {
  176. $propertyParameterValues[] = $xmlParameterValues['value'];
  177. }
  178. $propertyParameters[static::getTagName($xmlParameter['name'])]
  179. = implode(',', $propertyParameterValues);
  180. }
  181. array_splice($xmlProperty['value'], $i, 1);
  182. }
  183. $propertyNameExtended = ($this->root instanceof VCalendar
  184. ? 'xcal'
  185. : 'xcard').':'.$propertyName;
  186. switch ($propertyNameExtended) {
  187. case 'xcal:geo':
  188. $propertyType = 'float';
  189. $propertyValue['latitude'] = 0;
  190. $propertyValue['longitude'] = 0;
  191. foreach ($xmlProperty['value'] as $xmlRequestChild) {
  192. $propertyValue[static::getTagName($xmlRequestChild['name'])]
  193. = $xmlRequestChild['value'];
  194. }
  195. break;
  196. case 'xcal:request-status':
  197. $propertyType = 'text';
  198. foreach ($xmlProperty['value'] as $xmlRequestChild) {
  199. $propertyValue[static::getTagName($xmlRequestChild['name'])]
  200. = $xmlRequestChild['value'];
  201. }
  202. break;
  203. case 'xcal:freebusy':
  204. $propertyType = 'freebusy';
  205. // We don't break because we only want to set
  206. // another property type.
  207. // no break
  208. case 'xcal:categories':
  209. case 'xcal:resources':
  210. case 'xcal:exdate':
  211. foreach ($xmlProperty['value'] as $specialChild) {
  212. $propertyValue[static::getTagName($specialChild['name'])]
  213. = $specialChild['value'];
  214. }
  215. break;
  216. case 'xcal:rdate':
  217. $propertyType = 'date-time';
  218. foreach ($xmlProperty['value'] as $specialChild) {
  219. $tagName = static::getTagName($specialChild['name']);
  220. if ('period' === $tagName) {
  221. $propertyParameters['value'] = 'PERIOD';
  222. $propertyValue[] = implode('/', $specialChild['value']);
  223. } else {
  224. $propertyValue[] = $specialChild['value'];
  225. }
  226. }
  227. break;
  228. default:
  229. $propertyType = static::getTagName($xmlProperty['value'][0]['name']);
  230. foreach ($xmlProperty['value'] as $value) {
  231. $propertyValue[] = $value['value'];
  232. }
  233. if ('date' === $propertyType) {
  234. $propertyParameters['value'] = 'DATE';
  235. }
  236. break;
  237. }
  238. $this->createProperty(
  239. $parentComponent,
  240. $propertyNamePrefix.$propertyName,
  241. $propertyParameters,
  242. $propertyType,
  243. $propertyValue
  244. );
  245. }
  246. }
  247. /**
  248. * Parse a component.
  249. */
  250. protected function parseComponent(Component $parentComponent)
  251. {
  252. $components = $this->pointer['value'] ?: [];
  253. foreach ($components as $component) {
  254. $componentName = static::getTagName($component['name']);
  255. $currentComponent = $this->root->createComponent(
  256. $componentName,
  257. null,
  258. false
  259. );
  260. $this->pointer = &$component;
  261. $this->parseVCalendarComponents($currentComponent);
  262. $parentComponent->add($currentComponent);
  263. }
  264. }
  265. /**
  266. * Create a property.
  267. *
  268. * @param string $name
  269. * @param array $parameters
  270. * @param string $type
  271. * @param mixed $value
  272. */
  273. protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value)
  274. {
  275. $property = $this->root->createProperty(
  276. $name,
  277. null,
  278. $parameters,
  279. $type
  280. );
  281. $parentComponent->add($property);
  282. $property->setXmlValue($value);
  283. }
  284. /**
  285. * Sets the input data.
  286. *
  287. * @param resource|string $input
  288. */
  289. public function setInput($input)
  290. {
  291. if (is_resource($input)) {
  292. $input = stream_get_contents($input);
  293. }
  294. if (is_string($input)) {
  295. $reader = new SabreXml\Reader();
  296. $reader->elementMap['{'.self::XCAL_NAMESPACE.'}period']
  297. = XML\Element\KeyValue::class;
  298. $reader->elementMap['{'.self::XCAL_NAMESPACE.'}recur']
  299. = XML\Element\KeyValue::class;
  300. $reader->xml($input);
  301. $input = $reader->parse();
  302. }
  303. $this->input = $input;
  304. }
  305. /**
  306. * Get tag name from a Clark notation.
  307. *
  308. * @param string $clarkedTagName
  309. *
  310. * @return string
  311. */
  312. protected static function getTagName($clarkedTagName)
  313. {
  314. list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName);
  315. return $tagName;
  316. }
  317. }