/** * 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 = "/\n ^(?P<name> [{$propNameToken}]+ ) (?=[;:]) # property name\n |\n (?<=:)(?P<propValue> .+)\$ # property value\n |\n ;(?P<paramName> [{$paramNameToken}]+) (?=[=;:]) # parameter name\n |\n (=|,)(?P<paramValue> # parameter value\n (?: [{$safeChar}]*) |\n \"(?: [{$qSafeChar}]+)\"\n ) (?=[;:,])\n /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; }