1
0
mirror of https://github.com/danog/postgres.git synced 2024-12-02 09:27:54 +01:00
postgres/lib/Internal/ArrayParser.php
2018-01-23 20:01:23 -06:00

129 lines
4.0 KiB
PHP

<?php
namespace Amp\Postgres\Internal;
use Amp\Postgres\ParseException;
class ArrayParser {
/**
* @param string $data String representation of PostgreSQL array.
* @param callable|null $cast Callback to cast parsed values.
* @param string $delimiter Delimiter used to separate values.
*
* @return array Parsed column data.
*
* @throws \Amp\Postgres\ParseException
*/
public function parse(string $data, callable $cast = null, string $delimiter = ','): array {
$data = \trim($data);
if ($data[0] !== '{' || \substr($data, -1) !== '}') {
throw new ParseException("Missing opening or closing brackets");
}
$parser = $this->parser($data, $cast, $delimiter);
$data = \iterator_to_array($parser);
if ($parser->getReturn() !== '') {
throw new ParseException("Data left in buffer after parsing");
}
return $data;
}
/**
* Recursive generator parser yielding array values.
*
* @param string $data Remaining buffer data.
* @param callable|null $cast Callback to cast parsed values.
* @param string $delimiter Delimiter used to separate values.
*
* @return \Generator
*
* @throws \Amp\Postgres\ParseException
*/
private function parser(string $data, callable $cast = null, string $delimiter = ','): \Generator {
$data = \ltrim(\substr($data, 1));
do {
if ($data === '') {
throw new ParseException("Missing closing bracket");
}
if ($data[0] === '{') { // Array
$parser = $this->parser($data, $cast, $delimiter);
yield \iterator_to_array($parser);
$data = $parser->getReturn();
$end = $this->trim($data, 0, $delimiter);
continue;
}
if ($data[0] === '"') { // Quoted value
for ($position = 1; isset($data[$position]); ++$position) {
if ($data[$position] === '\\') {
++$position; // Skip next character
continue;
}
if ($data[$position] === '"') {
break;
}
}
if (!isset($data[$position])) {
throw new ParseException("Could not find matching quote in quoted value");
}
$yield = \stripslashes(\substr($data, 1, $position - 1));
$end = $this->trim($data, $position + 1, $delimiter);
} else { // Unquoted value
$position = 0;
while (isset($data[$position]) && $data[$position] !== $delimiter && $data[$position] !== '}') {
++$position;
}
$yield = \trim(\substr($data, 0, $position));
$end = $this->trim($data, $position, $delimiter);
if (\strcasecmp($yield, "NULL") === 0) { // Literal NULL is always unquoted.
yield null;
continue;
}
}
yield $cast ? $cast($yield) : $yield;
} while ($end !== '}');
return $data;
}
/**
* @param string $data Data trimmed past next delimiter and any whitespace to the right of the delimiter.
* @param int $position Position to start search for delimiter.
* @param string $delimiter Delimiter used to separate values.
*
* @return string First non-whitespace character after given position.
*
* @throws \Amp\Postgres\ParseException
*/
private function trim(string &$data, int $position, string $delimiter): string {
$data = \ltrim(\substr($data, $position));
if ($data === '') {
throw new ParseException("Unexpected end of data");
}
$end = $data[0];
if ($end !== $delimiter && $end !== '}') {
throw new ParseException("Invalid delimiter");
}
$data = \ltrim(\substr($data, 1));
return $end;
}
}