diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76daa54f..47bf45fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: run: composer install --no-interaction --prefer-dist --optimize-autoloader - name: PHPCS checks - run: ./vendor/bin/phpcs ${{ github.workspace }}/src + run: ./vendor/bin/phpcs ${{ github.workspace }}/src/WordPress test-unit: runs-on: ${{ matrix.os }} diff --git a/composer.json b/composer.json index 9eeb687e..729d401f 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ "nette/php-generator": "*", "jane-php/json-schema": "*", "bamarni/composer-bin-plugin": "*", - "wp-coding-standards/wpcs": "3.0" + "wp-coding-standards/wpcs": "3.0", + "phpcompatibility/php-compatibility": "^9.3" }, "config": { "optimize-autoloader": true, diff --git a/composer.lock b/composer.lock index 46abc4a7..3b34e33b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "71709bd85d8f5389e7b741c8f505994c", + "content-hash": "3fe66d0e097d4dbd4858fa622220f097", "packages": [ { "name": "pimple/pimple", @@ -1858,6 +1858,68 @@ }, "time": "2022-01-11T14:28:07+00:00" }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, { "name": "phpcsstandards/phpcsextra", "version": "1.2.1", diff --git a/phpcs.xml b/phpcs.xml index 5645e28d..d5acadf5 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,6 +3,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/WordPress/AsyncHttp/Client.php b/src/WordPress/AsyncHttp/Client.php index a5e68f60..0d7484ab 100644 --- a/src/WordPress/AsyncHttp/Client.php +++ b/src/WordPress/AsyncHttp/Client.php @@ -94,7 +94,7 @@ class Client { protected $queue_needs_processing = false; public function __construct() { - $this->requests = new Map(); + $this->requests = new Map(); $this->onProgress = function () { }; } @@ -163,13 +163,13 @@ public function get_stream( $request ) { } /** - * @param \WordPress\AsyncHttp\Request $request - */ - protected function enqueue_request( $request ) { - $stream = StreamWrapper::create_resource( + * @param \WordPress\AsyncHttp\Request $request + */ + protected function enqueue_request( $request ) { + $stream = StreamWrapper::create_resource( new StreamData( $request, $this ) ); - $this->requests[ $request ] = new RequestInfo( $stream ); + $this->requests[ $request ] = new RequestInfo( $stream ); $this->queue_needs_processing = true; return $stream; @@ -182,18 +182,18 @@ public function process_queue() { $this->queue_needs_processing = false; $active_requests = count( $this->get_streamed_requests() ); - $backfill = $this->concurrency - $active_requests; + $backfill = $this->concurrency - $active_requests; if ( $backfill <= 0 ) { return; } - $enqueued = array_slice( $this->get_enqueued_request(), 0, $backfill ); + $enqueued = array_slice( $this->get_enqueued_request(), 0, $backfill ); list( $streams, $response_headers ) = streams_send_http_requests( $enqueued ); foreach ( $streams as $k => $stream ) { - $request = $enqueued[ $k ]; - $total = $response_headers[ $k ]['headers']['content-length']; - $this->requests[ $request ]->state = RequestInfo::STATE_STREAMING; + $request = $enqueued[ $k ]; + $total = $response_headers[ $k ]['headers']['content-length']; + $this->requests[ $request ]->state = RequestInfo::STATE_STREAMING; $this->requests[ $request ]->stream = stream_monitor_progress( $stream, function ( $downloaded ) use ( $request, $total ) { @@ -205,7 +205,7 @@ function ( $downloaded ) use ( $request, $total ) { } protected function get_enqueued_request() { - $enqueued_requests = []; + $enqueued_requests = array(); foreach ( $this->requests as $request => $info ) { if ( $info->state === RequestInfo::STATE_ENQUEUED ) { $enqueued_requests[] = $request; @@ -216,7 +216,7 @@ protected function get_enqueued_request() { } protected function get_streamed_requests() { - $active_requests = []; + $active_requests = array(); foreach ( $this->requests as $request => $info ) { if ( $info->state !== RequestInfo::STATE_ENQUEUED ) { $active_requests[] = $request; @@ -245,12 +245,15 @@ public function read_bytes( $request, $length ) { } $request_info = $this->requests[ $request ]; - $stream = $request_info->stream; + $stream = $request_info->stream; $active_requests = $this->get_streamed_requests(); - $active_streams = array_map( function ( $request ) { - return $this->requests[ $request ]->stream; - }, $active_requests ); + $active_streams = array_map( + function ( $request ) { + return $this->requests[ $request ]->stream; + }, + $active_requests + ); if ( ! count( $active_streams ) ) { return false; @@ -264,7 +267,7 @@ public function read_bytes( $request, $length ) { } if ( strlen( $request_info->buffer ) >= $length ) { - $buffered = substr( $request_info->buffer, 0, $length ); + $buffered = substr( $request_info->buffer, 0, $length ); $request_info->buffer = substr( $request_info->buffer, $length ); return $buffered; @@ -274,9 +277,12 @@ public function read_bytes( $request, $length ) { return $request_info->buffer; } - $active_streams = array_filter( $active_streams, function ( $stream ) { - return ! feof( $stream ); - } ); + $active_streams = array_filter( + $active_streams, + function ( $stream ) { + return ! feof( $stream ); + } + ); if ( ! count( $active_streams ) ) { continue; } diff --git a/src/WordPress/AsyncHttp/Request.php b/src/WordPress/AsyncHttp/Request.php index a7999386..5f681b7c 100644 --- a/src/WordPress/AsyncHttp/Request.php +++ b/src/WordPress/AsyncHttp/Request.php @@ -12,5 +12,4 @@ class Request { public function __construct( string $url ) { $this->url = $url; } - } diff --git a/src/WordPress/AsyncHttp/RequestInfo.php b/src/WordPress/AsyncHttp/RequestInfo.php index b2ddc71c..6cae6c81 100644 --- a/src/WordPress/AsyncHttp/RequestInfo.php +++ b/src/WordPress/AsyncHttp/RequestInfo.php @@ -17,8 +17,7 @@ public function __construct( $stream ) { $this->stream = $stream; } - public function isFinished() { + public function is_finished() { return $this->state === self::STATE_FINISHED; } - } diff --git a/src/WordPress/AsyncHttp/StreamData.php b/src/WordPress/AsyncHttp/StreamData.php index 7bd5546e..0d36f117 100644 --- a/src/WordPress/AsyncHttp/StreamData.php +++ b/src/WordPress/AsyncHttp/StreamData.php @@ -12,7 +12,6 @@ class StreamData extends VanillaStreamWrapperData { public function __construct( Request $request, Client $group ) { parent::__construct( null ); $this->request = $request; - $this->client = $group; + $this->client = $group; } - } diff --git a/src/WordPress/AsyncHttp/StreamWrapper.php b/src/WordPress/AsyncHttp/StreamWrapper.php index f661a4ba..f48f3f23 100644 --- a/src/WordPress/AsyncHttp/StreamWrapper.php +++ b/src/WordPress/AsyncHttp/StreamWrapper.php @@ -31,9 +31,9 @@ public function stream_open( $path, $mode, $options, &$opened_path ) { } /** - * @param int $cast_as - */ - public function stream_cast( $cast_as ) { + * @param int $cast_as + */ + public function stream_cast( $cast_as ) { $this->initialize(); return parent::stream_cast( $cast_as ); @@ -103,7 +103,6 @@ public function stream_seek( $offset, $whence ) { * Let's refuse to call fclose() in that scenario. */ protected function has_valid_stream() { - return get_resource_type( $this->stream ) !== "Unknown"; + return get_resource_type( $this->stream ) !== 'Unknown'; } - } diff --git a/src/WordPress/AsyncHttp/async_http_streams.php b/src/WordPress/AsyncHttp/async_http_streams.php index 0b9e9d84..16a1f58d 100644 --- a/src/WordPress/AsyncHttp/async_http_streams.php +++ b/src/WordPress/AsyncHttp/async_http_streams.php @@ -20,7 +20,7 @@ * @see stream_http_open_nonblocking */ function streams_http_open_nonblocking( $urls ) { - $streams = []; + $streams = array(); foreach ( $urls as $k => $url ) { $streams[ $k ] = stream_http_open_nonblocking( $url ); } @@ -44,9 +44,9 @@ function streams_http_open_nonblocking( $urls ) { * @throws Exception If unable to open the stream. */ function stream_http_open_nonblocking( $url ) { - $parts = parse_url( $url ); + $parts = parse_url( $url ); $scheme = $parts['scheme']; - if ( ! in_array( $scheme, [ 'http', 'https' ] ) ) { + if ( ! in_array( $scheme, array( 'http', 'https' ) ) ) { throw new InvalidArgumentException( 'Invalid scheme – only http:// and https:// URLs are supported' ); } @@ -55,13 +55,13 @@ function stream_http_open_nonblocking( $url ) { // Create stream context $context = stream_context_create( - [ - 'socket' => [ + array( + 'socket' => array( 'isSsl' => $scheme === 'https', 'originalUrl' => $url, 'socketUrl' => 'tcp://' . $host . ':' . $port, - ], - ] + ), + ) ); $stream = stream_socket_client( @@ -92,24 +92,23 @@ function stream_http_open_nonblocking( $url ) { * @throws Exception If there is an error enabling crypto or if stream_select times out. */ function streams_http_requests_send( $streams ) { - $read = $except = null; + $read = $except = null; $remaining_streams = $streams; while ( count( $remaining_streams ) ) { $write = $remaining_streams; sleep( 2 ); + // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged $ready = @stream_select( $read, $write, $except, 5, 5000000 ); if ( $ready === false ) { - throw new Exception( "Error: " . error_get_last()['message'] ); + throw new Exception( 'Error: ' . error_get_last()['message'] ); } elseif ( $ready <= 0 ) { - var_dump( $ready ); - die(); - throw new Exception( "stream_select timed out" ); + throw new Exception( 'stream_select timed out' ); } foreach ( $write as $k => $stream ) { $enabled_crypto = stream_socket_enable_crypto( $stream, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT ); if ( false === $enabled_crypto ) { - throw new Exception( "Failed to enable crypto: " . error_get_last()['message'] ); + throw new Exception( 'Failed to enable crypto: ' . error_get_last()['message'] ); } elseif ( 0 === $enabled_crypto ) { // Wait for the handshake to complete } else { @@ -128,8 +127,8 @@ function streams_http_requests_send( $streams ) { * Waits for response bytes to be available in the given streams. * * @param array $streams The array of streams to wait for. - * @param int $length The number of bytes to read from each stream. - * @param int $timeout_microseconds The timeout in microseconds for the stream_select function. + * @param int $length The number of bytes to read from each stream. + * @param int $timeout_microseconds The timeout in microseconds for the stream_select function. * * @return array|false An array of chunks read from the streams, or false if no streams are available. * @throws Exception If an error occurs during the stream_select operation or if the operation times out. @@ -139,16 +138,17 @@ function streams_http_response_await_bytes( $streams, $length, $timeout_microsec if ( count( $read ) === 0 ) { return false; } - $write = []; + $write = array(); $except = null; + // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged $ready = @stream_select( $read, $write, $except, 0, $timeout_microseconds ); if ( $ready === false ) { - throw new Exception( "Could not retrieve response bytes: " . error_get_last()['message'] ); + throw new Exception( 'Could not retrieve response bytes: ' . error_get_last()['message'] ); } elseif ( $ready <= 0 ) { - throw new Exception( "stream_select timed out" ); + throw new Exception( 'stream_select timed out' ); } - $chunks = []; + $chunks = array(); foreach ( $read as $k => $stream ) { $chunks[ $k ] = fread( $stream, $length ); } @@ -163,29 +163,28 @@ function streams_http_response_await_bytes( $streams, $length, $timeout_microsec * * @return array An array containing the parsed status and headers. */ - function parse_http_headers( string $headers ) { - $lines = explode( "\r\n", $headers ); - $status = array_shift( $lines ); - $status = explode( ' ', $status ); - $status = [ + $lines = explode( "\r\n", $headers ); + $status = array_shift( $lines ); + $status = explode( ' ', $status ); + $status = array( 'protocol' => $status[0], 'code' => $status[1], 'message' => $status[2], - ]; - $headers = []; + ); + $headers = array(); foreach ( $lines as $line ) { - if ( strpos($line, ': ') === false ) { + if ( strpos( $line, ': ' ) === false ) { continue; } - $line = explode( ': ', $line ); + $line = explode( ': ', $line ); $headers[ strtolower( $line[0] ) ] = $line[1]; } - return [ + return array( 'status' => $status, 'headers' => $headers, - ]; + ); } /** @@ -195,11 +194,10 @@ function parse_http_headers( string $headers ) { * * @return string The prepared HTTP request string. */ - function stream_http_prepare_request_bytes( $url ) { - $parts = parse_url( $url ); - $host = $parts['host']; - $path = $parts['path'] . ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' ); + $parts = parse_url( $url ); + $host = $parts['host']; + $path = $parts['path'] . ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' ); $request = << $stream ) { $headers[ $k ] = ''; } @@ -234,7 +232,7 @@ function streams_http_response_await_headers( $streams ) { } foreach ( $bytes as $k => $byte ) { $headers[ $k ] .= $byte; - if ( substr_compare($headers[ $k ], "\r\n\r\n", -strlen("\r\n\r\n")) === 0 ) { + if ( substr_compare( $headers[ $k ], "\r\n\r\n", - strlen( "\r\n\r\n" ) ) === 0 ) { unset( $remaining_streams[ $k ] ); } } @@ -262,7 +260,7 @@ function stream_monitor_progress( $stream, $onProgress ) { $stream, function ( $data ) use ( $onProgress ) { static $streamedBytes = 0; - $streamedBytes += strlen( $data ); + $streamedBytes += strlen( $data ); $onProgress( $streamedBytes ); } ) @@ -278,23 +276,23 @@ function ( $data ) use ( $onProgress ) { * @throws Exception If any of the requests fail with a non-successful HTTP code. */ function streams_send_http_requests( array $requests ) { - $urls = []; + $urls = array(); foreach ( $requests as $k => $request ) { $urls[ $k ] = $request->url; } - $redirects = $urls; - $final_streams = []; - $response_headers = []; + $redirects = $urls; + $final_streams = array(); + $response_headers = array(); do { $streams = streams_http_open_nonblocking( $redirects ); streams_http_requests_send( $streams ); - $redirects = []; - $headers = streams_http_response_await_headers( $streams ); + $redirects = array(); + $headers = streams_http_response_await_headers( $streams ); foreach ( array_keys( $headers ) as $k ) { $code = $headers[ $k ]['status']['code']; if ( $code > 399 || $code < 200 ) { - throw new Exception( "Failed to download file " . $requests[ $k ]->url . ": Server responded with HTTP code " . $code ); + throw new Exception( 'Failed to download file ' . $requests[ $k ]->url . ': Server responded with HTTP code ' . $code ); } if ( isset( $headers[ $k ]['headers']['location'] ) ) { $redirects[ $k ] = $headers[ $k ]['headers']['location']; @@ -302,10 +300,10 @@ function streams_send_http_requests( array $requests ) { continue; } - $final_streams[ $k ] = $streams[ $k ]; + $final_streams[ $k ] = $streams[ $k ]; $response_headers[ $k ] = $headers[ $k ]; } } while ( count( $redirects ) ); - return [ $final_streams, $response_headers ]; + return array( $final_streams, $response_headers ); } diff --git a/src/WordPress/Blueprints/BlueprintException.php b/src/WordPress/Blueprints/BlueprintException.php index a53c7dfc..3fd991cf 100644 --- a/src/WordPress/Blueprints/BlueprintException.php +++ b/src/WordPress/Blueprints/BlueprintException.php @@ -4,7 +4,7 @@ use Exception; -class BlueprintException extends Exception -{ +class BlueprintException extends Exception { + } diff --git a/src/WordPress/Blueprints/BlueprintMapper.php b/src/WordPress/Blueprints/BlueprintMapper.php index 32d62944..c66c89fe 100644 --- a/src/WordPress/Blueprints/BlueprintMapper.php +++ b/src/WordPress/Blueprints/BlueprintMapper.php @@ -24,7 +24,7 @@ public function __construct() { ResourceDefinitionInterface::class => array( $this, 'resource_factory' ), StepDefinitionInterface::class => array( $this, 'step_factory' ), ); - $this->mapper = new JsonMapper( $custom_factories ); + $this->mapper = new JsonMapper( $custom_factories ); } /** diff --git a/src/WordPress/Blueprints/BlueprintParser.php b/src/WordPress/Blueprints/BlueprintParser.php index 728dcef6..28374406 100644 --- a/src/WordPress/Blueprints/BlueprintParser.php +++ b/src/WordPress/Blueprints/BlueprintParser.php @@ -56,9 +56,9 @@ public function parse( $raw_blueprint ) { } /** - * @param \stdClass $data - */ - public function fromObject( $data ) { + * @param \stdClass $data + */ + public function fromObject( $data ) { $result = $this->validator->validate( $data ); if ( ! $result->isValid() ) { print_r( ( new ErrorFormatter() )->format( $result->error() ) ); @@ -68,9 +68,9 @@ public function fromObject( $data ) { } /** - * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint - */ - public function fromBlueprint( $blueprint ) { + * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint + */ + public function fromBlueprint( $blueprint ) { $result = $this->validator->validate( $blueprint ); if ( ! $result->isValid() ) { print_r( ( new ErrorFormatter() )->format( $result->error() ) ); diff --git a/src/WordPress/Blueprints/BlueprintValidator.php b/src/WordPress/Blueprints/BlueprintValidator.php index a4fcc8fe..094b08d2 100644 --- a/src/WordPress/Blueprints/BlueprintValidator.php +++ b/src/WordPress/Blueprints/BlueprintValidator.php @@ -63,6 +63,4 @@ protected function removeNulls( $value ) { return $value; } - } - diff --git a/src/WordPress/Blueprints/Cache/FileCache.php b/src/WordPress/Blueprints/Cache/FileCache.php index 534c6e58..5ba67132 100644 --- a/src/WordPress/Blueprints/Cache/FileCache.php +++ b/src/WordPress/Blueprints/Cache/FileCache.php @@ -32,15 +32,15 @@ public function __construct( $cacheDirectory = null ) { try { $this->filesystem->mkdir( $this->cacheDirectory ); } catch ( IOExceptionInterface $exception ) { - echo "An error occurred while creating your cache directory at " . $exception->getPath(); + echo 'An error occurred while creating your cache directory at ' . $exception->getPath(); } } } /** - * @return mixed - */ - public function get( $key, $default = null ) { + * @return mixed + */ + public function get( $key, $default = null ) { $filepath = $this->getFilePathForKey( $key ); if ( ! file_exists( $filepath ) ) { return $default; @@ -53,7 +53,7 @@ public function get( $key, $default = null ) { public function set( $key, $value, $ttl = null ): bool { $filepath = $this->getFilePathForKey( $key ); - $data = serialize( $value ); + $data = serialize( $value ); return file_put_contents( $filepath, $data ) !== false; } @@ -77,7 +77,7 @@ public function clear(): bool { } public function getMultiple( $keys, $default = null ): iterable { - $result = []; + $result = array(); foreach ( $keys as $key ) { $result[ $key ] = $this->get( $key, $default ); } @@ -113,4 +113,3 @@ private function getFilePathForKey( $key ) { return $this->cacheDirectory . DIRECTORY_SEPARATOR . sha1( $key ); } } - diff --git a/src/WordPress/Blueprints/Compile/BlueprintCompiler.php b/src/WordPress/Blueprints/Compile/BlueprintCompiler.php index 7934ed49..449028c8 100644 --- a/src/WordPress/Blueprints/Compile/BlueprintCompiler.php +++ b/src/WordPress/Blueprints/Compile/BlueprintCompiler.php @@ -23,19 +23,19 @@ public function __construct( $stepRunnerFactory, ResourceResolverInterface $resourceResolver ) { - $this->resourceResolver = $resourceResolver; + $this->resourceResolver = $resourceResolver; $this->stepRunnerFactory = $stepRunnerFactory; } /** - * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint - */ - public function compile( $blueprint ): CompiledBlueprint { + * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint + */ + public function compile( $blueprint ): CompiledBlueprint { $blueprint->steps = array_merge( $this->expandShorthandsIntoSteps( $blueprint ), $blueprint->steps ); $progressTracker = new Tracker(); - $stepsStage = $progressTracker->stage( 0.6 ); - $resourcesStage = $progressTracker->stage( 0.4 ); + $stepsStage = $progressTracker->stage( 0.6 ); + $resourcesStage = $progressTracker->stage( 0.4 ); return new CompiledBlueprint( $this->compileSteps( $blueprint, $stepsStage ), @@ -46,14 +46,14 @@ public function compile( $blueprint ): CompiledBlueprint { } /** - * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint - */ - protected function expandShorthandsIntoSteps( $blueprint ) { + * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint + */ + protected function expandShorthandsIntoSteps( $blueprint ) { // @TODO: This duplicates the logic in BlueprintComposer. - // It cannot be easily reused because of the dichotomy between the - // Step and Step model classes. Let's alter the code generation - // to only generate a single model class for each schema object. - $additional_steps = []; + // It cannot be easily reused because of the dichotomy between the + // Step and Step model classes. Let's alter the code generation + // to only generate a single model class for each schema object. + $additional_steps = array(); if ( $blueprint->WordPressVersion ) { $additional_steps[] = ( new DownloadWordPressStep() ) ->setWordPressZip( @@ -65,7 +65,7 @@ protected function expandShorthandsIntoSteps( $blueprint ) { ( new UrlResource() ) ->setUrl( 'https://downloads.wordpress.org/plugin/sqlite-database-integration.zip' ) ); -// @TODO: stream_select times out here: + // @TODO: stream_select times out here: $additional_steps[] = ( new WriteFileStep() ) ->setPath( 'wp-cli.phar' ) ->setData( ( new UrlResource() )->setUrl( 'https://playground.wordpress.net/wp-cli.phar' ) ); @@ -73,15 +73,15 @@ protected function expandShorthandsIntoSteps( $blueprint ) { ->setOptions( new WordPressInstallationOptions() ); } if ( $blueprint->constants ) { - $step = new DefineWpConfigConstsStep(); - $step->consts = $blueprint->constants; + $step = new DefineWpConfigConstsStep(); + $step->consts = $blueprint->constants; $additional_steps[] = $step; } if ( $blueprint->plugins ) { foreach ( $blueprint->plugins as $plugin ) { - $step = new InstallPluginStep(); + $step = new InstallPluginStep(); $step->pluginZipFile = $plugin; - $additional_steps[] = $step; + $additional_steps[] = $step; } } if ( $blueprint->siteOptions ) { @@ -94,10 +94,10 @@ protected function expandShorthandsIntoSteps( $blueprint ) { } /** - * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint - * @param \WordPress\Blueprints\Progress\Tracker $progress - */ - protected function compileSteps( $blueprint, $progress ) { + * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint + * @param \WordPress\Blueprints\Progress\Tracker $progress + */ + protected function compileSteps( $blueprint, $progress ) { $stepRunnerFactory = $this->stepRunnerFactory; // Compile, ensure all the runners may be created and configured $totalProgressWeight = 0; @@ -105,9 +105,9 @@ protected function compileSteps( $blueprint, $progress ) { $totalProgressWeight += $step->progress->weight ?? 1; } - $compiledSteps = []; + $compiledSteps = array(); foreach ( $blueprint->steps as $step ) { - $runner = $stepRunnerFactory( $step->step ); + $runner = $stepRunnerFactory( $step->step ); $stepProgressTracker = $progress->stage( ( $step->progress->weight ?? 1 ) / $totalProgressWeight, $step->progress->caption ?? $runner->getDefaultCaption( $step ) @@ -125,15 +125,15 @@ protected function compileSteps( $blueprint, $progress ) { } /** - * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint - * @param \WordPress\Blueprints\Progress\Tracker $progress - */ - protected function compileResources( $blueprint, $progress ) { - $resources = []; + * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint + * @param \WordPress\Blueprints\Progress\Tracker $progress + */ + protected function compileResources( $blueprint, $progress ) { + $resources = array(); $this->findResources( $blueprint, $resources ); $totalProgressWeight = count( $resources ); - $compiledResources = []; + $compiledResources = array(); foreach ( $resources as $path => list( $declaration, $resource ) ) { /** @var $resource ResourceDefinitionInterface */ $compiledResources[ $path ] = new CompiledResource( @@ -152,21 +152,21 @@ protected function compileResources( $blueprint, $progress ) { // Find all the resources in the blueprint protected function findResources( $blueprintFragment, &$resources, $path = '', $parseUrls = false ) { if ( $parseUrls && is_string( $blueprintFragment ) ) { - $resources[ $path ] = [ + $resources[ $path ] = array( $blueprintFragment, $this->resourceResolver->parseUrl( $blueprintFragment ), - ]; + ); } elseif ( $blueprintFragment instanceof ResourceDefinitionInterface ) { - $resources[ $path ] = [ + $resources[ $path ] = array( $blueprintFragment, $blueprintFragment, - ]; + ); } elseif ( is_object( $blueprintFragment ) ) { // Check if the @var annotation mentions a ResourceDefinitionInterface foreach ( get_object_vars( $blueprintFragment ) as $key => $value ) { $reflection = new \ReflectionProperty( $blueprintFragment, $key ); - $reflection->setAccessible(true); - $docComment = $reflection->getDocComment(); + $reflection->setAccessible( true ); + $docComment = $reflection->getDocComment(); $parseNestedUrls = false; if ( preg_match( '/@var\s+([^\s]+)/', $docComment, $matches ) ) { $className = $matches[1]; @@ -187,5 +187,4 @@ protected function findResources( $blueprintFragment, &$resources, $path = '', $ return $blueprintFragment; } } - } diff --git a/src/WordPress/Blueprints/Compile/CompiledBlueprint.php b/src/WordPress/Blueprints/Compile/CompiledBlueprint.php index a33ea060..ea1c0c29 100644 --- a/src/WordPress/Blueprints/Compile/CompiledBlueprint.php +++ b/src/WordPress/Blueprints/Compile/CompiledBlueprint.php @@ -17,11 +17,9 @@ class CompiledBlueprint { * @param $stepsProgressStage */ public function __construct( $compiledSteps, $compiledResources, $progressTracker, $stepsProgressStage ) { - $this->compiledSteps = $compiledSteps; - $this->compiledResources = $compiledResources; - $this->progressTracker = $progressTracker; + $this->compiledSteps = $compiledSteps; + $this->compiledResources = $compiledResources; + $this->progressTracker = $progressTracker; $this->stepsProgressStage = $stepsProgressStage; } - - } diff --git a/src/WordPress/Blueprints/Compile/CompiledResource.php b/src/WordPress/Blueprints/Compile/CompiledResource.php index d911a444..169fdc91 100644 --- a/src/WordPress/Blueprints/Compile/CompiledResource.php +++ b/src/WordPress/Blueprints/Compile/CompiledResource.php @@ -16,9 +16,8 @@ public function __construct( ResourceDefinitionInterface $resource, Tracker $progressTracker ) { - $this->declaration = $declaration; - $this->resource = $resource; + $this->declaration = $declaration; + $this->resource = $resource; $this->progressTracker = $progressTracker; } - } diff --git a/src/WordPress/Blueprints/Compile/CompiledStep.php b/src/WordPress/Blueprints/Compile/CompiledStep.php index 2256e910..c99acc84 100644 --- a/src/WordPress/Blueprints/Compile/CompiledStep.php +++ b/src/WordPress/Blueprints/Compile/CompiledStep.php @@ -17,9 +17,8 @@ public function __construct( StepRunnerInterface $runner, Tracker $progressTracker ) { - $this->step = $step; - $this->runner = $runner; + $this->step = $step; + $this->runner = $runner; $this->progressTracker = $progressTracker; } - } diff --git a/src/WordPress/Blueprints/Compile/StepFailure.php b/src/WordPress/Blueprints/Compile/StepFailure.php index affa5601..ce074af5 100644 --- a/src/WordPress/Blueprints/Compile/StepFailure.php +++ b/src/WordPress/Blueprints/Compile/StepFailure.php @@ -8,14 +8,14 @@ class StepFailure extends BlueprintException implements StepResultInterface { /** - * @var \WordPress\Blueprints\Model\DataClass\StepDefinitionInterface - */ - public $step; - public function __construct( + * @var \WordPress\Blueprints\Model\DataClass\StepDefinitionInterface + */ + public $step; + public function __construct( StepDefinitionInterface $step, \Exception $cause ) { $this->step = $step; - parent::__construct( "Error when executing step $step->step", 0, $cause ); + parent::__construct( "Error when executing step $step->step", 0, $cause ); } } diff --git a/src/WordPress/Blueprints/Compile/StepSuccess.php b/src/WordPress/Blueprints/Compile/StepSuccess.php index 2eddbf48..a5de5f7a 100644 --- a/src/WordPress/Blueprints/Compile/StepSuccess.php +++ b/src/WordPress/Blueprints/Compile/StepSuccess.php @@ -9,7 +9,7 @@ class StepSuccess implements StepResultInterface { public $result; public function __construct( StepDefinitionInterface $step, $result ) { - $this->step = $step; + $this->step = $step; $this->result = $result; } } diff --git a/src/WordPress/Blueprints/ContainerBuilder.php b/src/WordPress/Blueprints/ContainerBuilder.php index e4d7d6d3..db962ee3 100644 --- a/src/WordPress/Blueprints/ContainerBuilder.php +++ b/src/WordPress/Blueprints/ContainerBuilder.php @@ -64,10 +64,10 @@ class ContainerBuilder { - const ENVIRONMENT_NATIVE = 'native'; + const ENVIRONMENT_NATIVE = 'native'; const ENVIRONMENT_PLAYGROUND = 'playground'; - const ENVIRONMENT_WP_NOW = 'wp-now'; - const ENVIRONMENTS = array( + const ENVIRONMENT_WP_NOW = 'wp-now'; + const ENVIRONMENTS = array( self::ENVIRONMENT_NATIVE, self::ENVIRONMENT_PLAYGROUND, self::ENVIRONMENT_WP_NOW, @@ -81,20 +81,20 @@ public function __construct() { /** - * @param string $environment - * @param \WordPress\Blueprints\Runtime\RuntimeInterface $runtime - */ - public function build( $environment, $runtime ) { - $container = $this->container; + * @param string $environment + * @param \WordPress\Blueprints\Runtime\RuntimeInterface $runtime + */ + public function build( $environment, $runtime ) { + $container = $this->container; $container['runtime'] = function () use ( $runtime ) { return $runtime; }; if ( $environment === static::ENVIRONMENT_NATIVE ) { - $container['downloads_cache'] = function ( $c ) { + $container['downloads_cache'] = function ( $c ) { return new FileCache(); }; - $container['http_client'] = function ( $c ) { + $container['http_client'] = function ( $c ) { return new Client(); }; $container[ 'resource.resolver.' . UrlResource::DISCRIMINATOR ] = function ( $c ) { @@ -141,7 +141,7 @@ function () use ( $c ) { $container['blueprint.json_schema_path'] = function () { return __DIR__ . '/schema.json'; }; - $container['blueprint.json_schema'] = function ( $c ) { + $container['blueprint.json_schema'] = function ( $c ) { return json_decode( file_get_contents( $c['blueprint.json_schema_path'] ) ); }; @@ -150,10 +150,10 @@ function () use ( $c ) { $c['blueprint.json_schema_path'] ); }; - $container['blueprint.mapper'] = function ( $c ) { + $container['blueprint.mapper'] = function ( $c ) { return new BlueprintMapper(); }; - $container['blueprint.parser'] = function ( $c ) { + $container['blueprint.parser'] = function ( $c ) { return new BlueprintParser( $c['blueprint.validator'], $c['blueprint.mapper'] @@ -163,71 +163,71 @@ function () use ( $c ) { $container[ 'step.runner.' . InstallSqliteIntegrationStep::DISCRIMINATOR ] = function () { return new InstallSqliteIntegrationStepRunner(); }; - $container[ 'step.runner.' . DownloadWordPressStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . DownloadWordPressStep::DISCRIMINATOR ] = function () { return new DownloadWordPressStepRunner(); }; - $container[ 'step.runner.' . UnzipStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . UnzipStep::DISCRIMINATOR ] = function () { return new UnzipStepRunner(); }; - $container[ 'step.runner.' . WriteFileStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . WriteFileStep::DISCRIMINATOR ] = function () { return new WriteFileStepRunner(); }; - $container[ 'step.runner.' . RunPHPStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . RunPHPStep::DISCRIMINATOR ] = function () { return new RunPHPStepRunner(); }; - $container[ 'step.runner.' . DefineWpConfigConstsStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . DefineWpConfigConstsStep::DISCRIMINATOR ] = function () { return new DefineWpConfigConstsStepRunner(); }; - $container[ 'step.runner.' . EnableMultisiteStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . EnableMultisiteStep::DISCRIMINATOR ] = function () { return new EnableMultisiteStepRunner(); }; - $container[ 'step.runner.' . DefineSiteUrlStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . DefineSiteUrlStep::DISCRIMINATOR ] = function () { return new DefineSiteUrlStepRunner(); }; - $container[ 'step.runner.' . MkdirStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . MkdirStep::DISCRIMINATOR ] = function () { return new MkdirStepRunner(); }; - $container[ 'step.runner.' . RmStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . RmStep::DISCRIMINATOR ] = function () { return new RmStepRunner(); }; - $container[ 'step.runner.' . MvStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . MvStep::DISCRIMINATOR ] = function () { return new MvStepRunner(); }; - $container[ 'step.runner.' . CpStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . CpStep::DISCRIMINATOR ] = function () { return new CpStepRunner(); }; - $container[ 'step.runner.' . WPCLIStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . WPCLIStep::DISCRIMINATOR ] = function () { return new WPCLIStepRunner(); }; - $container[ 'step.runner.' . SetSiteOptionsStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . SetSiteOptionsStep::DISCRIMINATOR ] = function () { return new SetSiteOptionsStepRunner(); }; - $container[ 'step.runner.' . ActivatePluginStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . ActivatePluginStep::DISCRIMINATOR ] = function () { return new ActivatePluginStepRunner(); }; - $container[ 'step.runner.' . ActivateThemeStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . ActivateThemeStep::DISCRIMINATOR ] = function () { return new ActivateThemeStepRunner(); }; - $container[ 'step.runner.' . InstallPluginStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . InstallPluginStep::DISCRIMINATOR ] = function () { return new InstallPluginStepRunner(); }; - $container[ 'step.runner.' . InstallThemeStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . InstallThemeStep::DISCRIMINATOR ] = function () { return new InstallThemeStepRunner(); }; - $container[ 'step.runner.' . ImportFileStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . ImportFileStep::DISCRIMINATOR ] = function () { return new ImportFileStepRunner(); }; - $container[ 'step.runner.' . RunWordPressInstallerStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . RunWordPressInstallerStep::DISCRIMINATOR ] = function () { return new RunWordPressInstallerStepRunner(); }; - $container[ 'step.runner.' . RunSQLStep::DISCRIMINATOR ] = function () { + $container[ 'step.runner.' . RunSQLStep::DISCRIMINATOR ] = function () { return new RunSQLStepRunner(); }; $container[ 'resource.resolver.' . FilesystemResource::DISCRIMINATOR ] = function () { return new FilesystemResourceResolver(); }; - $container[ 'resource.resolver.' . InlineResource::DISCRIMINATOR ] = function () { + $container[ 'resource.resolver.' . InlineResource::DISCRIMINATOR ] = function () { return new InlineResourceResolver(); }; @@ -256,11 +256,11 @@ function ( $c ) { $container['step.runner_factory'] = function ( $c ) { return function ( $slug ) use ( $c ) { - if ( ! isset( $c["step.runner.$slug"] ) ) { + if ( ! isset( $c[ "step.runner.$slug" ] ) ) { throw new InvalidArgumentException( "No runner registered for step {$slug}" ); } - return $c["step.runner.$slug"]; + return $c[ "step.runner.$slug" ]; }; }; diff --git a/src/WordPress/Blueprints/Engine.php b/src/WordPress/Blueprints/Engine.php index 8168ae8c..9dca3a87 100644 --- a/src/WordPress/Blueprints/Engine.php +++ b/src/WordPress/Blueprints/Engine.php @@ -28,9 +28,9 @@ public function __construct( BlueprintCompiler $compiler, BlueprintRunner $runner ) { - $this->runner = $runner; + $this->runner = $runner; $this->compiler = $compiler; - $this->parser = $parser; + $this->parser = $parser; } public function parseAndCompile( $raw_blueprint ) { @@ -40,9 +40,9 @@ public function parseAndCompile( $raw_blueprint ) { } /** - * @param \WordPress\Blueprints\Compile\CompiledBlueprint $compiled_blueprint - */ - public function run( $compiled_blueprint ) { + * @param \WordPress\Blueprints\Compile\CompiledBlueprint $compiled_blueprint + */ + public function run( $compiled_blueprint ) { return $this->runner->run( $compiled_blueprint ); } } diff --git a/src/WordPress/Blueprints/Model/BlueprintBuilder.php b/src/WordPress/Blueprints/Model/BlueprintBuilder.php index 52773260..0a3abc53 100644 --- a/src/WordPress/Blueprints/Model/BlueprintBuilder.php +++ b/src/WordPress/Blueprints/Model/BlueprintBuilder.php @@ -48,18 +48,18 @@ public function withWordPressVersion( $v ) { } /** - * @param mixed[] $options - */ - public function withSiteOptions( $options ) { + * @param mixed[] $options + */ + public function withSiteOptions( $options ) { $this->blueprint->setSiteOptions( $options ); return $this; } /** - * @param mixed[] $constants - */ - public function withWpConfigConstants( $constants ) { + * @param mixed[] $constants + */ + public function withWpConfigConstants( $constants ) { $this->blueprint->setConstants( $constants ); return $this; @@ -184,9 +184,9 @@ private function prependStep( StepDefinitionInterface $builder ) { } /** - * @param \WordPress\Blueprints\Model\DataClass\StepDefinitionInterface $builder - */ - public function addStep( $builder ) { + * @param \WordPress\Blueprints\Model\DataClass\StepDefinitionInterface $builder + */ + public function addStep( $builder ) { array_push( $this->blueprint->steps, $builder ); return $this; diff --git a/src/WordPress/Blueprints/Model/BlueprintSerializer.php b/src/WordPress/Blueprints/Model/BlueprintSerializer.php index 93e02386..2c6a2a9c 100644 --- a/src/WordPress/Blueprints/Model/BlueprintSerializer.php +++ b/src/WordPress/Blueprints/Model/BlueprintSerializer.php @@ -8,10 +8,9 @@ class BlueprintSerializer { /** - * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint - */ - public function toJson( $blueprint ) { + * @param \WordPress\Blueprints\Model\DataClass\Blueprint $blueprint + */ + public function toJson( $blueprint ) { return json_encode( $blueprint ); } - } diff --git a/src/WordPress/Blueprints/Model/DataClass/ActivatePluginStep.php b/src/WordPress/Blueprints/Model/DataClass/ActivatePluginStep.php index 030c9693..c36a1542 100644 --- a/src/WordPress/Blueprints/Model/DataClass/ActivatePluginStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/ActivatePluginStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class ActivatePluginStep implements StepDefinitionInterface -{ +class ActivatePluginStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'activatePlugin'; /** @var Progress */ @@ -17,46 +17,43 @@ class ActivatePluginStep implements StepDefinitionInterface /** * Plugin slug, like 'gutenberg' or 'hello-dolly'. + * * @var string */ public $slug; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $slug - */ - public function setSlug($slug) - { + * @param string $slug + */ + public function setSlug( $slug ) { $this->slug = $slug; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/ActivateThemeStep.php b/src/WordPress/Blueprints/Model/DataClass/ActivateThemeStep.php index bb31bdf1..1d019cca 100644 --- a/src/WordPress/Blueprints/Model/DataClass/ActivateThemeStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/ActivateThemeStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class ActivateThemeStep implements StepDefinitionInterface -{ +class ActivateThemeStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'activateTheme'; /** @var Progress */ @@ -17,46 +17,43 @@ class ActivateThemeStep implements StepDefinitionInterface /** * Theme slug, like 'twentytwentythree'. + * * @var string */ public $slug; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $slug - */ - public function setSlug($slug) - { + * @param string $slug + */ + public function setSlug( $slug ) { $this->slug = $slug; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/Blueprint.php b/src/WordPress/Blueprints/Model/DataClass/Blueprint.php index b43d0d00..99d6e4b1 100644 --- a/src/WordPress/Blueprints/Model/DataClass/Blueprint.php +++ b/src/WordPress/Blueprints/Model/DataClass/Blueprint.php @@ -5,18 +5,21 @@ class Blueprint { /** * Optional description. It doesn't do anything but is exposed as a courtesy to developers who may want to document which blueprint file does what. + * * @var string */ public $description = ''; /** * Version of WordPress to use. Also accepts URL to a WordPress zip file. + * * @var string */ public $WordPressVersion; /** * Slot for runtime–specific options, schema must be provided by the runtime. + * * @var \ArrayObject */ public $runtime; @@ -26,33 +29,37 @@ class Blueprint { /** * PHP Constants to define on every request + * * @var \ArrayObject */ - public $constants = []; + public $constants = array(); /** * WordPress plugins to install and activate + * * @var string[]|ResourceDefinitionInterface[] */ - public $plugins = []; + public $plugins = array(); /** * WordPress site options to define + * * @var \ArrayObject */ - public $siteOptions = []; + public $siteOptions = array(); /** * The steps to run after every other operation in this Blueprint was executed. + * * @var StepDefinitionInterface[] */ - public $steps = []; + public $steps = array(); /** - * @param string $description - */ - public function setDescription( $description ) { + * @param string $description + */ + public function setDescription( $description ) { $this->description = $description; return $this; @@ -60,9 +67,9 @@ public function setDescription( $description ) { /** - * @param string $WordPressVersion - */ - public function setWordPressVersion( $WordPressVersion ) { + * @param string $WordPressVersion + */ + public function setWordPressVersion( $WordPressVersion ) { $this->WordPressVersion = $WordPressVersion; return $this; @@ -77,9 +84,9 @@ public function setRuntime( $runtime ) { /** - * @param \WordPress\Blueprints\Model\DataClass\BlueprintOnBoot $onBoot - */ - public function setOnBoot( $onBoot ) { + * @param \WordPress\Blueprints\Model\DataClass\BlueprintOnBoot $onBoot + */ + public function setOnBoot( $onBoot ) { $this->onBoot = $onBoot; return $this; @@ -94,9 +101,9 @@ public function setConstants( $constants ) { /** - * @param mixed[] $plugins - */ - public function setPlugins( $plugins ) { + * @param mixed[] $plugins + */ + public function setPlugins( $plugins ) { $this->plugins = $plugins; return $this; @@ -111,9 +118,9 @@ public function setSiteOptions( $siteOptions ) { /** - * @param mixed[] $steps - */ - public function setSteps( $steps ) { + * @param mixed[] $steps + */ + public function setSteps( $steps ) { $this->steps = $steps; return $this; diff --git a/src/WordPress/Blueprints/Model/DataClass/BlueprintOnBoot.php b/src/WordPress/Blueprints/Model/DataClass/BlueprintOnBoot.php index 554b7184..c91c17f6 100644 --- a/src/WordPress/Blueprints/Model/DataClass/BlueprintOnBoot.php +++ b/src/WordPress/Blueprints/Model/DataClass/BlueprintOnBoot.php @@ -2,10 +2,11 @@ namespace WordPress\Blueprints\Model\DataClass; -class BlueprintOnBoot -{ +class BlueprintOnBoot { + /** * The URL to navigate to after the blueprint has been run. + * * @var string */ public $openUrl; @@ -15,20 +16,18 @@ class BlueprintOnBoot /** - * @param string $openUrl - */ - public function setOpenUrl($openUrl) - { + * @param string $openUrl + */ + public function setOpenUrl( $openUrl ) { $this->openUrl = $openUrl; return $this; } /** - * @param bool $login - */ - public function setLogin($login) - { + * @param bool $login + */ + public function setLogin( $login ) { $this->login = $login; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/CorePluginResource.php b/src/WordPress/Blueprints/Model/DataClass/CorePluginResource.php index 472bcef1..0c18d971 100644 --- a/src/WordPress/Blueprints/Model/DataClass/CorePluginResource.php +++ b/src/WordPress/Blueprints/Model/DataClass/CorePluginResource.php @@ -2,38 +2,38 @@ namespace WordPress\Blueprints\Model\DataClass; -class CorePluginResource implements ResourceDefinitionInterface -{ +class CorePluginResource implements ResourceDefinitionInterface { + const DISCRIMINATOR = 'wordpress.org/plugins'; /** * Identifies the file resource as a WordPress Core plugin + * * @var string */ public $resource = 'wordpress.org/plugins'; /** * The slug of the WordPress Core plugin + * * @var string */ public $slug; /** - * @param string $resource - */ - public function setResource($resource) - { + * @param string $resource + */ + public function setResource( $resource ) { $this->resource = $resource; return $this; } /** - * @param string $slug - */ - public function setSlug($slug) - { + * @param string $slug + */ + public function setSlug( $slug ) { $this->slug = $slug; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/CoreThemeResource.php b/src/WordPress/Blueprints/Model/DataClass/CoreThemeResource.php index 2296d87d..20785f19 100644 --- a/src/WordPress/Blueprints/Model/DataClass/CoreThemeResource.php +++ b/src/WordPress/Blueprints/Model/DataClass/CoreThemeResource.php @@ -2,38 +2,38 @@ namespace WordPress\Blueprints\Model\DataClass; -class CoreThemeResource implements ResourceDefinitionInterface -{ +class CoreThemeResource implements ResourceDefinitionInterface { + const DISCRIMINATOR = 'wordpress.org/themes'; /** * Identifies the file resource as a WordPress Core theme + * * @var string */ public $resource = 'wordpress.org/themes'; /** * The slug of the WordPress Core theme + * * @var string */ public $slug; /** - * @param string $resource - */ - public function setResource($resource) - { + * @param string $resource + */ + public function setResource( $resource ) { $this->resource = $resource; return $this; } /** - * @param string $slug - */ - public function setSlug($slug) - { + * @param string $slug + */ + public function setSlug( $slug ) { $this->slug = $slug; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/CpStep.php b/src/WordPress/Blueprints/Model/DataClass/CpStep.php index c5d22c1e..5b4c16c7 100644 --- a/src/WordPress/Blueprints/Model/DataClass/CpStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/CpStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class CpStep implements StepDefinitionInterface -{ +class CpStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'cp'; /** @var Progress */ @@ -17,62 +17,59 @@ class CpStep implements StepDefinitionInterface /** * Source path + * * @var string */ public $fromPath; /** * Target path + * * @var string */ public $toPath; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $fromPath - */ - public function setFromPath($fromPath) - { + * @param string $fromPath + */ + public function setFromPath( $fromPath ) { $this->fromPath = $fromPath; return $this; } /** - * @param string $toPath - */ - public function setToPath($toPath) - { + * @param string $toPath + */ + public function setToPath( $toPath ) { $this->toPath = $toPath; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/DefineSiteUrlStep.php b/src/WordPress/Blueprints/Model/DataClass/DefineSiteUrlStep.php index b21cf0e8..07433cee 100644 --- a/src/WordPress/Blueprints/Model/DataClass/DefineSiteUrlStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/DefineSiteUrlStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class DefineSiteUrlStep implements StepDefinitionInterface -{ +class DefineSiteUrlStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'defineSiteUrl'; /** @var Progress */ @@ -17,46 +17,43 @@ class DefineSiteUrlStep implements StepDefinitionInterface /** * The URL + * * @var string */ public $siteUrl; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $siteUrl - */ - public function setSiteUrl($siteUrl) - { + * @param string $siteUrl + */ + public function setSiteUrl( $siteUrl ) { $this->siteUrl = $siteUrl; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/DefineWpConfigConstsStep.php b/src/WordPress/Blueprints/Model/DataClass/DefineWpConfigConstsStep.php index 19a0bbc6..ae227048 100644 --- a/src/WordPress/Blueprints/Model/DataClass/DefineWpConfigConstsStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/DefineWpConfigConstsStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class DefineWpConfigConstsStep implements StepDefinitionInterface -{ +class DefineWpConfigConstsStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'defineWpConfigConsts'; /** @var Progress */ @@ -17,46 +17,43 @@ class DefineWpConfigConstsStep implements StepDefinitionInterface /** * The constants to define + * * @var \ArrayObject */ public $consts; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param iterable $consts - */ - public function setConsts($consts) - { + * @param iterable $consts + */ + public function setConsts( $consts ) { $this->consts = $consts; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/DownloadWordPressStep.php b/src/WordPress/Blueprints/Model/DataClass/DownloadWordPressStep.php index 1db432bf..d60a6eba 100644 --- a/src/WordPress/Blueprints/Model/DataClass/DownloadWordPressStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/DownloadWordPressStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class DownloadWordPressStep implements StepDefinitionInterface -{ +class DownloadWordPressStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'downloadWordPress'; /** @var Progress */ @@ -20,37 +20,33 @@ class DownloadWordPressStep implements StepDefinitionInterface /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } - public function setWordPressZip($wordPressZip) - { + public function setWordPressZip( $wordPressZip ) { $this->wordPressZip = $wordPressZip; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/EnableMultisiteStep.php b/src/WordPress/Blueprints/Model/DataClass/EnableMultisiteStep.php index 8dda6c98..8c381b9c 100644 --- a/src/WordPress/Blueprints/Model/DataClass/EnableMultisiteStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/EnableMultisiteStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class EnableMultisiteStep implements StepDefinitionInterface -{ +class EnableMultisiteStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'enableMultisite'; /** @var Progress */ @@ -17,30 +17,27 @@ class EnableMultisiteStep implements StepDefinitionInterface /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/EvalPHPCallbackStep.php b/src/WordPress/Blueprints/Model/DataClass/EvalPHPCallbackStep.php index b2d480b5..1e5eb6a7 100644 --- a/src/WordPress/Blueprints/Model/DataClass/EvalPHPCallbackStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/EvalPHPCallbackStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class EvalPHPCallbackStep implements StepDefinitionInterface -{ +class EvalPHPCallbackStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'evalPHPCallback'; /** @var Progress */ @@ -14,49 +14,47 @@ class EvalPHPCallbackStep implements StepDefinitionInterface /** * The step identifier. + * * @var string */ public $step = 'evalPHPCallback'; /** * The PHP function. + * * @var mixed */ public $callback; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } - public function setCallback($callback) - { + public function setCallback( $callback ) { $this->callback = $callback; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/FileInfo.php b/src/WordPress/Blueprints/Model/DataClass/FileInfo.php index 534f76a9..fc1ab214 100644 --- a/src/WordPress/Blueprints/Model/DataClass/FileInfo.php +++ b/src/WordPress/Blueprints/Model/DataClass/FileInfo.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class FileInfo -{ +class FileInfo { + /** @var string */ public $key; @@ -18,40 +18,36 @@ class FileInfo /** - * @param string $key - */ - public function setKey($key) - { + * @param string $key + */ + public function setKey( $key ) { $this->key = $key; return $this; } /** - * @param string $name - */ - public function setName($name) - { + * @param string $name + */ + public function setName( $name ) { $this->name = $name; return $this; } /** - * @param string $type - */ - public function setType($type) - { + * @param string $type + */ + public function setType( $type ) { $this->type = $type; return $this; } /** - * @param \WordPress\Blueprints\Model\DataClass\FileInfoData $data - */ - public function setData($data) - { + * @param \WordPress\Blueprints\Model\DataClass\FileInfoData $data + */ + public function setData( $data ) { $this->data = $data; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/FileInfoData.php b/src/WordPress/Blueprints/Model/DataClass/FileInfoData.php index 39ff5129..72ef3ce2 100644 --- a/src/WordPress/Blueprints/Model/DataClass/FileInfoData.php +++ b/src/WordPress/Blueprints/Model/DataClass/FileInfoData.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class FileInfoData -{ +class FileInfoData { + /** @var float */ public $BYTES_PER_ELEMENT; @@ -21,50 +21,45 @@ class FileInfoData /** - * @param float $BYTES_PER_ELEMENT - */ - public function setBYTES_PER_ELEMENT($BYTES_PER_ELEMENT) - { + * @param float $BYTES_PER_ELEMENT + */ + public function setBYTES_PER_ELEMENT( $BYTES_PER_ELEMENT ) { $this->BYTES_PER_ELEMENT = $BYTES_PER_ELEMENT; return $this; } /** - * @param \WordPress\Blueprints\Model\DataClass\FileInfoDataBuffer $buffer - */ - public function setBuffer($buffer) - { + * @param \WordPress\Blueprints\Model\DataClass\FileInfoDataBuffer $buffer + */ + public function setBuffer( $buffer ) { $this->buffer = $buffer; return $this; } /** - * @param float $byteLength - */ - public function setByteLength($byteLength) - { + * @param float $byteLength + */ + public function setByteLength( $byteLength ) { $this->byteLength = $byteLength; return $this; } /** - * @param float $byteOffset - */ - public function setByteOffset($byteOffset) - { + * @param float $byteOffset + */ + public function setByteOffset( $byteOffset ) { $this->byteOffset = $byteOffset; return $this; } /** - * @param float $length - */ - public function setLength($length) - { + * @param float $length + */ + public function setLength( $length ) { $this->length = $length; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/FileInfoDataBuffer.php b/src/WordPress/Blueprints/Model/DataClass/FileInfoDataBuffer.php index 535ef8b4..a1d03063 100644 --- a/src/WordPress/Blueprints/Model/DataClass/FileInfoDataBuffer.php +++ b/src/WordPress/Blueprints/Model/DataClass/FileInfoDataBuffer.php @@ -2,17 +2,16 @@ namespace WordPress\Blueprints\Model\DataClass; -class FileInfoDataBuffer -{ +class FileInfoDataBuffer { + /** @var float */ public $byteLength; /** - * @param float $byteLength - */ - public function setByteLength($byteLength) - { + * @param float $byteLength + */ + public function setByteLength( $byteLength ) { $this->byteLength = $byteLength; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/FilesystemResource.php b/src/WordPress/Blueprints/Model/DataClass/FilesystemResource.php index caffb091..9637f215 100644 --- a/src/WordPress/Blueprints/Model/DataClass/FilesystemResource.php +++ b/src/WordPress/Blueprints/Model/DataClass/FilesystemResource.php @@ -2,38 +2,38 @@ namespace WordPress\Blueprints\Model\DataClass; -class FilesystemResource implements ResourceDefinitionInterface -{ +class FilesystemResource implements ResourceDefinitionInterface { + const DISCRIMINATOR = 'filesystem'; /** * Identifies the file resource as Virtual File System (VFS) + * * @var string */ public $resource = 'filesystem'; /** * The path to the file in the VFS + * * @var string */ public $path; /** - * @param string $resource - */ - public function setResource($resource) - { + * @param string $resource + */ + public function setResource( $resource ) { $this->resource = $resource; return $this; } /** - * @param string $path - */ - public function setPath($path) - { + * @param string $path + */ + public function setPath( $path ) { $this->path = $path; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/ImportFileStep.php b/src/WordPress/Blueprints/Model/DataClass/ImportFileStep.php index 568a04ad..caa11d88 100644 --- a/src/WordPress/Blueprints/Model/DataClass/ImportFileStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/ImportFileStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class ImportFileStep implements StepDefinitionInterface -{ +class ImportFileStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'importFile'; /** @var Progress */ @@ -20,37 +20,33 @@ class ImportFileStep implements StepDefinitionInterface /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } - public function setFile($file) - { + public function setFile( $file ) { $this->file = $file; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/InlineResource.php b/src/WordPress/Blueprints/Model/DataClass/InlineResource.php index 4e30c0ad..8e2ec36e 100644 --- a/src/WordPress/Blueprints/Model/DataClass/InlineResource.php +++ b/src/WordPress/Blueprints/Model/DataClass/InlineResource.php @@ -2,38 +2,38 @@ namespace WordPress\Blueprints\Model\DataClass; -class InlineResource implements ResourceDefinitionInterface -{ +class InlineResource implements ResourceDefinitionInterface { + const DISCRIMINATOR = 'inline'; /** * Identifies the file resource as an inline string + * * @var string */ public $resource = 'inline'; /** * The contents of the file + * * @var string */ public $contents; /** - * @param string $resource - */ - public function setResource($resource) - { + * @param string $resource + */ + public function setResource( $resource ) { $this->resource = $resource; return $this; } /** - * @param string $contents - */ - public function setContents($contents) - { + * @param string $contents + */ + public function setContents( $contents ) { $this->contents = $contents; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/InstallPluginStep.php b/src/WordPress/Blueprints/Model/DataClass/InstallPluginStep.php index 54375712..1da3cb0c 100644 --- a/src/WordPress/Blueprints/Model/DataClass/InstallPluginStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/InstallPluginStep.php @@ -13,6 +13,7 @@ class InstallPluginStep implements StepDefinitionInterface { /** * The step identifier. + * * @var string */ public $step = 'installPlugin'; @@ -22,15 +23,16 @@ class InstallPluginStep implements StepDefinitionInterface { /** * Whether to activate the plugin after installing it. + * * @var bool */ public $activate = true; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress( $progress ) { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; @@ -38,9 +40,9 @@ public function setProgress( $progress ) { /** - * @param bool $continueOnError - */ - public function setContinueOnError( $continueOnError ) { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; @@ -48,9 +50,9 @@ public function setContinueOnError( $continueOnError ) { /** - * @param string $step - */ - public function setStep( $step ) { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; @@ -65,9 +67,9 @@ public function setPluginZipFile( $pluginZipFile ) { /** - * @param bool $activate - */ - public function setActivate( $activate ) { + * @param bool $activate + */ + public function setActivate( $activate ) { $this->activate = $activate; return $this; diff --git a/src/WordPress/Blueprints/Model/DataClass/InstallSqliteIntegrationStep.php b/src/WordPress/Blueprints/Model/DataClass/InstallSqliteIntegrationStep.php index 63e868e4..d13f5bb2 100644 --- a/src/WordPress/Blueprints/Model/DataClass/InstallSqliteIntegrationStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/InstallSqliteIntegrationStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class InstallSqliteIntegrationStep implements StepDefinitionInterface -{ +class InstallSqliteIntegrationStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'installSqliteIntegration'; /** @var Progress */ @@ -20,37 +20,33 @@ class InstallSqliteIntegrationStep implements StepDefinitionInterface /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } - public function setSqlitePluginZip($sqlitePluginZip) - { + public function setSqlitePluginZip( $sqlitePluginZip ) { $this->sqlitePluginZip = $sqlitePluginZip; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/InstallThemeStep.php b/src/WordPress/Blueprints/Model/DataClass/InstallThemeStep.php index 5bd52b88..05bf1a69 100644 --- a/src/WordPress/Blueprints/Model/DataClass/InstallThemeStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/InstallThemeStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class InstallThemeStep implements StepDefinitionInterface -{ +class InstallThemeStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'installTheme'; /** @var Progress */ @@ -14,6 +14,7 @@ class InstallThemeStep implements StepDefinitionInterface /** * The step identifier. + * * @var string */ public $step = 'installTheme'; @@ -23,53 +24,49 @@ class InstallThemeStep implements StepDefinitionInterface /** * Whether to activate the theme after installing it. + * * @var bool */ public $activate = true; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } - public function setThemeZipFile($themeZipFile) - { + public function setThemeZipFile( $themeZipFile ) { $this->themeZipFile = $themeZipFile; return $this; } /** - * @param bool $activate - */ - public function setActivate($activate) - { + * @param bool $activate + */ + public function setActivate( $activate ) { $this->activate = $activate; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/MkdirStep.php b/src/WordPress/Blueprints/Model/DataClass/MkdirStep.php index 291151c9..efcb6e51 100644 --- a/src/WordPress/Blueprints/Model/DataClass/MkdirStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/MkdirStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class MkdirStep implements StepDefinitionInterface -{ +class MkdirStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'mkdir'; /** @var Progress */ @@ -17,46 +17,43 @@ class MkdirStep implements StepDefinitionInterface /** * The path of the directory you want to create + * * @var string */ public $path; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $path - */ - public function setPath($path) - { + * @param string $path + */ + public function setPath( $path ) { $this->path = $path; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/ModelInfo.php b/src/WordPress/Blueprints/Model/DataClass/ModelInfo.php index ae54db72..322a60a3 100644 --- a/src/WordPress/Blueprints/Model/DataClass/ModelInfo.php +++ b/src/WordPress/Blueprints/Model/DataClass/ModelInfo.php @@ -2,23 +2,21 @@ namespace WordPress\Blueprints\Model\DataClass; -class ModelInfo -{ - public static function getResourceDefinitionInterfaceImplementations(): array - { - return [ +class ModelInfo { + + public static function getResourceDefinitionInterfaceImplementations(): array { + return array( FilesystemResource::class, InlineResource::class, CoreThemeResource::class, CorePluginResource::class, - UrlResource::class - ]; + UrlResource::class, + ); } - public static function getStepDefinitionInterfaceImplementations(): array - { - return [ + public static function getStepDefinitionInterfaceImplementations(): array { + return array( ActivatePluginStep::class, ActivateThemeStep::class, CpStep::class, @@ -40,7 +38,7 @@ public static function getStepDefinitionInterfaceImplementations(): array DownloadWordPressStep::class, InstallSqliteIntegrationStep::class, WriteFileStep::class, - WPCLIStep::class - ]; + WPCLIStep::class, + ); } } diff --git a/src/WordPress/Blueprints/Model/DataClass/MvStep.php b/src/WordPress/Blueprints/Model/DataClass/MvStep.php index 10c35d5b..c596ab1a 100644 --- a/src/WordPress/Blueprints/Model/DataClass/MvStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/MvStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class MvStep implements StepDefinitionInterface -{ +class MvStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'mv'; /** @var Progress */ @@ -17,62 +17,59 @@ class MvStep implements StepDefinitionInterface /** * Source path + * * @var string */ public $fromPath; /** * Target path + * * @var string */ public $toPath; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $fromPath - */ - public function setFromPath($fromPath) - { + * @param string $fromPath + */ + public function setFromPath( $fromPath ) { $this->fromPath = $fromPath; return $this; } /** - * @param string $toPath - */ - public function setToPath($toPath) - { + * @param string $toPath + */ + public function setToPath( $toPath ) { $this->toPath = $toPath; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/Progress.php b/src/WordPress/Blueprints/Model/DataClass/Progress.php index 973bc481..06261350 100644 --- a/src/WordPress/Blueprints/Model/DataClass/Progress.php +++ b/src/WordPress/Blueprints/Model/DataClass/Progress.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class Progress -{ +class Progress { + /** @var float */ public $weight; @@ -12,20 +12,18 @@ class Progress /** - * @param float $weight - */ - public function setWeight($weight) - { + * @param float $weight + */ + public function setWeight( $weight ) { $this->weight = $weight; return $this; } /** - * @param string $caption - */ - public function setCaption($caption) - { + * @param string $caption + */ + public function setCaption( $caption ) { $this->caption = $caption; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/ResourceDefinitionInterface.php b/src/WordPress/Blueprints/Model/DataClass/ResourceDefinitionInterface.php index 8bee1256..b156c3fc 100644 --- a/src/WordPress/Blueprints/Model/DataClass/ResourceDefinitionInterface.php +++ b/src/WordPress/Blueprints/Model/DataClass/ResourceDefinitionInterface.php @@ -2,6 +2,6 @@ namespace WordPress\Blueprints\Model\DataClass; -interface ResourceDefinitionInterface -{ +interface ResourceDefinitionInterface { + } diff --git a/src/WordPress/Blueprints/Model/DataClass/RmStep.php b/src/WordPress/Blueprints/Model/DataClass/RmStep.php index af6d3853..831afae6 100644 --- a/src/WordPress/Blueprints/Model/DataClass/RmStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/RmStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class RmStep implements StepDefinitionInterface -{ +class RmStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'rm'; /** @var Progress */ @@ -17,46 +17,43 @@ class RmStep implements StepDefinitionInterface /** * The path to remove + * * @var string */ public $path; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $path - */ - public function setPath($path) - { + * @param string $path + */ + public function setPath( $path ) { $this->path = $path; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/RunPHPStep.php b/src/WordPress/Blueprints/Model/DataClass/RunPHPStep.php index 3c763d38..63e5f9e0 100644 --- a/src/WordPress/Blueprints/Model/DataClass/RunPHPStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/RunPHPStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class RunPHPStep implements StepDefinitionInterface -{ +class RunPHPStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'runPHP'; /** @var Progress */ @@ -14,52 +14,50 @@ class RunPHPStep implements StepDefinitionInterface /** * The step identifier. + * * @var string */ public $step = 'runPHP'; /** * The PHP code to run. + * * @var string */ public $code; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $code - */ - public function setCode($code) - { + * @param string $code + */ + public function setCode( $code ) { $this->code = $code; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/RunSQLStep.php b/src/WordPress/Blueprints/Model/DataClass/RunSQLStep.php index ebaf9c68..8872ba3f 100644 --- a/src/WordPress/Blueprints/Model/DataClass/RunSQLStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/RunSQLStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class RunSQLStep implements StepDefinitionInterface -{ +class RunSQLStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'runSql'; /** @var Progress */ @@ -14,6 +14,7 @@ class RunSQLStep implements StepDefinitionInterface /** * The step identifier. + * * @var string */ public $step = 'runSql'; @@ -23,37 +24,33 @@ class RunSQLStep implements StepDefinitionInterface /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } - public function setSql($sql) - { + public function setSql( $sql ) { $this->sql = $sql; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/RunWordPressInstallerStep.php b/src/WordPress/Blueprints/Model/DataClass/RunWordPressInstallerStep.php index b4a38a06..15744739 100644 --- a/src/WordPress/Blueprints/Model/DataClass/RunWordPressInstallerStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/RunWordPressInstallerStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class RunWordPressInstallerStep implements StepDefinitionInterface -{ +class RunWordPressInstallerStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'runWpInstallationWizard'; /** @var Progress */ @@ -20,40 +20,36 @@ class RunWordPressInstallerStep implements StepDefinitionInterface /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param \WordPress\Blueprints\Model\DataClass\WordPressInstallationOptions $options - */ - public function setOptions($options) - { + * @param \WordPress\Blueprints\Model\DataClass\WordPressInstallationOptions $options + */ + public function setOptions( $options ) { $this->options = $options; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/SetSiteOptionsStep.php b/src/WordPress/Blueprints/Model/DataClass/SetSiteOptionsStep.php index 3b9e7327..4ee1bd19 100644 --- a/src/WordPress/Blueprints/Model/DataClass/SetSiteOptionsStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/SetSiteOptionsStep.php @@ -13,21 +13,23 @@ class SetSiteOptionsStep implements StepDefinitionInterface { /** * The name of the step. Must be "setSiteOptions". + * * @var string */ public $step = 'setSiteOptions'; /** * The options to set on the site. + * * @var \ArrayObject */ public $options; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress( $progress ) { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; @@ -35,9 +37,9 @@ public function setProgress( $progress ) { /** - * @param bool $continueOnError - */ - public function setContinueOnError( $continueOnError ) { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; @@ -45,9 +47,9 @@ public function setContinueOnError( $continueOnError ) { /** - * @param string $step - */ - public function setStep( $step ) { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; diff --git a/src/WordPress/Blueprints/Model/DataClass/StepDefinitionInterface.php b/src/WordPress/Blueprints/Model/DataClass/StepDefinitionInterface.php index 84a42524..451abfd2 100644 --- a/src/WordPress/Blueprints/Model/DataClass/StepDefinitionInterface.php +++ b/src/WordPress/Blueprints/Model/DataClass/StepDefinitionInterface.php @@ -2,6 +2,6 @@ namespace WordPress\Blueprints\Model\DataClass; -interface StepDefinitionInterface -{ +interface StepDefinitionInterface { + } diff --git a/src/WordPress/Blueprints/Model/DataClass/UnzipStep.php b/src/WordPress/Blueprints/Model/DataClass/UnzipStep.php index 5333e315..06ab8124 100644 --- a/src/WordPress/Blueprints/Model/DataClass/UnzipStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/UnzipStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class UnzipStep implements StepDefinitionInterface -{ +class UnzipStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'unzip'; /** @var Progress */ @@ -20,53 +20,49 @@ class UnzipStep implements StepDefinitionInterface /** * The path to extract the zip file to + * * @var string */ public $extractToPath; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } - public function setZipFile($zipFile) - { + public function setZipFile( $zipFile ) { $this->zipFile = $zipFile; return $this; } /** - * @param string $extractToPath - */ - public function setExtractToPath($extractToPath) - { + * @param string $extractToPath + */ + public function setExtractToPath( $extractToPath ) { $this->extractToPath = $extractToPath; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/UrlResource.php b/src/WordPress/Blueprints/Model/DataClass/UrlResource.php index 8d25d706..fa3b2d18 100644 --- a/src/WordPress/Blueprints/Model/DataClass/UrlResource.php +++ b/src/WordPress/Blueprints/Model/DataClass/UrlResource.php @@ -2,54 +2,54 @@ namespace WordPress\Blueprints\Model\DataClass; -class UrlResource implements ResourceDefinitionInterface -{ +class UrlResource implements ResourceDefinitionInterface { + const DISCRIMINATOR = 'url'; /** * Identifies the file resource as a URL + * * @var string */ public $resource = 'url'; /** * The URL of the file + * * @var string */ public $url; /** * Optional caption for displaying a progress message + * * @var string */ public $caption; /** - * @param string $resource - */ - public function setResource($resource) - { + * @param string $resource + */ + public function setResource( $resource ) { $this->resource = $resource; return $this; } /** - * @param string $url - */ - public function setUrl($url) - { + * @param string $url + */ + public function setUrl( $url ) { $this->url = $url; return $this; } /** - * @param string $caption - */ - public function setCaption($caption) - { + * @param string $caption + */ + public function setCaption( $caption ) { $this->caption = $caption; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/WPCLIStep.php b/src/WordPress/Blueprints/Model/DataClass/WPCLIStep.php index ea7b3060..ed034b66 100644 --- a/src/WordPress/Blueprints/Model/DataClass/WPCLIStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/WPCLIStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class WPCLIStep implements StepDefinitionInterface -{ +class WPCLIStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'wp-cli'; /** @var Progress */ @@ -14,52 +14,50 @@ class WPCLIStep implements StepDefinitionInterface /** * The step identifier. + * * @var string */ public $step = 'wp-cli'; /** * The WP CLI command to run. + * * @var string[] */ public $command; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param mixed[] $command - */ - public function setCommand($command) - { + * @param mixed[] $command + */ + public function setCommand( $command ) { $this->command = $command; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/WordPressInstallationOptions.php b/src/WordPress/Blueprints/Model/DataClass/WordPressInstallationOptions.php index 3446a085..214038c7 100644 --- a/src/WordPress/Blueprints/Model/DataClass/WordPressInstallationOptions.php +++ b/src/WordPress/Blueprints/Model/DataClass/WordPressInstallationOptions.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class WordPressInstallationOptions -{ +class WordPressInstallationOptions { + /** @var string */ public $adminUsername; @@ -12,20 +12,18 @@ class WordPressInstallationOptions /** - * @param string $adminUsername - */ - public function setAdminUsername($adminUsername) - { + * @param string $adminUsername + */ + public function setAdminUsername( $adminUsername ) { $this->adminUsername = $adminUsername; return $this; } /** - * @param string $adminPassword - */ - public function setAdminPassword($adminPassword) - { + * @param string $adminPassword + */ + public function setAdminPassword( $adminPassword ) { $this->adminPassword = $adminPassword; return $this; } diff --git a/src/WordPress/Blueprints/Model/DataClass/WriteFileStep.php b/src/WordPress/Blueprints/Model/DataClass/WriteFileStep.php index 784e1845..d2fd90de 100644 --- a/src/WordPress/Blueprints/Model/DataClass/WriteFileStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/WriteFileStep.php @@ -2,8 +2,8 @@ namespace WordPress\Blueprints\Model\DataClass; -class WriteFileStep implements StepDefinitionInterface -{ +class WriteFileStep implements StepDefinitionInterface { + const DISCRIMINATOR = 'writeFile'; /** @var Progress */ @@ -17,59 +17,56 @@ class WriteFileStep implements StepDefinitionInterface /** * The path of the file to write to + * * @var string */ public $path; /** * The data to write + * * @var string|ResourceDefinitionInterface */ public $data; /** - * @param \WordPress\Blueprints\Model\DataClass\Progress $progress - */ - public function setProgress($progress) - { + * @param \WordPress\Blueprints\Model\DataClass\Progress $progress + */ + public function setProgress( $progress ) { $this->progress = $progress; return $this; } /** - * @param bool $continueOnError - */ - public function setContinueOnError($continueOnError) - { + * @param bool $continueOnError + */ + public function setContinueOnError( $continueOnError ) { $this->continueOnError = $continueOnError; return $this; } /** - * @param string $step - */ - public function setStep($step) - { + * @param string $step + */ + public function setStep( $step ) { $this->step = $step; return $this; } /** - * @param string $path - */ - public function setPath($path) - { + * @param string $path + */ + public function setPath( $path ) { $this->path = $path; return $this; } - public function setData($data) - { + public function setData( $data ) { $this->data = $data; return $this; } diff --git a/src/WordPress/Blueprints/Progress/ProgressCaptionEvent.php b/src/WordPress/Blueprints/Progress/ProgressCaptionEvent.php index 5fbe1836..c23a726b 100644 --- a/src/WordPress/Blueprints/Progress/ProgressCaptionEvent.php +++ b/src/WordPress/Blueprints/Progress/ProgressCaptionEvent.php @@ -4,11 +4,10 @@ class ProgressCaptionEvent extends \Symfony\Contracts\EventDispatcher\Event { /** - * @var string - */ - public $caption; - public function __construct(string $caption) - { - $this->caption = $caption; - } + * @var string + */ + public $caption; + public function __construct( string $caption ) { + $this->caption = $caption; + } } diff --git a/src/WordPress/Blueprints/Progress/ProgressEvent.php b/src/WordPress/Blueprints/Progress/ProgressEvent.php index 80b4fb85..ab04a23d 100644 --- a/src/WordPress/Blueprints/Progress/ProgressEvent.php +++ b/src/WordPress/Blueprints/Progress/ProgressEvent.php @@ -26,7 +26,7 @@ public function __construct( float $progress, string $caption ) { - $this->caption = $caption; + $this->caption = $caption; $this->progress = $progress; } } diff --git a/src/WordPress/Blueprints/Progress/Tracker.php b/src/WordPress/Blueprints/Progress/Tracker.php index 123b57b8..b1d6afb2 100644 --- a/src/WordPress/Blueprints/Progress/Tracker.php +++ b/src/WordPress/Blueprints/Progress/Tracker.php @@ -39,19 +39,19 @@ * stage2.finish(); */ class Tracker { - private $selfWeight = 1; - private $selfDone = false; + private $selfWeight = 1; + private $selfDone = false; private $selfProgress = 0; - private $selfCaption = ''; + private $selfCaption = ''; private $weight; - private $subTrackers = []; + private $subTrackers = array(); public $events; - public function __construct( $options = [] ) { - $this->weight = $options['weight'] ?? 1; + public function __construct( $options = array() ) { + $this->weight = $options['weight'] ?? 1; $this->selfCaption = $options['caption'] ?? ''; - $this->events = new EventDispatcher(); + $this->events = new EventDispatcher(); } /** @@ -100,28 +100,36 @@ public function stage( $weight = null, $caption = '' ) { } $this->selfWeight -= $weight; - $subTracker = new Tracker( [ - 'caption' => $caption, - 'weight' => $weight, - ] ); + $subTracker = new Tracker( + array( + 'caption' => $caption, + 'weight' => $weight, + ) + ); $this->subTrackers[] = $subTracker; - $subTracker->events->addListener( ProgressEvent::class, function () { - $this->notifyProgress(); - } ); - $subTracker->events->addListener( DoneEvent::class, function () { - if ( $this->isDone() ) { - $this->notifyDone(); + $subTracker->events->addListener( + ProgressEvent::class, + function () { + $this->notifyProgress(); + } + ); + $subTracker->events->addListener( + DoneEvent::class, + function () { + if ( $this->isDone() ) { + $this->notifyDone(); + } } - } ); + ); return $subTracker; } /** - * @param float $value - * @param string|null $caption - */ - public function set( $value, $caption = null ) { + * @param float $value + * @param string|null $caption + */ + public function set( $value, $caption = null ) { if ( $value < $this->selfProgress ) { throw new \InvalidArgumentException( "Progress cannot go backwards (tried updating to $value when it already was $this->selfProgress)" ); } @@ -145,7 +153,7 @@ public function setCaption( $caption ) { } public function finish() { - $this->selfDone = true; + $this->selfDone = true; $this->selfProgress = 100; $this->notifyProgress(); $this->notifyDone(); @@ -170,9 +178,13 @@ public function getProgress() { if ( $this->selfDone ) { return 100; } - $sum = array_reduce( $this->subTrackers, function ( $sum, $tracker ) { - return $sum + $tracker->getProgress() * $tracker->getWeight(); - }, $this->selfProgress * $this->selfWeight ); + $sum = array_reduce( + $this->subTrackers, + function ( $sum, $tracker ) { + return $sum + $tracker->getProgress() * $tracker->getWeight(); + }, + $this->selfProgress * $this->selfWeight + ); return round( $sum * 10000 ) / 10000; } @@ -193,5 +205,4 @@ private function notifyProgress() { private function notifyDone() { $this->events->dispatch( new DoneEvent() ); } - } diff --git a/src/WordPress/Blueprints/Resources/Resolver/FilesystemResourceResolver.php b/src/WordPress/Blueprints/Resources/Resolver/FilesystemResourceResolver.php index 0611758d..a0b059bc 100644 --- a/src/WordPress/Blueprints/Resources/Resolver/FilesystemResourceResolver.php +++ b/src/WordPress/Blueprints/Resources/Resolver/FilesystemResourceResolver.php @@ -9,32 +9,32 @@ class FilesystemResourceResolver implements ResourceResolverInterface { /** - * @param string $url - */ - public function parseUrl( $url ) { - if ( strncmp($url, 'file://', strlen('file://')) !== 0 ) { + * @param string $url + */ + public function parseUrl( $url ) { + if ( strncmp( $url, 'file://', strlen( 'file://' ) ) !== 0 ) { return null; } return ( new FilesystemResource() )->setPath( $url ); } - static public function getResourceClass(): string { + public static function getResourceClass(): string { return FilesystemResource::class; } /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - */ - public function supports( $resource ): bool { + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + */ + public function supports( $resource ): bool { return $resource instanceof FilesystemResource; } /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - * @param \WordPress\Blueprints\Progress\Tracker $progress_tracker - */ - public function stream( $resource, $progress_tracker ) { + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + * @param \WordPress\Blueprints\Progress\Tracker $progress_tracker + */ + public function stream( $resource, $progress_tracker ) { if ( ! $this->supports( $resource ) ) { throw new \InvalidArgumentException( 'Resource ' . get_class( $resource ) . ' unsupported' ); } @@ -44,5 +44,4 @@ public function stream( $resource, $progress_tracker ) { /** @var $resource FilesystemResource */ return fopen( $resource->path, 'r' ); } - } diff --git a/src/WordPress/Blueprints/Resources/Resolver/InlineResourceResolver.php b/src/WordPress/Blueprints/Resources/Resolver/InlineResourceResolver.php index 4d961240..f8d4774a 100644 --- a/src/WordPress/Blueprints/Resources/Resolver/InlineResourceResolver.php +++ b/src/WordPress/Blueprints/Resources/Resolver/InlineResourceResolver.php @@ -10,9 +10,9 @@ class InlineResourceResolver implements ResourceResolverInterface { /** - * @param string $url - */ - public function parseUrl( $url ) { + * @param string $url + */ + public function parseUrl( $url ) { // If url starts with "protocol://" then we assume it's not inline raw data if ( 0 !== preg_match( '#^[a-z_+]+://#', $url ) ) { return null; @@ -21,33 +21,32 @@ public function parseUrl( $url ) { return ( new InlineResource() )->setContents( $url ); } - static public function getResourceClass(): string { + public static function getResourceClass(): string { return InlineResource::class; } /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - */ - public function supports( $resource ): bool { + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + */ + public function supports( $resource ): bool { return $resource instanceof InlineResource; } /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - * @param \WordPress\Blueprints\Progress\Tracker $progress_tracker - */ - public function stream( $resource, $progress_tracker ) { + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + * @param \WordPress\Blueprints\Progress\Tracker $progress_tracker + */ + public function stream( $resource, $progress_tracker ) { if ( ! $this->supports( $resource ) ) { throw new \InvalidArgumentException( 'Resource ' . get_class( $resource ) . ' unsupported' ); } $progress_tracker->finish(); /** @var $resource InlineResource */ - $fp = fopen( "php://temp", 'r+' ); + $fp = fopen( 'php://temp', 'r+' ); fwrite( $fp, $resource->contents ); rewind( $fp ); return $fp; } - } diff --git a/src/WordPress/Blueprints/Resources/Resolver/ResourceResolverCollection.php b/src/WordPress/Blueprints/Resources/Resolver/ResourceResolverCollection.php index 67584450..31a449ca 100644 --- a/src/WordPress/Blueprints/Resources/Resolver/ResourceResolverCollection.php +++ b/src/WordPress/Blueprints/Resources/Resolver/ResourceResolverCollection.php @@ -23,9 +23,9 @@ public static function getResourceClass(): string { } /** - * @param string $url - */ - public function parseUrl( $url ) { + * @param string $url + */ + public function parseUrl( $url ) { foreach ( $this->resource_resolvers as $resolver ) { /** @var ResourceResolverInterface $resolver */ $resource = $resolver->parseUrl( $url ); @@ -38,9 +38,9 @@ public function parseUrl( $url ) { } /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - */ - public function supports( $resource ): bool { + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + */ + public function supports( $resource ): bool { foreach ( $this->resource_resolvers as $resolver ) { /** @var ResourceResolverInterface $resolver */ if ( $resolver->supports( $resource ) ) { @@ -52,10 +52,10 @@ public function supports( $resource ): bool { } /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - * @param \WordPress\Blueprints\Progress\Tracker $progressTracker - */ - public function stream( $resource, $progressTracker ) { + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + * @param \WordPress\Blueprints\Progress\Tracker $progressTracker + */ + public function stream( $resource, $progressTracker ) { foreach ( $this->resource_resolvers as $resolver ) { /** @var ResourceResolverInterface $resolver */ if ( $resolver->supports( $resource ) ) { diff --git a/src/WordPress/Blueprints/Resources/Resolver/ResourceResolverInterface.php b/src/WordPress/Blueprints/Resources/Resolver/ResourceResolverInterface.php index 7f7b8beb..345b52d7 100644 --- a/src/WordPress/Blueprints/Resources/Resolver/ResourceResolverInterface.php +++ b/src/WordPress/Blueprints/Resources/Resolver/ResourceResolverInterface.php @@ -7,20 +7,20 @@ interface ResourceResolverInterface { /** - * @param string $url - */ - public function parseUrl( $url ); + * @param string $url + */ + public function parseUrl( $url ); /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - */ - public function supports( $resource ): bool; + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + */ + public function supports( $resource ): bool; - static public function getResourceClass(): string; + public static function getResourceClass(): string; /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - * @param \WordPress\Blueprints\Progress\Tracker $progress_tracker - */ - public function stream( $resource, $progress_tracker ); + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + * @param \WordPress\Blueprints\Progress\Tracker $progress_tracker + */ + public function stream( $resource, $progress_tracker ); } diff --git a/src/WordPress/Blueprints/Resources/Resolver/UrlResourceResolver.php b/src/WordPress/Blueprints/Resources/Resolver/UrlResourceResolver.php index 22edb3e7..d70da6b4 100644 --- a/src/WordPress/Blueprints/Resources/Resolver/UrlResourceResolver.php +++ b/src/WordPress/Blueprints/Resources/Resolver/UrlResourceResolver.php @@ -18,10 +18,10 @@ public function __construct( DataSourceInterface $data_source ) { } /** - * @param string $url - */ - public function parseUrl( $url ) { - if ( strncmp($url, 'http://', strlen('http://')) !== 0 && strncmp($url, 'https://', strlen('https://')) !== 0 ) { + * @param string $url + */ + public function parseUrl( $url ) { + if ( strncmp( $url, 'http://', strlen( 'http://' ) ) !== 0 && strncmp( $url, 'https://', strlen( 'https://' ) ) !== 0 ) { return null; } @@ -34,17 +34,17 @@ public static function getResourceClass(): string { } /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - */ - public function supports( $resource ): bool { + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + */ + public function supports( $resource ): bool { return $resource instanceof UrlResource; } /** - * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource - * @param \WordPress\Blueprints\Progress\Tracker $progress_tracker - */ - public function stream( $resource, $progress_tracker ) { + * @param \WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface $resource + * @param \WordPress\Blueprints\Progress\Tracker $progress_tracker + */ + public function stream( $resource, $progress_tracker ) { if ( ! $this->supports( $resource ) ) { throw new InvalidArgumentException( 'Resource ' . get_class( $resource ) . ' unsupported' ); } diff --git a/src/WordPress/Blueprints/Resources/ResourceManager.php b/src/WordPress/Blueprints/Resources/ResourceManager.php index 2ceba893..f713bb77 100644 --- a/src/WordPress/Blueprints/Resources/ResourceManager.php +++ b/src/WordPress/Blueprints/Resources/ResourceManager.php @@ -17,14 +17,14 @@ public function __construct( ResourceResolverCollection $resource_resolvers ) { $this->resource_resolvers = $resource_resolvers; - $this->fs = new Filesystem(); - $this->map = new Map(); + $this->fs = new Filesystem(); + $this->map = new Map(); } /** - * @param mixed[] $compiledResources - */ - public function enqueue( $compiledResources ) { + * @param mixed[] $compiledResources + */ + public function enqueue( $compiledResources ) { foreach ( $compiledResources as $compiled ) { /** @var CompiledResource $compiled */ @@ -40,7 +40,7 @@ public function getStream( $key ) { } public function bufferToTemporaryFile( $resource, $callback, $suffix = null ) { - $fp = $this->getStream( $resource ); + $fp = $this->getStream( $resource ); $path = $this->fs->tempnam( sys_get_temp_dir(), 'resource', $suffix ); $this->fs->dumpFile( $path, $fp ); diff --git a/src/WordPress/Blueprints/Runner/Blueprint/BlueprintRunner.php b/src/WordPress/Blueprints/Runner/Blueprint/BlueprintRunner.php index 8b83878e..85f3fe20 100644 --- a/src/WordPress/Blueprints/Runner/Blueprint/BlueprintRunner.php +++ b/src/WordPress/Blueprints/Runner/Blueprint/BlueprintRunner.php @@ -20,16 +20,16 @@ public function __construct( $resourceManagerFactory ) { $this->resourceManagerFactory = $resourceManagerFactory; - $this->runtime = $runtime; - $this->events = new EventDispatcher(); + $this->runtime = $runtime; + $this->events = new EventDispatcher(); } /** - * @param \WordPress\Blueprints\Compile\CompiledBlueprint $blueprint - */ - public function run( $blueprint ) { + * @param \WordPress\Blueprints\Compile\CompiledBlueprint $blueprint + */ + public function run( $blueprint ) { $resourceManagerFactory = $this->resourceManagerFactory; - $resourceManager = $resourceManagerFactory(); + $resourceManager = $resourceManagerFactory(); $resourceManager->enqueue( $blueprint->compiledResources ); @@ -38,7 +38,7 @@ public function run( $blueprint ) { $compiledStep->runner->setResourceManager( $resourceManager ); } // Run, store results - $results = []; + $results = array(); foreach ( $blueprint->compiledSteps as $k => $compiledStep ) { /** @var CompiledStep $compiledStep */ try { @@ -64,5 +64,4 @@ public function run( $blueprint ) { return $results; } - } diff --git a/src/WordPress/Blueprints/Runner/Step/ActivatePlugin/wp_activate_plugin.php b/src/WordPress/Blueprints/Runner/Step/ActivatePlugin/wp_activate_plugin.php index d42f258d..9e517359 100644 --- a/src/WordPress/Blueprints/Runner/Step/ActivatePlugin/wp_activate_plugin.php +++ b/src/WordPress/Blueprints/Runner/Step/ActivatePlugin/wp_activate_plugin.php @@ -1,14 +1,14 @@ 'Administrator') )[0] ); +set_current_user( get_users( array( 'role' => 'Administrator' ) )[0] ); -$pluginPath = getenv('PLUGIN_PATH'); -if (!is_dir($pluginPath)) { - activate_plugin($pluginPath); +$pluginPath = getenv( 'PLUGIN_PATH' ); +if ( ! is_dir( $pluginPath ) ) { + activate_plugin( $pluginPath ); die(); } @@ -21,4 +21,4 @@ } // If we got here, the plugin was not found. -exit(1); +exit( 1 ); diff --git a/src/WordPress/Blueprints/Runner/Step/ActivatePluginStepRunner.php b/src/WordPress/Blueprints/Runner/Step/ActivatePluginStepRunner.php index 7889b7eb..783c0fe0 100644 --- a/src/WordPress/Blueprints/Runner/Step/ActivatePluginStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/ActivatePluginStepRunner.php @@ -9,33 +9,33 @@ class ActivatePluginStepRunner extends BaseStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\ActivatePluginStep $input - * @param \WordPress\Blueprints\Progress\Tracker $tracker - */ - function run( $input, $tracker ) { - ($nullsafeVariable1 = $tracker) ? $nullsafeVariable1->setCaption($input->progress->caption ?? "Activating plugin " . $input->slug) : null; + * @param \WordPress\Blueprints\Model\DataClass\ActivatePluginStep $input + * @param \WordPress\Blueprints\Progress\Tracker $tracker + */ + function run( $input, $tracker ) { + ( $nullsafeVariable1 = $tracker ) ? $nullsafeVariable1->setCaption( $input->progress->caption ?? 'Activating plugin ' . $input->slug ) : null; // @TODO: Compare performance to the wp_activate_plugin.php script. - // On the first sight it seems to be significantly faster. + // On the first sight it seems to be significantly faster. return $this->getRuntime()->runShellCommand( - [ + array( 'php', 'wp-cli.phar', 'plugin', 'activate', $input->slug, - ] + ) ); -// return $this->getRuntime()->evalPhpInSubProcess( -// file_get_contents( __DIR__ . '/ActivatePlugin/wp_activate_plugin.php' ), -// [ -// 'PLUGIN_PATH' => $input->pluginPath, -// ] -// ); + // return $this->getRuntime()->evalPhpInSubProcess( + // file_get_contents( __DIR__ . '/ActivatePlugin/wp_activate_plugin.php' ), + // [ + // 'PLUGIN_PATH' => $input->pluginPath, + // ] + // ); } public function getDefaultCaption( $input ) { - return "Activating plugin"; + return 'Activating plugin'; } } diff --git a/src/WordPress/Blueprints/Runner/Step/ActivateTheme/wp_activate_theme.php b/src/WordPress/Blueprints/Runner/Step/ActivateTheme/wp_activate_theme.php index a653c2d0..4fa3982d 100644 --- a/src/WordPress/Blueprints/Runner/Step/ActivateTheme/wp_activate_theme.php +++ b/src/WordPress/Blueprints/Runner/Step/ActivateTheme/wp_activate_theme.php @@ -1,7 +1,7 @@ 'Administrator') )[0] ); -switch_theme( getenv('THEME_FOLDER_NAME') ); +set_current_user( get_users( array( 'role' => 'Administrator' ) )[0] ); +switch_theme( getenv( 'THEME_FOLDER_NAME' ) ); diff --git a/src/WordPress/Blueprints/Runner/Step/ActivateThemeStepRunner.php b/src/WordPress/Blueprints/Runner/Step/ActivateThemeStepRunner.php index 03f67944..455f441b 100644 --- a/src/WordPress/Blueprints/Runner/Step/ActivateThemeStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/ActivateThemeStepRunner.php @@ -11,40 +11,40 @@ class ActivateThemeStepRunner extends BaseStepRunner { - static public function getStepClass(): string { + public static function getStepClass(): string { return ActivateThemeStep::class; } /** - * @param ActivateThemeStep $input - * @return string|null - */ - public function getDefaultCaption( $input ) { - return "Activating theme " . $input->slug; + * @param ActivateThemeStep $input + * @return string|null + */ + public function getDefaultCaption( $input ) { + return 'Activating theme ' . $input->slug; } /** - * @param \WordPress\Blueprints\Model\DataClass\ActivateThemeStep $input - * @param \WordPress\Blueprints\Progress\Tracker $tracker - */ - public function run( $input, $tracker ) { + * @param \WordPress\Blueprints\Model\DataClass\ActivateThemeStep $input + * @param \WordPress\Blueprints\Progress\Tracker $tracker + */ + public function run( $input, $tracker ) { // @TODO: Compare performance to the wp_activate_theme.php script. - // On the first sight it seems to be significantly faster. + // On the first sight it seems to be significantly faster. return $this->getRuntime()->runShellCommand( - [ + array( 'php', 'wp-cli.phar', 'theme', 'activate', $input->slug, - ] + ) ); -// return $this->getRuntime()->evalPhpInSubProcess( -// file_get_contents( __DIR__ . '/ActivatePlugin/wp_activate_theme.php' ), -// [ -// 'THEME_FOLDER_NAME' => $this->input->themeFolderName, -// ] -// ); + // return $this->getRuntime()->evalPhpInSubProcess( + // file_get_contents( __DIR__ . '/ActivatePlugin/wp_activate_theme.php' ), + // [ + // 'THEME_FOLDER_NAME' => $this->input->themeFolderName, + // ] + // ); } } diff --git a/src/WordPress/Blueprints/Runner/Step/BaseStepRunner.php b/src/WordPress/Blueprints/Runner/Step/BaseStepRunner.php index 8a81093c..77052467 100644 --- a/src/WordPress/Blueprints/Runner/Step/BaseStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/BaseStepRunner.php @@ -11,9 +11,9 @@ abstract class BaseStepRunner implements StepRunnerInterface { protected $runtime; /** - * @param \WordPress\Blueprints\Resources\ResourceManager $map - */ - public function setResourceManager( $map ) { + * @param \WordPress\Blueprints\Resources\ResourceManager $map + */ + public function setResourceManager( $map ) { $this->resourceManager = $map; } @@ -22,9 +22,9 @@ protected function getResource( $declaration ) { } /** - * @param \WordPress\Blueprints\Runtime\RuntimeInterface $runtime - */ - public function setRuntime( $runtime ) { + * @param \WordPress\Blueprints\Runtime\RuntimeInterface $runtime + */ + public function setRuntime( $runtime ) { $this->runtime = $runtime; } diff --git a/src/WordPress/Blueprints/Runner/Step/DefineSiteUrlStepRunner.php b/src/WordPress/Blueprints/Runner/Step/DefineSiteUrlStepRunner.php index 803c3573..df7dc24f 100644 --- a/src/WordPress/Blueprints/Runner/Step/DefineSiteUrlStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/DefineSiteUrlStepRunner.php @@ -8,23 +8,29 @@ class DefineSiteUrlStepRunner extends BaseStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\DefineSiteUrlStep $input - */ - function run( $input ) { + * @param \WordPress\Blueprints\Model\DataClass\DefineSiteUrlStep $input + */ + function run( $input ) { // @TODO: Don't manually construct the step object like this. - // There may be more required fields in the future. - // Instead, either remove this step, move the const-setting - // logic to another class with crisply defined dependencies, - // or provide a method similar to: - // $executionContext->createStepRunner( DefineWpConfigConstsStepRunner::class ) + // There may be more required fields in the future. + // Instead, either remove this step, move the const-setting + // logic to another class with crisply defined dependencies, + // or provide a method similar to: + // $executionContext->createStepRunner( DefineWpConfigConstsStepRunner::class ) $defineConstsHandler = new DefineWpConfigConstsStepRunner(); $defineConstsHandler->setRuntime( $this->getRuntime() ); - $defineConstsHandler->run( ( new DefineWpConfigConstsStep() ) - ->setConsts( [ 'WP_HOME' => $input->siteUrl, 'WP_SITEURL' => $input->siteUrl ] ) + $defineConstsHandler->run( + ( new DefineWpConfigConstsStep() ) + ->setConsts( + array( + 'WP_HOME' => $input->siteUrl, + 'WP_SITEURL' => $input->siteUrl, + ) + ) ); } public function getDefaultCaption( $input ) { - return "Defining site URL"; + return 'Defining site URL'; } } diff --git a/src/WordPress/Blueprints/Runner/Step/DefineWpConfigConsts/functions.php b/src/WordPress/Blueprints/Runner/Step/DefineWpConfigConsts/functions.php index 9a83ae1c..740e51be 100644 --- a/src/WordPress/Blueprints/Runner/Step/DefineWpConfigConsts/functions.php +++ b/src/WordPress/Blueprints/Runner/Step/DefineWpConfigConsts/functions.php @@ -68,24 +68,24 @@ * * @return string */ -function rewrite_wp_config_to_define_constants( $content, $constants = [] ) { +function rewrite_wp_config_to_define_constants( $content, $constants = array() ) { $tokens = array_reverse( token_get_all( $content ) ); - $output = []; - $defined_expressions = []; + $output = array(); + $defined_expressions = array(); // Look through all the tokens and find the define calls do { - $buffer = []; - $name_buffer = []; - $value_buffer = []; - $third_arg_buffer = []; + $buffer = array(); + $name_buffer = array(); + $value_buffer = array(); + $third_arg_buffer = array(); // Capture everything until the define call into output. // Capturing the define call into a buffer. // Example: - // 2 ? 'WP_DEBUG' : 'FOO', true); - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // define(count([1,2]) > 2 ? 'WP_DEBUG' : 'FOO', true); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ $open_parenthesis = 0; while ( $token = array_pop( $tokens ) ) { $buffer[] = $token; - if ( $token === "(" || $token === "[" || $token === "{" ) { - ++ $open_parenthesis; - } elseif ( $token === ")" || $token === "]" || $token === "}" ) { - -- $open_parenthesis; - } elseif ( $token === "," && $open_parenthesis === 0 ) { + if ( $token === '(' || $token === '[' || $token === '{' ) { + ++$open_parenthesis; + } elseif ( $token === ')' || $token === ']' || $token === '}' ) { + --$open_parenthesis; + } elseif ( $token === ',' && $open_parenthesis === 0 ) { break; } @@ -167,20 +167,20 @@ function rewrite_wp_config_to_define_constants( $content, $constants = [] ) { } // Capture everything until the closing parenthesis - // define("WP_DEBUG", true); - // ^^^^^^ + // define("WP_DEBUG", true); + // ^^^^^^ $open_parenthesis = 0; $is_second_argument = true; while ( $token = array_pop( $tokens ) ) { $buffer[] = $token; - if ( $token === ")" && $open_parenthesis === 0 ) { + if ( $token === ')' && $open_parenthesis === 0 ) { // Final parenthesis of the define call. break; - } elseif ( $token === "(" || $token === "[" || $token === "{" ) { - ++ $open_parenthesis; - } elseif ( $token === ")" || $token === "]" || $token === "}" ) { - -- $open_parenthesis; - } elseif ( $token === "," && $open_parenthesis === 0 ) { + } elseif ( $token === '(' || $token === '[' || $token === '{' ) { + ++$open_parenthesis; + } elseif ( $token === ')' || $token === ']' || $token === '}' ) { + --$open_parenthesis; + } elseif ( $token === ',' && $open_parenthesis === 0 ) { // This define call has more than 2 arguments! The third one is the // boolean value indicating $is_case_insensitive. Let's continue capturing // to $third_arg_buffer. @@ -194,11 +194,11 @@ function rewrite_wp_config_to_define_constants( $content, $constants = [] ) { } // Capture until the semicolon - // define("WP_DEBUG", true) ; - // ^^^ + // define("WP_DEBUG", true) ; + // ^^^ while ( $token = array_pop( $tokens ) ) { $buffer[] = $token; - if ( $token === ";" ) { + if ( $token === ';' ) { break; } } @@ -218,7 +218,7 @@ function rewrite_wp_config_to_define_constants( $content, $constants = [] ) { $name_is_literal = false; break; } - } elseif ( $token !== "(" && $token !== ")" ) { + } elseif ( $token !== '(' && $token !== ')' ) { $name_is_literal = false; break; } @@ -237,16 +237,16 @@ function rewrite_wp_config_to_define_constants( $content, $constants = [] ) { } $output = array_merge( $output, - [ "if(!defined(" ], + array( 'if(!defined(' ), $name_buffer, - [ ")) {\n " ], - [ 'define(' ], + array( ")) {\n " ), + array( 'define(' ), $name_buffer, - [ ',' ], + array( ',' ), $value_buffer, $third_arg_buffer, - [ ");" ], - [ "\n}\n" ] + array( ');' ), + array( "\n}\n" ) ); continue; } @@ -266,12 +266,12 @@ function rewrite_wp_config_to_define_constants( $content, $constants = [] ) { // Let's rewrite its value to the one $output = array_merge( $output, - [ 'define(' ], + array( 'define(' ), $name_buffer, - [ ',' ], - [ var_export( $constants[ $name ], true ) ], + array( ',' ), + array( var_export( $constants[ $name ], true ) ), $third_arg_buffer, - [ ");" ] + array( ');' ) ); // Remove the constant from the list so we can process any remaining @@ -281,22 +281,22 @@ function rewrite_wp_config_to_define_constants( $content, $constants = [] ) { // Add any constants that weren't found in the file if ( count( $constants ) ) { - $prepend = [ + $prepend = array( " $value ) { $prepend = array_merge( $prepend, - [ - "define(", + array( + 'define(', var_export( $name, true ), ',', var_export( $value, true ), ");\n", - ] + ) ); } - $prepend[] = "?>"; + $prepend[] = '?>'; $output = array_merge( $prepend, $output @@ -321,7 +321,7 @@ function stringify_tokens( $tokens ) { } function skip_whitespace( $tokens ) { - $output = []; + $output = array(); foreach ( $tokens as $token ) { if ( is_array( $token ) && ( $token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT ) ) { continue; diff --git a/src/WordPress/Blueprints/Runner/Step/DefineWpConfigConstsStepRunner.php b/src/WordPress/Blueprints/Runner/Step/DefineWpConfigConstsStepRunner.php index 0c84cd5c..dad321da 100644 --- a/src/WordPress/Blueprints/Runner/Step/DefineWpConfigConstsStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/DefineWpConfigConstsStepRunner.php @@ -7,9 +7,9 @@ class DefineWpConfigConstsStepRunner extends BaseStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\DefineWpConfigConstsStep $input - */ - function run( $input ) { + * @param \WordPress\Blueprints\Model\DataClass\DefineWpConfigConstsStep $input + */ + function run( $input ) { $functions = file_get_contents( __DIR__ . '/DefineWpConfigConsts/functions.php' ); return $this->getRuntime()->evalPhpInSubProcess( @@ -20,14 +20,13 @@ function run( $input ) { $new_wp_config = rewrite_wp_config_to_define_constants($wp_config, $consts); file_put_contents($wp_config_path, $new_wp_config); ', - [ + array( 'CONSTS' => json_encode( $input->consts ), - ] + ) ); } public function getDefaultCaption( $input ) { - return "Defining wp-config constants"; + return 'Defining wp-config constants'; } - } diff --git a/src/WordPress/Blueprints/Runner/Step/DownloadWordPressStepRunner.php b/src/WordPress/Blueprints/Runner/Step/DownloadWordPressStepRunner.php index e39d443e..6c34c443 100644 --- a/src/WordPress/Blueprints/Runner/Step/DownloadWordPressStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/DownloadWordPressStepRunner.php @@ -8,24 +8,23 @@ class DownloadWordPressStepRunner extends InstallAssetStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\DownloadWordPressStep $input - * @param \WordPress\Blueprints\Progress\Tracker $progress - */ - public function run( + * @param \WordPress\Blueprints\Model\DataClass\DownloadWordPressStep $input + * @param \WordPress\Blueprints\Progress\Tracker $progress + */ + public function run( $input, $progress ) { $this->unzipAssetTo( $input->wordPressZip, $this->getRuntime()->getDocumentRoot() ); $cofigSample = $this->getRuntime()->resolvePath( 'wp-config-sample.php' ); - $cofig = $this->getRuntime()->resolvePath( 'wp-config.php' ); + $cofig = $this->getRuntime()->resolvePath( 'wp-config.php' ); if ( file_exists( $cofigSample ) && ! file_exists( $cofig ) ) { copy( $cofigSample, $cofig ); } } public function getDefaultCaption( $input ) { - return "Extracting WordPress"; + return 'Extracting WordPress'; } - } diff --git a/src/WordPress/Blueprints/Runner/Step/EnableMultisiteStepRunner.php b/src/WordPress/Blueprints/Runner/Step/EnableMultisiteStepRunner.php index f8aad316..39bf5cc9 100644 --- a/src/WordPress/Blueprints/Runner/Step/EnableMultisiteStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/EnableMultisiteStepRunner.php @@ -12,16 +12,16 @@ function run( $input ) { throw new \LogicException( 'Not implemented yet' ); // @TODO: -// return $this->getRuntime()->runShellCommand( -// [ -// 'php', -// 'wp-cli.phar', -// 'core', -// 'multisite-convert', -// // @TODO: Base path should come from the runtime, e.g. -// // Playground need to provide the /scope:0.892173/ value -// '--base=/wordpress', -// ] -// ); + // return $this->getRuntime()->runShellCommand( + // [ + // 'php', + // 'wp-cli.phar', + // 'core', + // 'multisite-convert', + // @TODO: Base path should come from the runtime, e.g. + // Playground need to provide the /scope:0.892173/ value + // '--base=/wordpress', + // ] + // ); } } diff --git a/src/WordPress/Blueprints/Runner/Step/EvalPHPCallbackStepRunner.php b/src/WordPress/Blueprints/Runner/Step/EvalPHPCallbackStepRunner.php index 000fc0dc..849b893d 100644 --- a/src/WordPress/Blueprints/Runner/Step/EvalPHPCallbackStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/EvalPHPCallbackStepRunner.php @@ -8,7 +8,7 @@ class EvalPHPCallbackStepRunner extends BaseStepRunner { /** * @param EvalPHPCallbackStep $input - * @param Tracker $tracker + * @param Tracker $tracker */ function run( $input, $tracker ) { if ( ! is_callable( $input->callback ) ) { diff --git a/src/WordPress/Blueprints/Runner/Step/ImportFileStepRunner.php b/src/WordPress/Blueprints/Runner/Step/ImportFileStepRunner.php index 1aa6f15c..24eb3362 100644 --- a/src/WordPress/Blueprints/Runner/Step/ImportFileStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/ImportFileStepRunner.php @@ -10,28 +10,28 @@ class ImportFileStepRunner extends BaseStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\ImportFileStep $input - * @param \WordPress\Blueprints\Progress\Tracker $tracker - */ - function run( $input, $tracker ) { - ($nullsafeVariable1 = $tracker) ? $nullsafeVariable1->setCaption($input->progress->caption ?? "Importing starter content") : null; + * @param \WordPress\Blueprints\Model\DataClass\ImportFileStep $input + * @param \WordPress\Blueprints\Progress\Tracker $tracker + */ + function run( $input, $tracker ) { + ( $nullsafeVariable1 = $tracker ) ? $nullsafeVariable1->setCaption( $input->progress->caption ?? 'Importing starter content' ) : null; // @TODO: Install the wordpress-importer plugin if it's not already installed - // wp plugin install wordpress-importer --activate - // Perhaps we'll need to package up some of these tasks in separate classes - // to make them more reusable? Or should we just reuse the existing steps? + // wp plugin install wordpress-importer --activate + // Perhaps we'll need to package up some of these tasks in separate classes + // to make them more reusable? Or should we just reuse the existing steps? return $this->resourceManager->bufferToTemporaryFile( $input->file, function ( $path ) use ( $input ) { return $this->getRuntime()->runShellCommand( - [ + array( 'php', 'wp-cli.phar', 'import', $path, '--authors=create', - ] + ) ); }, '.wxr' diff --git a/src/WordPress/Blueprints/Runner/Step/InstallAssetStepRunner.php b/src/WordPress/Blueprints/Runner/Step/InstallAssetStepRunner.php index 7995403e..61c75940 100644 --- a/src/WordPress/Blueprints/Runner/Step/InstallAssetStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/InstallAssetStepRunner.php @@ -15,16 +15,18 @@ protected function unzipAssetTo( $zipResource, $targetPath ) { if ( ! file_exists( $targetPath ) ) { mkdir( $targetPath, 0777, true ); } - $this->getRuntime()->withTemporaryDirectory( function ( $tmpPath ) use ( $zipResource, $targetPath ) { - zip_extract_to( $this->getResource( $zipResource ), $tmpPath ); - $extractedFiles = list_files( $tmpPath, $omitDotFiles = true ); - $onlyExtractedSingleDirectory = count( $extractedFiles ) === 1 && is_dir( $tmpPath . '/' . $extractedFiles[0] ); - $moveFromPath = $onlyExtractedSingleDirectory ? "$tmpPath/$extractedFiles[0]" : $tmpPath; + $this->getRuntime()->withTemporaryDirectory( + function ( $tmpPath ) use ( $zipResource, $targetPath ) { + zip_extract_to( $this->getResource( $zipResource ), $tmpPath ); + $extractedFiles = list_files( $tmpPath, $omitDotFiles = true ); + $onlyExtractedSingleDirectory = count( $extractedFiles ) === 1 && is_dir( $tmpPath . '/' . $extractedFiles[0] ); + $moveFromPath = $onlyExtractedSingleDirectory ? "$tmpPath/$extractedFiles[0]" : $tmpPath; - move_files_from_directory_to_directory( - $moveFromPath, - $this->getRuntime()->resolvePath( $targetPath ) - ); - } ); + move_files_from_directory_to_directory( + $moveFromPath, + $this->getRuntime()->resolvePath( $targetPath ) + ); + } + ); } } diff --git a/src/WordPress/Blueprints/Runner/Step/InstallPluginStepRunner.php b/src/WordPress/Blueprints/Runner/Step/InstallPluginStepRunner.php index b5322189..ccf56447 100644 --- a/src/WordPress/Blueprints/Runner/Step/InstallPluginStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/InstallPluginStepRunner.php @@ -9,12 +9,12 @@ class InstallPluginStepRunner extends InstallAssetStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\InstallPluginStep $input - * @param \WordPress\Blueprints\Progress\Tracker $tracker - */ - function run( $input, $tracker ) { + * @param \WordPress\Blueprints\Model\DataClass\InstallPluginStep $input + * @param \WordPress\Blueprints\Progress\Tracker $tracker + */ + function run( $input, $tracker ) { // @TODO: inject this information into this step - $pluginDir = 'plugin' . rand( 0, 1000 ); + $pluginDir = 'plugin' . rand( 0, 1000 ); $targetPath = $this->getRuntime()->resolvePath( 'wp-content/plugins/' . $pluginDir ); $this->unzipAssetTo( $input->pluginZipFile, $targetPath ); @@ -23,14 +23,14 @@ function run( $input, $tracker ) { // plugins in WordPress are identified by their path, not slug. $this->getRuntime()->evalPhpInSubProcess( file_get_contents( __DIR__ . '/ActivatePlugin/wp_activate_plugin.php' ), - [ + array( 'PLUGIN_PATH' => $targetPath, - ] + ) ); } } public function getDefaultCaption( $input ) { - return "Installing plugin " . $input->pluginZipFile; + return 'Installing plugin ' . $input->pluginZipFile; } } diff --git a/src/WordPress/Blueprints/Runner/Step/InstallSqliteIntegrationStepRunner.php b/src/WordPress/Blueprints/Runner/Step/InstallSqliteIntegrationStepRunner.php index 6be1d28b..63ffe998 100644 --- a/src/WordPress/Blueprints/Runner/Step/InstallSqliteIntegrationStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/InstallSqliteIntegrationStepRunner.php @@ -9,10 +9,10 @@ class InstallSqliteIntegrationStepRunner extends InstallAssetStepRunner { /** * @param InstallSqliteIntegrationStep $input - * @param Tracker $tracker + * @param Tracker $tracker */ function run( $input, $tracker ) { - $pluginDir = 'sqlite-database-integration'; + $pluginDir = 'sqlite-database-integration'; $targetPath = $this->getRuntime()->resolvePath( 'wp-content/mu-plugins/' . $pluginDir ); $this->unzipAssetTo( $input->sqlitePluginZip, $targetPath ); @@ -29,12 +29,13 @@ function run( $input, $tracker ) { $db ); file_put_contents( $this->getRuntime()->resolvePath( 'wp-content/db.php' ), $db ); - file_put_contents( $this->getRuntime()->resolvePath( 'wp-content/mu-plugins/0-sqlite.php' ), - 'getRuntime()->resolvePath( 'wp-content/mu-plugins/0-sqlite.php' ), + 'getRuntime()->resolvePath( 'wp-content/themes/' . $themeDir ); $this->unzipAssetTo( $input->themeZipFile, $targetPath ); @@ -25,15 +25,14 @@ function run( $input, $tracker ) { // plugins in WordPress are identified by their path, not slug. $this->getRuntime()->evalPhpInSubProcess( file_get_contents( __DIR__ . '/ActivateTheme/wp_activate_theme.php' ), - [ + array( 'THEME_FOLDER_NAME' => $themeDir, - ] + ) ); } } public function getDefaultCaption( $input ) { - return "Installing theme " . $input->themeZipFile; + return 'Installing theme ' . $input->themeZipFile; } - } diff --git a/src/WordPress/Blueprints/Runner/Step/MkdirStepRunner.php b/src/WordPress/Blueprints/Runner/Step/MkdirStepRunner.php index e7af07c5..86d99370 100644 --- a/src/WordPress/Blueprints/Runner/Step/MkdirStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/MkdirStepRunner.php @@ -8,9 +8,9 @@ class MkdirStepRunner extends BaseStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\MkdirStep $input - */ - function run( $input ) { + * @param \WordPress\Blueprints\Model\DataClass\MkdirStep $input + */ + function run( $input ) { // @TODO: Treat $input->path as relative path to the document root (unless it's absolute) $success = mkdir( $input->path ); if ( ! $success ) { diff --git a/src/WordPress/Blueprints/Runner/Step/RmStepRunner.php b/src/WordPress/Blueprints/Runner/Step/RmStepRunner.php index 23df8388..63394382 100644 --- a/src/WordPress/Blueprints/Runner/Step/RmStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/RmStepRunner.php @@ -7,22 +7,21 @@ use WordPress\Blueprints\BlueprintException; use WordPress\Blueprints\Model\DataClass\RmStep; -class RmStepRunner extends BaseStepRunner -{ - /** - * @param RmStep $input - */ - public function run($input) - { - $resolvedPath = $this->getRuntime()->resolvePath($input->path); - $fileSystem = new Filesystem(); - if (false === $fileSystem->exists($resolvedPath)) { - throw new BlueprintException("Failed to remove \"$resolvedPath\": the directory or file does not exist."); - } - try { - $fileSystem->remove($resolvedPath); - } catch (IOException $exception) { - throw new BlueprintException("Failed to remove the directory or file at \"$resolvedPath\"", 0, $exception); - } - } +class RmStepRunner extends BaseStepRunner { + + /** + * @param RmStep $input + */ + public function run( $input ) { + $resolvedPath = $this->getRuntime()->resolvePath( $input->path ); + $fileSystem = new Filesystem(); + if ( false === $fileSystem->exists( $resolvedPath ) ) { + throw new BlueprintException( "Failed to remove \"$resolvedPath\": the directory or file does not exist." ); + } + try { + $fileSystem->remove( $resolvedPath ); + } catch ( IOException $exception ) { + throw new BlueprintException( "Failed to remove the directory or file at \"$resolvedPath\"", 0, $exception ); + } + } } diff --git a/src/WordPress/Blueprints/Runner/Step/RunPHPStepRunner.php b/src/WordPress/Blueprints/Runner/Step/RunPHPStepRunner.php index f1ae4a40..65f4f9f3 100644 --- a/src/WordPress/Blueprints/Runner/Step/RunPHPStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/RunPHPStepRunner.php @@ -7,11 +7,11 @@ class RunPHPStepRunner extends BaseStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\RunPHPStep $input - * @param \WordPress\Blueprints\Progress\Tracker $tracker - */ - function run( $input, $tracker ) { - ($nullsafeVariable1 = $tracker) ? $nullsafeVariable1->setCaption("Running custom PHP code") : null; + * @param \WordPress\Blueprints\Model\DataClass\RunPHPStep $input + * @param \WordPress\Blueprints\Progress\Tracker $tracker + */ + function run( $input, $tracker ) { + ( $nullsafeVariable1 = $tracker ) ? $nullsafeVariable1->setCaption( 'Running custom PHP code' ) : null; return $this->getRuntime()->evalPhpInSubProcess( $input->code ); } diff --git a/src/WordPress/Blueprints/Runner/Step/RunSQLStepRunner.php b/src/WordPress/Blueprints/Runner/Step/RunSQLStepRunner.php index 8032d545..c2f96728 100644 --- a/src/WordPress/Blueprints/Runner/Step/RunSQLStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/RunSQLStepRunner.php @@ -11,14 +11,15 @@ class RunSQLStepRunner extends BaseStepRunner { /** - * @param RunSQLStep $input - * @param \WordPress\Blueprints\Progress\Tracker|null $progress - */ - function run( + * @param RunSQLStep $input + * @param \WordPress\Blueprints\Progress\Tracker|null $progress + */ + function run( $input, $progress = null ) { - return $this->getRuntime()->evalPhpInSubProcess( <<<'CODE' + return $this->getRuntime()->evalPhpInSubProcess( + <<<'CODE' getResource( $input->sql ) ); } public function getDefaultCaption( $input ) { - return "Running SQL queries"; + return 'Running SQL queries'; } } diff --git a/src/WordPress/Blueprints/Runner/Step/RunWordPressInstallerStepRunner.php b/src/WordPress/Blueprints/Runner/Step/RunWordPressInstallerStepRunner.php index 0ef91f3f..b32f5044 100644 --- a/src/WordPress/Blueprints/Runner/Step/RunWordPressInstallerStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/RunWordPressInstallerStepRunner.php @@ -7,12 +7,12 @@ class RunWordPressInstallerStepRunner extends BaseStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\RunWordPressInstallerStep $input - * @param \WordPress\Blueprints\Progress\Tracker $tracker - */ - function run( $input, $tracker ) { + * @param \WordPress\Blueprints\Model\DataClass\RunWordPressInstallerStep $input + * @param \WordPress\Blueprints\Progress\Tracker $tracker + */ + function run( $input, $tracker ) { return $this->getRuntime()->runShellCommand( - [ + array( 'php', 'wp-cli.phar', 'core', @@ -22,13 +22,12 @@ function run( $input, $tracker ) { '--admin_user=' . $input->options->adminUsername, '--admin_password=' . $input->options->adminPassword, '--admin_email=admin@wordpress.internal', - ], + ), $this->getRuntime()->getDocumentRoot() ); } public function getDefaultCaption( $input ) { - return "Installing WordPress"; + return 'Installing WordPress'; } - } diff --git a/src/WordPress/Blueprints/Runner/Step/SetSiteOptionsStepRunner.php b/src/WordPress/Blueprints/Runner/Step/SetSiteOptionsStepRunner.php index e032b12d..4bc9447b 100644 --- a/src/WordPress/Blueprints/Runner/Step/SetSiteOptionsStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/SetSiteOptionsStepRunner.php @@ -8,13 +8,14 @@ class SetSiteOptionsStepRunner extends BaseStepRunner { /** - * @param SetSiteOptionsStep $input - * @param \WordPress\Blueprints\Progress\Tracker $tracker - */ - function run( $input, $tracker ) { + * @param SetSiteOptionsStep $input + * @param \WordPress\Blueprints\Progress\Tracker $tracker + */ + function run( $input, $tracker ) { // Running a custom PHP script is much faster than setting each option // with a separate wp-cli command. - return $this->getRuntime()->evalPhpInSubProcess( ' + return $this->getRuntime()->evalPhpInSubProcess( + ' json_encode( $input->options ), - ] + ) ); } public function getDefaultCaption( $input ) { - return "Setting site options"; + return 'Setting site options'; } - } diff --git a/src/WordPress/Blueprints/Runner/Step/StepRunnerInterface.php b/src/WordPress/Blueprints/Runner/Step/StepRunnerInterface.php index 1f3a83c1..5056ba20 100644 --- a/src/WordPress/Blueprints/Runner/Step/StepRunnerInterface.php +++ b/src/WordPress/Blueprints/Runner/Step/StepRunnerInterface.php @@ -8,16 +8,16 @@ interface StepRunnerInterface { /** - * @param \WordPress\Blueprints\Resources\ResourceManager $map - */ - public function setResourceManager( $map ); + * @param \WordPress\Blueprints\Resources\ResourceManager $map + */ + public function setResourceManager( $map ); /** - * @param \WordPress\Blueprints\Runtime\RuntimeInterface $runtime - */ - public function setRuntime( $runtime ); + * @param \WordPress\Blueprints\Runtime\RuntimeInterface $runtime + */ + public function setRuntime( $runtime ); -// @TODO: Document how this method isn't defined because -// PHP doens't support covariant arguments -// function run( StepInterface $input, Tracker $tracker ); + // @TODO: Document how this method isn't defined because + // PHP doens't support covariant arguments + // function run( StepInterface $input, Tracker $tracker ); } diff --git a/src/WordPress/Blueprints/Runner/Step/WriteFileStepRunner.php b/src/WordPress/Blueprints/Runner/Step/WriteFileStepRunner.php index a7a83930..3e9bee3b 100644 --- a/src/WordPress/Blueprints/Runner/Step/WriteFileStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/WriteFileStepRunner.php @@ -7,10 +7,10 @@ class WriteFileStepRunner extends BaseStepRunner { /** - * @param \WordPress\Blueprints\Model\DataClass\WriteFileStep $input - * @param \WordPress\Blueprints\Progress\Tracker|null $progress - */ - public function run( + * @param \WordPress\Blueprints\Model\DataClass\WriteFileStep $input + * @param \WordPress\Blueprints\Progress\Tracker|null $progress + */ + public function run( $input, $progress = null ) { @@ -30,7 +30,6 @@ public function run( } public function getDefaultCaption( $input ) { - return "Writing file " . $input->path; + return 'Writing file ' . $input->path; } - } diff --git a/src/WordPress/Blueprints/Runtime/ProcessFailedException.php b/src/WordPress/Blueprints/Runtime/ProcessFailedException.php index 5bd5909d..8a6bf161 100644 --- a/src/WordPress/Blueprints/Runtime/ProcessFailedException.php +++ b/src/WordPress/Blueprints/Runtime/ProcessFailedException.php @@ -16,7 +16,7 @@ class ProcessFailedException extends BlueprintException { public function __construct( Process $process, Throwable $previous = null ) { $this->process = $process; parent::__construct( - "Process `" . $process->getCommandLine() . "` failed with exit code " . $process->getExitCode() . " and the following stderr output: \n" . $process->getErrorOutput() . "\n" . $process->getOutput(), + 'Process `' . $process->getCommandLine() . '` failed with exit code ' . $process->getExitCode() . " and the following stderr output: \n" . $process->getErrorOutput() . "\n" . $process->getOutput(), $process->getExitCode(), $previous ); @@ -25,5 +25,4 @@ public function __construct( Process $process, Throwable $previous = null ) { public function getProcess(): Process { return $this->process; } - } diff --git a/src/WordPress/Blueprints/Runtime/Runtime.php b/src/WordPress/Blueprints/Runtime/Runtime.php index 011b3918..22e03579 100644 --- a/src/WordPress/Blueprints/Runtime/Runtime.php +++ b/src/WordPress/Blueprints/Runtime/Runtime.php @@ -16,7 +16,7 @@ public function __construct( string $documentRoot ) { $this->documentRoot = $documentRoot; - $this->fs = new Filesystem(); + $this->fs = new Filesystem(); if ( ! file_exists( $this->getDocumentRoot() ) ) { $this->fs->mkdir( $this->getDocumentRoot() ); } @@ -29,15 +29,15 @@ public function __construct( // @TODO: should this class mediate network requests? // @TODO: Move these filesystem operations to a separate class - // Maybe ExecutionContext? Or a separate Filesystem class? + // Maybe ExecutionContext? Or a separate Filesystem class? public function getDocumentRoot(): string { return $this->documentRoot; } /** - * @param string $path - */ - public function resolvePath( $path ): string { + * @param string $path + */ + public function resolvePath( $path ): string { return Path::makeAbsolute( $path, $this->getDocumentRoot() ); } @@ -50,7 +50,6 @@ public function withTemporaryDirectory( $callback ) { } finally { $this->fs->remove( $path ); } - } public function withTemporaryFile( $callback, $suffix = null ) { @@ -67,32 +66,32 @@ public function getTempRoot() { // `/tmp` may be on another filesystem and we couldn't move files across filesystems // without a slow recursive copy. return join_paths( $this->getDocumentRoot(), '/tmp' ); -// return sys_get_temp_dir(); + // return sys_get_temp_dir(); } // @TODO: Move this to a separate class - /** - * @param mixed[]|null $env - * @param float $timeout - */ - public function evalPhpInSubProcess( + /** + * @param mixed[]|null $env + * @param float $timeout + */ + public function evalPhpInSubProcess( $code, $env = null, $input = null, $timeout = 60 ) { return $this->runShellCommand( - [ + array( 'php', '-r', '?>' . $code, - ], + ), null, array_merge( - [ + array( 'DOCROOT' => $this->getDocumentRoot(), - ], - $env ?? [] + ), + $env ?? array() ), $input, $timeout @@ -100,13 +99,13 @@ public function evalPhpInSubProcess( } // @TODO: Move this to a separate class - /** - * @param mixed[] $command - * @param string|null $cwd - * @param mixed[]|null $env - * @param float $timeout - */ - public function runShellCommand( + /** + * @param mixed[] $command + * @param string|null $cwd + * @param mixed[]|null $env + * @param float $timeout + */ + public function runShellCommand( $command, $cwd = null, $env = null, @@ -130,12 +129,12 @@ public function runShellCommand( } /** - * @param mixed[] $command - * @param string|null $cwd - * @param mixed[]|null $env - * @param float $timeout - */ - public function startProcess( + * @param mixed[] $command + * @param string|null $cwd + * @param mixed[]|null $env + * @param float $timeout + */ + public function startProcess( $command, $cwd = null, $env = null, diff --git a/src/WordPress/Blueprints/Runtime/RuntimeInterface.php b/src/WordPress/Blueprints/Runtime/RuntimeInterface.php index cab23cc5..0e3278e7 100644 --- a/src/WordPress/Blueprints/Runtime/RuntimeInterface.php +++ b/src/WordPress/Blueprints/Runtime/RuntimeInterface.php @@ -7,17 +7,16 @@ interface RuntimeInterface { /** - * @param mixed[] $command - * @param string|null $cwd - * @param mixed[]|null $env - * @param float $timeout - */ - public function startProcess( + * @param mixed[] $command + * @param string|null $cwd + * @param mixed[]|null $env + * @param float $timeout + */ + public function startProcess( $command, $cwd = null, $env = null, $input = null, $timeout = 60 ): Process; - } diff --git a/src/WordPress/Blueprints/bin/autogenerate_models.php b/src/WordPress/Blueprints/bin/autogenerate_models.php index 6be4c267..26e6bf8f 100644 --- a/src/WordPress/Blueprints/bin/autogenerate_models.php +++ b/src/WordPress/Blueprints/bin/autogenerate_models.php @@ -25,19 +25,26 @@ throw new \RuntimeException( "Target directory $targetDirectory does not exist" ); } -$jane = Jane::build( [ - 'reference' => '#', - 'validation' => true, - 'strict' => false, -] ); -$registry = new Registry(); +$jane = Jane::build( + array( + 'reference' => '#', + 'validation' => true, + 'strict' => false, + ) +); +$registry = new Registry(); $schemaLoader = new SchemaLoader(); -$registry->addSchema( $schemaLoader->resolve( $schemaPath, [ - 'namespace' => $targetNamespace, - 'directory' => $targetDirectory, - 'root-class' => 'Blueprint', - 'strict' => false, -] ) ); +$registry->addSchema( + $schemaLoader->resolve( + $schemaPath, + array( + 'namespace' => $targetNamespace, + 'directory' => $targetDirectory, + 'root-class' => 'Blueprint', + 'strict' => false, + ) + ) +); $context = $jane->createContext( $registry ); $jane->generate( $registry ); @@ -45,30 +52,30 @@ mkdir( $targetDirectory, 0777, true ); } -$schema = $context->getRegistry()->getSchemas()[0]; +$schema = $context->getRegistry()->getSchemas()[0]; $janeClasses = $schema->getClasses(); // Every group of classes with a discriminator and oneOf should have // an interface that all the classes in the group implement. -$netteInterfaces = []; -$shouldImplement = []; -$interfaceImplementors = []; +$netteInterfaces = array(); +$shouldImplement = array(); +$interfaceImplementors = array(); foreach ( $janeClasses as $ref => $class ) { // If $ref ends with /([^/]+)/oneOf/\d, extract the first group if ( 1 !== preg_match( '/\/([^\/]+)\/oneOf\/\d+$/', $ref, $matches ) ) { continue; } - $interfaceName = $matches[1] . 'Interface'; - $shouldImplement[ $class->getName() ] = $interfaceName; + $interfaceName = $matches[1] . 'Interface'; + $shouldImplement[ $class->getName() ] = $interfaceName; $interfaceImplementors[ $interfaceName ][] = $class->getName(); if ( array_key_exists( $interfaceName, $netteInterfaces ) ) { continue; } - $namespace = new PhpNamespace( $targetNamespace ); - $interface = $namespace->addInterface( $interfaceName ); + $namespace = new PhpNamespace( $targetNamespace ); + $interface = $namespace->addInterface( $interfaceName ); $netteInterfaces[ $interface->getName() ] = $interface; } @@ -78,9 +85,12 @@ */ $modelInfoClass = ( new PhpNamespace( $targetNamespace ) )->addClass( 'ModelInfo' ); foreach ( $interfaceImplementors as $interfaceName => $implementors ) { - $implementorsClassExpressions = array_map( function ($implementor) { - return $implementor . '::class'; - }, $implementors ); + $implementorsClassExpressions = array_map( + function ( $implementor ) { + return $implementor . '::class'; + }, + $implementors + ); $modelInfoClass->addMethod( 'get' . $interfaceName . 'Implementations' ) ->setStatic( true ) ->setReturnType( 'array' ) @@ -107,13 +117,16 @@ * * For that, we'll need a map of replacements. */ -$unionReplacements = []; +$unionReplacements = array(); foreach ( $interfaceImplementors as $interfaceName => $implementors ) { $unionReplacements[ implode( '|', $implementors ) ] = $interfaceName; - $arrayUnion = array_map( function ($implementor) { - return $implementor . '[]'; - }, $implementors ); + $arrayUnion = array_map( + function ( $implementor ) { + return $implementor . '[]'; + }, + $implementors + ); $unionReplacements[ implode( '|', $arrayUnion ) ] = $interfaceName . '[]'; } @@ -130,24 +143,24 @@ function fixTypeHint( string $typeHint, array $replacements ): string { * the PHP classes. However, the classes it generates are too opinionated. * Let's convert them to Nette classes and customize them to our needs. */ -$typeHintMap = []; -$netteClasses = []; +$typeHintMap = array(); +$netteClasses = array(); /** @var ClassType[] $netteClasses */ foreach ( $janeClasses as $ref => $janeClass ) { - $namespace = new PhpNamespace( $targetNamespace ); - $class = $namespace->addClass( $janeClass->getName() ); + $namespace = new PhpNamespace( $targetNamespace ); + $class = $namespace->addClass( $janeClass->getName() ); $hasInterface = isset( $shouldImplement[ $janeClass->getName() ] ); if ( $hasInterface ) { $class->addImplement( $targetNamespace . '\\' . $shouldImplement[ $janeClass->getName() ] ); } - $typeHintMap[ $janeClass->getName() ] = []; + $typeHintMap[ $janeClass->getName() ] = array(); foreach ( $janeClass->getProperties() as $janeProperty ) { // Add the property to the class. - $property = $class->addProperty( $janeProperty->getName() ); + $property = $class->addProperty( $janeProperty->getName() ); $description = $janeProperty->getDescription(); - $typeHint = $janeProperty->getType()->getTypeHint( '' ); + $typeHint = $janeProperty->getType()->getTypeHint( '' ); $docTypeHint = $janeProperty->getType()->getDocTypeHint( '' ); - if ( strpos($docTypeHint, $targetNamespace) !== false ) { + if ( strpos( $docTypeHint, $targetNamespace ) !== false ) { // Jane prepends "\$namespace\Model\" to type hints, let's remove that. $docTypeHint = str_replace( '\\' . $targetNamespace . '\\Model\\', '', $docTypeHint ); // Let's replace the lengthy union types with the interface types. @@ -160,14 +173,14 @@ function fixTypeHint( string $typeHint, array $replacements ): string { // fix that by moving the object types to the front. $subTypes = explode( '|', $docTypeHint ); if ( count( $subTypes ) > 1 ) { - $phpPrimitiveTypes = [ 'string', 'int', 'float', 'bool', 'array', 'object' ]; - $finalTypes = []; + $phpPrimitiveTypes = array( 'string', 'int', 'float', 'bool', 'array', 'object' ); + $finalTypes = array(); foreach ( $phpPrimitiveTypes as $phpPrimitiveType ) { if ( in_array( $phpPrimitiveType, $subTypes ) ) { $finalTypes[] = $phpPrimitiveType; } } - $finalTypes = array_merge( $finalTypes, array_diff( $subTypes, $phpPrimitiveTypes ) ); + $finalTypes = array_merge( $finalTypes, array_diff( $subTypes, $phpPrimitiveTypes ) ); $docTypeHint = implode( '|', $finalTypes ); } } @@ -183,13 +196,15 @@ function fixTypeHint( string $typeHint, array $replacements ): string { // Add a setter $setter = $class->addMethod( 'set' . ucfirst( $janeProperty->getName() ) ); - $setterArg = $setter->addParameter( $janeProperty->getName() ); + $setterArg = $setter->addParameter( $janeProperty->getName() ); $argTypeHint = $janeProperty->getType()->getTypeHint( $targetNamespace ) . ''; if ( $argTypeHint ) { - if ( strpos($argTypeHint, '\\') !== false ) { - $argTypeHint = $targetNamespace . '\\' . str_replace( '\\' . $targetNamespace . '\\Model\\', - '', - $argTypeHint ); + if ( strpos( $argTypeHint, '\\' ) !== false ) { + $argTypeHint = $targetNamespace . '\\' . str_replace( + '\\' . $targetNamespace . '\\Model\\', + '', + $argTypeHint + ); } $setterArg->setType( $argTypeHint ); } @@ -218,7 +233,7 @@ function fixTypeHint( string $typeHint, array $replacements ): string { // value of all class properties. $property->setValue( $schema->getDefault() ); } elseif ( $schema->getType() === 'array' ) { - $property->setValue( [] ); + $property->setValue( array() ); } } } @@ -228,7 +243,7 @@ function fixTypeHint( string $typeHint, array $replacements ): string { /** * We're finished, yay! Let's write the generated classes to the disk. */ -$netteOutput = array_merge( $netteInterfaces, $netteClasses, [ $modelInfoClass ] ); +$netteOutput = array_merge( $netteInterfaces, $netteClasses, array( $modelInfoClass ) ); foreach ( glob( $targetDirectory . '/*.php' ) as $file ) { unlink( $file ); } @@ -238,4 +253,4 @@ function fixTypeHint( string $typeHint, array $replacements ): string { file_put_contents( $filename, "getNamespace() ); } -echo "Generated " . count( $netteOutput ) . " classes in $targetDirectory\n"; +echo 'Generated ' . count( $netteOutput ) . " classes in $targetDirectory\n"; diff --git a/src/WordPress/Blueprints/functions.php b/src/WordPress/Blueprints/functions.php index bdc97850..83d233bc 100644 --- a/src/WordPress/Blueprints/functions.php +++ b/src/WordPress/Blueprints/functions.php @@ -7,19 +7,19 @@ use Symfony\Component\Filesystem\Exception\IOException; use WordPress\Blueprints\Runtime\Runtime; -function run_blueprint( $json, $options = [] ) { +function run_blueprint( $json, $options = array() ) { - $environment = $options['environment'] ?? ContainerBuilder::ENVIRONMENT_NATIVE; - $documentRoot = $options['documentRoot'] ?? '/wordpress'; + $environment = $options['environment'] ?? ContainerBuilder::ENVIRONMENT_NATIVE; + $documentRoot = $options['documentRoot'] ?? '/wordpress'; $progressSubscriber = $options['progressSubscriber'] ?? null; - $progressType = $options['progressType'] ?? 'all'; + $progressType = $options['progressType'] ?? 'all'; $c = ( new ContainerBuilder() )->build( $environment, new Runtime( $documentRoot ) ); - $engine = $c['blueprint.engine']; + $engine = $c['blueprint.engine']; $compiledBlueprint = $engine->parseAndCompile( $json ); /** @var $engine Engine */ @@ -35,24 +35,29 @@ function run_blueprint( $json, $options = [] ) { } function list_files( string $path, $omitDotFiles = false ): array { - return array_values( array_filter( scandir( $path ), function ( $file ) use ( $omitDotFiles ) { - if ( $omitDotFiles && $file[0] === '.' ) { - return false; - } + return array_values( + array_filter( + scandir( $path ), + function ( $file ) use ( $omitDotFiles ) { + if ( $omitDotFiles && $file[0] === '.' ) { + return false; + } - return '.' !== $file && '..' !== $file; - } ) ); + return '.' !== $file && '..' !== $file; + } + ) + ); } function move_files_from_directory_to_directory( string $from, string $to ) { - $fs = new Filesystem(); + $fs = new Filesystem(); $files = scandir( $from ); foreach ( $files as $file ) { if ( '.' === $file || '..' === $file ) { continue; } $fromPath = Path::canonicalize( $from . '/' . $file ); - $toPath = Path::canonicalize( $to . '/' . $file ); + $toPath = Path::canonicalize( $to . '/' . $file ); try { $fs->rename( $fromPath, $toPath ); } catch ( IOException $exception ) { diff --git a/src/WordPress/DataSource/BaseDataSource.php b/src/WordPress/DataSource/BaseDataSource.php index 8407aaf3..aea9bf42 100644 --- a/src/WordPress/DataSource/BaseDataSource.php +++ b/src/WordPress/DataSource/BaseDataSource.php @@ -13,5 +13,4 @@ public function __construct() { } abstract public function stream( $resourceIdentifier ); - } diff --git a/src/WordPress/DataSource/DataSourceProgressEvent.php b/src/WordPress/DataSource/DataSourceProgressEvent.php index b444d112..b0704740 100644 --- a/src/WordPress/DataSource/DataSourceProgressEvent.php +++ b/src/WordPress/DataSource/DataSourceProgressEvent.php @@ -6,24 +6,23 @@ class DataSourceProgressEvent extends Event { /** - * @var string - */ - public $url; - /** - * @var int - */ - public $downloadedBytes; - /** - * @var int|null - */ - public $totalBytes; - /** - * @param int|null $totalBytes - */ - public function __construct(string $url, int $downloadedBytes, $totalBytes) - { - $this->url = $url; - $this->downloadedBytes = $downloadedBytes; - $this->totalBytes = $totalBytes; - } + * @var string + */ + public $url; + /** + * @var int + */ + public $downloadedBytes; + /** + * @var int|null + */ + public $totalBytes; + /** + * @param int|null $totalBytes + */ + public function __construct( string $url, int $downloadedBytes, $totalBytes ) { + $this->url = $url; + $this->downloadedBytes = $downloadedBytes; + $this->totalBytes = $totalBytes; + } } diff --git a/src/WordPress/DataSource/PlaygroundFetchSource.php b/src/WordPress/DataSource/PlaygroundFetchSource.php index f18089bf..ba2b458e 100644 --- a/src/WordPress/DataSource/PlaygroundFetchSource.php +++ b/src/WordPress/DataSource/PlaygroundFetchSource.php @@ -7,13 +7,16 @@ class PlaygroundFetchSource extends BaseDataSource { - public $proc_handles = []; + public $proc_handles = array(); public function stream( $resourceIdentifier ) { - $url = $resourceIdentifier; + $url = $resourceIdentifier; $proc_handle = proc_open( - is_array([ 'fetch', $url, ]) ? implode(' ', array_map('escapeshellarg', [ 'fetch', $url, ])) : [ 'fetch', $url, ], - [ 1 => [ 'pipe', 'w' ], 2 => [ 'pipe', 'w' ] ], + is_array( array( 'fetch', $url ) ) ? implode( ' ', array_map( 'escapeshellarg', array( 'fetch', $url ) ) ) : array( 'fetch', $url ), + array( + 1 => array( 'pipe', 'w' ), + 2 => array( 'pipe', 'w' ), + ), $pipes ); // This prevents the process handle from getting garbage collected and @@ -23,13 +26,11 @@ public function stream( $resourceIdentifier ) { // Without this line, we get the following error: // PHP Fatal error: Uncaught TypeError: stream_copy_to_stream(): supplied resource is not a valid stream resource i // var_dump()–ing first says - // resource(457) of type (stream) + // resource(457) of type (stream) // but then it says - // resource(457) of type (Unknown) + // resource(457) of type (Unknown) $this->proc_handles[] = $proc_handle; return $pipes[1]; } - } - diff --git a/src/WordPress/DataSource/UrlSource.php b/src/WordPress/DataSource/UrlSource.php index d02fe310..9b9ebf4a 100644 --- a/src/WordPress/DataSource/UrlSource.php +++ b/src/WordPress/DataSource/UrlSource.php @@ -18,16 +18,20 @@ public function __construct( CacheInterface $cache ) { $this->client = $client; - $this->cache = $cache; + $this->cache = $cache; parent::__construct(); - $client->set_progress_callback( function ( Request $request, $downloaded, $total ) { - $this->events->dispatch( new DataSourceProgressEvent( - $request->url, - $downloaded, - $total - ) ); - } ); + $client->set_progress_callback( + function ( Request $request, $downloaded, $total ) { + $this->events->dispatch( + new DataSourceProgressEvent( + $request->url, + $downloaded, + $total + ) + ); + } + ); } public function stream( $resourceIdentifier ) { @@ -36,13 +40,15 @@ public function stream( $resourceIdentifier ) { if ( false && $this->cache->has( $url ) ) { // Return a stream resource. // @TODO: Stream directly from the cache - $cached = $this->cache->get( $url ); + $cached = $this->cache->get( $url ); $data_size = strlen( $cached ); - $this->events->dispatch( new DataSourceProgressEvent( - $url, - $data_size, - $data_size - ) ); + $this->events->dispatch( + new DataSourceProgressEvent( + $url, + $data_size, + $data_size + ) + ); $stream = fopen( 'php://memory', 'r+' ); fwrite( $stream, $cached ); rewind( $stream ); @@ -57,11 +63,11 @@ public function stream( $resourceIdentifier ) { // Cache $onChunk = function ( $chunk ) use ( $url, $stream ) { // Handle response caching - static $bufferedChunks = []; - $bufferedChunks[] = $chunk; + static $bufferedChunks = array(); + $bufferedChunks[] = $chunk; if ( feof( $stream ) ) { $this->cache->set( $url, implode( '', $bufferedChunks ) ); - $bufferedChunks = []; + $bufferedChunks = array(); } }; @@ -72,5 +78,4 @@ public function stream( $resourceIdentifier ) { ) ); } - } diff --git a/src/WordPress/JsonMapper/JsonMapper.php b/src/WordPress/JsonMapper/JsonMapper.php index 86b54a9e..b8f5d8dd 100644 --- a/src/WordPress/JsonMapper/JsonMapper.php +++ b/src/WordPress/JsonMapper/JsonMapper.php @@ -38,7 +38,7 @@ public function __construct( array $custom_factories = array() ) { * Creates an instance of $class_name based on parsed JSON data. * * @param stdClass $json The JSON object containing the data to populate the new object with. - * @param string $class_name The fully qualified name of the class to create an instance of. + * @param string $class_name The fully qualified name of the class to create an instance of. * * @return object An instance of the class specified by $class_name, populated with data from $json. * @throws ReflectionException If the class does not exist and the instance is being created manually. @@ -55,7 +55,7 @@ public function hydrate( $json, $class_name ) { * Populates an instance of $class_name created via ReflectionClass::newInstance. * * @param stdClass $json The JSON object containing the data to populate the new object with. - * @param string $class_name The fully qualified name of the class to create an instance of. + * @param string $class_name The fully qualified name of the class to create an instance of. * * @return object An instance of the class specified by $class_name, populated with data from $json. * @throws ReflectionException If the class does not exist. @@ -64,8 +64,8 @@ public function hydrate( $json, $class_name ) { */ private function create_and_hydrate( string $class_name, stdClass $json ) { $reflection_class = new ReflectionClass( $class_name ); - $object = $reflection_class->newInstance(); - $property_map = PropertyParser::compute_property_map( $reflection_class ); + $object = $reflection_class->newInstance(); + $property_map = PropertyParser::compute_property_map( $reflection_class ); foreach ( (array) $json as $key => $value ) { // Ignore null data in JSON. @@ -91,7 +91,7 @@ private function create_and_hydrate( string $class_name, stdClass $json ) { * Warning - array depths are not fully checked during mapping. * * @param Property $property The Property object with a list of possible types the value could be mapped to. - * @param mixed $value The value from the JSON object. + * @param mixed $value The value from the JSON object. * * @return mixed The mapped value, of the type specified by the Property. * @throws JsonMapperException If the Property type is not supported, or if the value cannot be mapped to the Property type. @@ -104,8 +104,8 @@ private function map_value( Property $property, $value ) { foreach ( $property->property_types as $property_type ) { $array_dimensions = PropertyParser::get_array_dimensions( $property_type ); - $property_type = PropertyParser::without_dimensions( $property_type ); - $type_is_array = 'array' === $property_type || $array_dimensions > 0; + $property_type = PropertyParser::without_dimensions( $property_type ); + $type_is_array = 'array' === $property_type || $array_dimensions > 0; if ( is_array( $value ) && $type_is_array && count( $value ) === 0 ) { return array(); @@ -146,7 +146,7 @@ private function set_value( $object, Property $property, $value ) { // Use a setter if it exists. $method_name = 'set' . ucfirst( $property->name ); if ( method_exists( $object, $method_name ) ) { - $method = new ReflectionMethod( $object, $method_name ); + $method = new ReflectionMethod( $object, $method_name ); $parameters = $method->getParameters(); if ( is_array( $value ) && count( $parameters ) === 1 && $parameters[0]->isVariadic() ) { @@ -171,7 +171,6 @@ private function set_value( $object, Property $property, $value ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped "Property: '" . get_class( $object ) . "::$property->name' is non-public and no setter method was found." ); - } private function is_scalar_recursive( $value, string $property_type ) { @@ -311,7 +310,7 @@ private function has_factory( string $class_name ): bool { } private function run_factory( string $class_name, $params ) { - return $this->factories[$this->sanitize_class_name( $class_name )]( $params ); + return $this->factories[ $this->sanitize_class_name( $class_name ) ]( $params ); } private function add_factory( string $class_name, callable $factory ) { diff --git a/src/WordPress/JsonMapper/JsonMapperException.php b/src/WordPress/JsonMapper/JsonMapperException.php index f80b1350..25e87038 100644 --- a/src/WordPress/JsonMapper/JsonMapperException.php +++ b/src/WordPress/JsonMapper/JsonMapperException.php @@ -4,4 +4,4 @@ use Exception; -class JsonMapperException extends Exception {} \ No newline at end of file +class JsonMapperException extends Exception {} diff --git a/src/WordPress/JsonMapper/PropertyParser.php b/src/WordPress/JsonMapper/PropertyParser.php index e200c9e0..d6b9c9aa 100644 --- a/src/WordPress/JsonMapper/PropertyParser.php +++ b/src/WordPress/JsonMapper/PropertyParser.php @@ -24,9 +24,9 @@ public static function without_dimensions( $type ) { public static function get_array_dimensions( $type ) { $dimension = 0; - $at = strlen( $type ); + $at = strlen( $type ); while ( substr( $type, $at - 2, 2 ) === '[]' ) { - $dimension ++; + ++$dimension; $at -= 2; } @@ -108,7 +108,7 @@ private static function parse_property_types( ReflectionProperty $reflection_pro $var = null; if ( preg_match_all( self::DOC_BLOCK_REGEX, $doc_block, $matches ) ) { - for ( $x = 0, $max = count( $matches[0] ); $x < $max; $x ++ ) { + for ( $x = 0, $max = count( $matches[0] ); $x < $max; $x++ ) { if ( 'var' === $matches['name'][ $x ] ) { $var = $matches['value'][ $x ]; } @@ -126,11 +126,11 @@ private static function parse_property_types( ReflectionProperty $reflection_pro continue; } $array_brackets = ''; - $type = $original_property_type; + $type = $original_property_type; preg_match( '/(\[\])+$/', $type, $matches ); if ( ! empty( $matches ) ) { $array_brackets = $matches[0]; - $type = substr( $type, 0, - strlen( $array_brackets ) ); + $type = substr( $type, 0, - strlen( $array_brackets ) ); } if ( Utils::is_type_scalar( $type ) || $type === 'array' || $type === 'object' || $type === 'mixed' ) { @@ -143,7 +143,7 @@ private static function parse_property_types( ReflectionProperty $reflection_pro $property_types[] = '\\' . $type . $array_brackets; continue; } - + if ( class_exists( $type ) ) { $property_types[] = $type . $array_brackets; continue; @@ -176,11 +176,11 @@ class_exists( $namespaced_type ) * @return ReflectionProperty[] array of properties. */ private static function get_properties( ReflectionClass $reflection_class ) { - $properties = $reflection_class->getProperties(); + $properties = $reflection_class->getProperties(); $reflected_parent = $reflection_class->getParentClass(); while ( false !== $reflected_parent ) { - $properties = array_merge( $properties, $reflected_parent->getProperties() ); + $properties = array_merge( $properties, $reflected_parent->getProperties() ); $reflected_parent = $reflected_parent->getParentClass(); } diff --git a/src/WordPress/JsonMapper/Utils.php b/src/WordPress/JsonMapper/Utils.php index 7086bcdd..01079c97 100644 --- a/src/WordPress/JsonMapper/Utils.php +++ b/src/WordPress/JsonMapper/Utils.php @@ -4,13 +4,12 @@ class Utils { - static public $scalar_types = array( 'string', 'bool', 'boolean', 'int', 'integer', 'double', 'float' ); + public static $scalar_types = array( 'string', 'bool', 'boolean', 'int', 'integer', 'double', 'float' ); /** - * @param string $type - */ - static public function is_type_scalar( $type ) { + * @param string $type + */ + public static function is_type_scalar( $type ) { return in_array( $type, self::$scalar_types, true ); } - } diff --git a/src/WordPress/Streams/StreamPeekerData.php b/src/WordPress/Streams/StreamPeekerData.php index a1b355a4..08a51f18 100644 --- a/src/WordPress/Streams/StreamPeekerData.php +++ b/src/WordPress/Streams/StreamPeekerData.php @@ -5,12 +5,12 @@ class StreamPeekerData extends VanillaStreamWrapperData { public $fp; - public $onChunk = null; - public $onClose = null; - public function __construct( $fp, $onChunk = null, $onClose = null ) { - $this->fp = $fp; - $this->onChunk = $onChunk; - $this->onClose = $onClose; - parent::__construct( $fp ); + public $onChunk = null; + public $onClose = null; + public function __construct( $fp, $onChunk = null, $onClose = null ) { + $this->fp = $fp; + $this->onChunk = $onChunk; + $this->onClose = $onClose; + parent::__construct( $fp ); } } diff --git a/src/WordPress/Streams/StreamPeekerWrapper.php b/src/WordPress/Streams/StreamPeekerWrapper.php index 6bfb6f4b..2b83aca2 100644 --- a/src/WordPress/Streams/StreamPeekerWrapper.php +++ b/src/WordPress/Streams/StreamPeekerWrapper.php @@ -42,7 +42,7 @@ public function stream_open( $path, $mode, $options, &$opened_path ) { // Reads from the stream public function stream_read( $count ) { - $ret = fread( $this->stream, $count ); + $ret = fread( $this->stream, $count ); $this->position += strlen( $ret ); $onChunk = $this->onChunk; @@ -53,7 +53,7 @@ public function stream_read( $count ) { // Writes to the stream public function stream_write( $data ) { - $written = fwrite( $this->stream, $data ); + $written = fwrite( $this->stream, $data ); $this->position += $written; return $written; @@ -70,5 +70,4 @@ public function stream_close() { public function stream_tell() { return $this->position; } - } diff --git a/src/WordPress/Streams/StreamWrapperInterface.php b/src/WordPress/Streams/StreamWrapperInterface.php index 9bc9d752..e5ceeec5 100644 --- a/src/WordPress/Streams/StreamWrapperInterface.php +++ b/src/WordPress/Streams/StreamWrapperInterface.php @@ -7,8 +7,8 @@ interface StreamWrapperInterface { /** * Sets an option on the stream * - * @param int $option - * @param int $arg1 + * @param int $option + * @param int $arg1 * @param int|null $arg2 * * @return bool @@ -21,9 +21,9 @@ public function stream_set_option( $option, $arg1, $arg2 = null ): bool; public function stream_open( $path, $mode, $options, &$opened_path ); /** - * @param int $cast_as - */ - public function stream_cast( $cast_as ); + * @param int $cast_as + */ + public function stream_cast( $cast_as ); /** * Reads from the stream diff --git a/src/WordPress/Streams/VanillaStreamWrapper.php b/src/WordPress/Streams/VanillaStreamWrapper.php index 810af238..0cc636df 100644 --- a/src/WordPress/Streams/VanillaStreamWrapper.php +++ b/src/WordPress/Streams/VanillaStreamWrapper.php @@ -12,21 +12,23 @@ class VanillaStreamWrapper implements StreamWrapperInterface { const SCHEME = 'vanilla'; /** - * @param \WordPress\Streams\VanillaStreamWrapperData $data - */ - static public function create_resource( $data ) { + * @param \WordPress\Streams\VanillaStreamWrapperData $data + */ + public static function create_resource( $data ) { static::register(); - $context = stream_context_create( [ - static::SCHEME => [ - 'wrapper_data' => $data, - ], - ] ); + $context = stream_context_create( + array( + static::SCHEME => array( + 'wrapper_data' => $data, + ), + ) + ); return fopen( static::SCHEME . '://', 'r', false, $context ); } - static public function register() { + public static function register() { if ( in_array( static::SCHEME, stream_get_wrappers() ) ) { return; } @@ -36,17 +38,17 @@ static public function register() { } } - static public function unregister() { + public static function unregister() { stream_wrapper_unregister( 'async' ); } /** - * @param int $option - * @param int $arg1 - * @param int|null $arg2 - */ - public function stream_set_option( $option, $arg1, $arg2 = null ): bool { + * @param int $option + * @param int $arg1 + * @param int|null $arg2 + */ + public function stream_set_option( $option, $arg1, $arg2 = null ): bool { if ( \STREAM_OPTION_BLOCKING === $option ) { return stream_set_blocking( $this->stream, (bool) $arg1 ); } elseif ( \STREAM_OPTION_READ_TIMEOUT === $option ) { @@ -73,9 +75,9 @@ public function stream_open( $path, $mode, $options, &$opened_path ) { } /** - * @param int $cast_as - */ - public function stream_cast( $cast_as ) { + * @param int $cast_as + */ + public function stream_cast( $cast_as ) { return $this->stream; } @@ -104,6 +106,6 @@ public function stream_seek( $offset, $whence ) { } public function stream_stat() { - return []; + return array(); } } diff --git a/src/WordPress/Util/ArrayPairIterator.php b/src/WordPress/Util/ArrayPairIterator.php index acef8157..8d8a64a9 100644 --- a/src/WordPress/Util/ArrayPairIterator.php +++ b/src/WordPress/Util/ArrayPairIterator.php @@ -22,7 +22,7 @@ public function key() { #[\ReturnTypeWillChange] public function next() { - ++ $this->position; + ++$this->position; } #[\ReturnTypeWillChange] diff --git a/src/WordPress/Util/Map.php b/src/WordPress/Util/Map.php index 65ff8558..0253a19e 100644 --- a/src/WordPress/Util/Map.php +++ b/src/WordPress/Util/Map.php @@ -7,7 +7,7 @@ use Traversable; class Map implements ArrayAccess, IteratorAggregate { - private $pairs = []; + private $pairs = array(); public function __construct() { } @@ -31,18 +31,18 @@ public function offsetGet( $offset ) { } // TODO Evaluate waring: 'ext-json' is missing in composer.json - throw new \Exception( "Stream for resource " . json_encode( $offset ) . " not found" ); + throw new \Exception( 'Stream for resource ' . json_encode( $offset ) . ' not found' ); } public function offsetSet( $offset, $value ) { foreach ( $this->pairs as $k => $pair ) { if ( $pair[0] === $offset ) { - $this->pairs[ $k ] = [ $offset, $value ]; + $this->pairs[ $k ] = array( $offset, $value ); return; } } - $this->pairs[] = [ $offset, $value ]; + $this->pairs[] = array( $offset, $value ); } public function offsetUnset( $offset ) { diff --git a/src/WordPress/Zip/ZipCentralDirectoryEntry.php b/src/WordPress/Zip/ZipCentralDirectoryEntry.php index 807d9260..3c2f2503 100644 --- a/src/WordPress/Zip/ZipCentralDirectoryEntry.php +++ b/src/WordPress/Zip/ZipCentralDirectoryEntry.php @@ -42,24 +42,24 @@ public function __construct( string $extra, string $fileComment ) { - $this->fileComment = $fileComment; - $this->extra = $extra; - $this->path = $path; - $this->lastByteAt = $lastByteAt; + $this->fileComment = $fileComment; + $this->extra = $extra; + $this->path = $path; + $this->lastByteAt = $lastByteAt; $this->externalAttributes = $externalAttributes; $this->internalAttributes = $internalAttributes; - $this->diskNumber = $diskNumber; - $this->uncompressedSize = $uncompressedSize; - $this->compressedSize = $compressedSize; - $this->crc = $crc; - $this->lastModifiedDate = $lastModifiedDate; - $this->lastModifiedTime = $lastModifiedTime; - $this->compressionMethod = $compressionMethod; - $this->generalPurpose = $generalPurpose; - $this->versionNeeded = $versionNeeded; - $this->versionCreated = $versionCreated; - $this->firstByteAt = $firstByteAt; - $this->isDirectory = $this->path[ - 1 ] === '/'; + $this->diskNumber = $diskNumber; + $this->uncompressedSize = $uncompressedSize; + $this->compressedSize = $compressedSize; + $this->crc = $crc; + $this->lastModifiedDate = $lastModifiedDate; + $this->lastModifiedTime = $lastModifiedTime; + $this->compressionMethod = $compressionMethod; + $this->generalPurpose = $generalPurpose; + $this->versionNeeded = $versionNeeded; + $this->versionCreated = $versionCreated; + $this->firstByteAt = $firstByteAt; + $this->isDirectory = $this->path[- 1] === '/'; } public function isFileEntry() { diff --git a/src/WordPress/Zip/ZipEndCentralDirectoryEntry.php b/src/WordPress/Zip/ZipEndCentralDirectoryEntry.php index d71a2365..632f41db 100644 --- a/src/WordPress/Zip/ZipEndCentralDirectoryEntry.php +++ b/src/WordPress/Zip/ZipEndCentralDirectoryEntry.php @@ -5,33 +5,33 @@ class ZipEndCentralDirectoryEntry { /** - * @var int - */ - public $diskNumber; + * @var int + */ + public $diskNumber; /** - * @var int - */ - public $centralDirectoryStartDisk; + * @var int + */ + public $centralDirectoryStartDisk; /** - * @var int - */ - public $numberCentralDirectoryRecordsOnThisDisk; + * @var int + */ + public $numberCentralDirectoryRecordsOnThisDisk; /** - * @var int - */ - public $numberCentralDirectoryRecords; + * @var int + */ + public $numberCentralDirectoryRecords; /** - * @var int - */ - public $centralDirectorySize; + * @var int + */ + public $centralDirectorySize; /** - * @var int - */ - public $centralDirectoryOffset; + * @var int + */ + public $centralDirectoryOffset; /** - * @var string - */ - public $comment; + * @var string + */ + public $comment; public function __construct( int $diskNumber, diff --git a/src/WordPress/Zip/ZipFileEntry.php b/src/WordPress/Zip/ZipFileEntry.php index 088a525d..6536b968 100644 --- a/src/WordPress/Zip/ZipFileEntry.php +++ b/src/WordPress/Zip/ZipFileEntry.php @@ -4,53 +4,53 @@ class ZipFileEntry { /** - * @var bool - */ - public $isDirectory; + * @var bool + */ + public $isDirectory; /** - * @var int - */ - public $version; + * @var int + */ + public $version; /** - * @var int - */ - public $generalPurpose; + * @var int + */ + public $generalPurpose; /** - * @var int - */ - public $compressionMethod; + * @var int + */ + public $compressionMethod; /** - * @var int - */ - public $lastModifiedTime; + * @var int + */ + public $lastModifiedTime; /** - * @var int - */ - public $lastModifiedDate; + * @var int + */ + public $lastModifiedDate; /** - * @var int - */ - public $crc; + * @var int + */ + public $crc; /** - * @var int - */ - public $compressedSize; + * @var int + */ + public $compressedSize; /** - * @var int - */ - public $uncompressedSize; + * @var int + */ + public $uncompressedSize; /** - * @var string - */ - public $path; + * @var string + */ + public $path; /** - * @var string - */ - public $extra; + * @var string + */ + public $extra; /** - * @var string - */ - public $bytes; + * @var string + */ + public $bytes; public function __construct( int $version, diff --git a/src/WordPress/Zip/ZipStreamReader.php b/src/WordPress/Zip/ZipStreamReader.php index af3cca38..daf087b5 100644 --- a/src/WordPress/Zip/ZipStreamReader.php +++ b/src/WordPress/Zip/ZipStreamReader.php @@ -4,17 +4,17 @@ class ZipStreamReader { - const SIGNATURE_FILE = 0x04034b50; - const SIGNATURE_CENTRAL_DIRECTORY = 0x02014b50; + const SIGNATURE_FILE = 0x04034b50; + const SIGNATURE_CENTRAL_DIRECTORY = 0x02014b50; const SIGNATURE_CENTRAL_DIRECTORY_END = 0x06054b50; - const COMPRESSION_DEFLATE = 8; + const COMPRESSION_DEFLATE = 8; /** * Reads the next zip entry from a stream of zip file bytes. * * @param resource $fp A stream of zip file bytes. */ - static public function readEntry( $fp ) { + public static function readEntry( $fp ) { $signature = static::read_bytes( $fp, 4 ); if ( $signature === false ) { return null; @@ -56,11 +56,13 @@ static public function readEntry( $fp ) { * * @param resource $stream */ - static protected function readFileEntry( $stream ): ZipFileEntry { - $data = self::read_bytes( $stream, 26 ); - $data = unpack( 'vversionNeeded/vgeneralPurpose/vcompressionMethod/vlastModifiedTime/vlastModifiedDate/Vcrc/VcompressedSize/VuncompressedSize/vpathLength/vextraLength', - $data ); - $path = self::read_bytes( $stream, $data['pathLength'] ); + protected static function readFileEntry( $stream ): ZipFileEntry { + $data = self::read_bytes( $stream, 26 ); + $data = unpack( + 'vversionNeeded/vgeneralPurpose/vcompressionMethod/vlastModifiedTime/vlastModifiedDate/Vcrc/VcompressedSize/VuncompressedSize/vpathLength/vextraLength', + $data + ); + $path = self::read_bytes( $stream, $data['pathLength'] ); $extra = self::read_bytes( $stream, $data['extraLength'] ); $bytes = self::read_bytes( $stream, $data['compressedSize'] ); @@ -118,12 +120,14 @@ static protected function readFileEntry( $stream ): ZipFileEntry { * * @param resource stream */ - static protected function readCentralDirectoryEntry( $stream ): ZipCentralDirectoryEntry { - $data = static::read_bytes( $stream, 42 ); - $data = unpack( 'vversionCreated/vversionNeeded/vgeneralPurpose/vcompressionMethod/vlastModifiedTime/vlastModifiedDate/Vcrc/VcompressedSize/VuncompressedSize/vpathLength/vextraLength/vfileCommentLength/vdiskNumber/vinternalAttributes/VexternalAttributes/VfirstByteAt', - $data ); - $path = static::read_bytes( $stream, $data['pathLength'] ); - $extra = static::read_bytes( $stream, $data['extraLength'] ); + protected static function readCentralDirectoryEntry( $stream ): ZipCentralDirectoryEntry { + $data = static::read_bytes( $stream, 42 ); + $data = unpack( + 'vversionCreated/vversionNeeded/vgeneralPurpose/vcompressionMethod/vlastModifiedTime/vlastModifiedDate/Vcrc/VcompressedSize/VuncompressedSize/vpathLength/vextraLength/vfileCommentLength/vdiskNumber/vinternalAttributes/VexternalAttributes/VfirstByteAt', + $data + ); + $path = static::read_bytes( $stream, $data['pathLength'] ); + $extra = static::read_bytes( $stream, $data['extraLength'] ); $fileComment = static::read_bytes( $stream, $data['fileCommentLength'] ); return new ZipCentralDirectoryEntry( @@ -167,10 +171,12 @@ static protected function readCentralDirectoryEntry( $stream ): ZipCentralDirect * * @param resource $stream */ - static protected function readEndCentralDirectoryEntry( $stream ): ZipEndCentralDirectoryEntry { + protected static function readEndCentralDirectoryEntry( $stream ): ZipEndCentralDirectoryEntry { $data = static::read_bytes( $stream, 18 ); - $data = unpack( 'vdiskNumber/vcentralDirectoryStartDisk/vnumberCentralDirectoryRecordsOnThisDisk/vnumberCentralDirectoryRecords/VcentralDirectorySize/VcentralDirectoryOffset/vcommentLength', - $data ); + $data = unpack( + 'vdiskNumber/vcentralDirectoryStartDisk/vnumberCentralDirectoryRecordsOnThisDisk/vnumberCentralDirectoryRecords/VcentralDirectorySize/VcentralDirectoryOffset/vcommentLength', + $data + ); return new ZipEndCentralDirectoryEntry( $data['diskNumber'], @@ -192,12 +198,12 @@ static protected function readEndCentralDirectoryEntry( $stream ): ZipEndCentral * * @return false|string */ - static protected function read_bytes( $stream, $length ) { + protected static function read_bytes( $stream, $length ) { if ( $length === 0 ) { return ''; } - $data = ''; + $data = ''; $remaining_length = $length; while ( $remaining_length > 0 ) { $chunk = fread( $stream, $remaining_length ); @@ -205,10 +211,9 @@ static protected function read_bytes( $stream, $length ) { return strlen( $data ) ? $data : false; } $remaining_length -= strlen( $chunk ); - $data .= $chunk; + $data .= $chunk; } return $data; } - } diff --git a/src/WordPress/Zip/functions.php b/src/WordPress/Zip/functions.php index eaa84fcc..4f02a0f2 100644 --- a/src/WordPress/Zip/functions.php +++ b/src/WordPress/Zip/functions.php @@ -14,7 +14,7 @@ function zip_extract_to( $fp, $to_path ) { continue; } - $path = Path::canonicalize( $to_path . '/' . $entry->path ); + $path = Path::canonicalize( $to_path . '/' . $entry->path ); $parent = Path::getDirectory( $path ); if ( ! is_dir( $parent ) ) { mkdir( $parent, 0777, true ); diff --git a/src/opis/json-schema/autoload.php b/src/opis/json-schema/autoload.php index 649fab8c..c408fef7 100644 --- a/src/opis/json-schema/autoload.php +++ b/src/opis/json-schema/autoload.php @@ -1,23 +1,25 @@ false, - 'allowFormats' => true, - 'allowMappers' => false, - 'allowTemplates' => false, - 'allowGlobals' => false, - 'allowDefaults' => false, - 'allowSlots' => false, - 'allowKeywordValidators' => false, - 'allowPragmas' => false, - 'allowDataKeyword' => false, - 'allowKeywordsAlongsideRef' => false, - 'allowUnevaluated' => true, - 'allowRelativeJsonPointerInRef' => false, - 'allowExclusiveMinMaxAsBool' => false, - 'keepDependenciesKeyword' => false, - 'keepAdditionalItemsKeyword' => false, - ]; +class CompliantValidator extends Validator { - public function __construct($loader = null, int $max_errors = 1) - { - parent::__construct($loader, $max_errors); + const COMPLIANT_OPTIONS = array( + 'allowFilters' => false, + 'allowFormats' => true, + 'allowMappers' => false, + 'allowTemplates' => false, + 'allowGlobals' => false, + 'allowDefaults' => false, + 'allowSlots' => false, + 'allowKeywordValidators' => false, + 'allowPragmas' => false, + 'allowDataKeyword' => false, + 'allowKeywordsAlongsideRef' => false, + 'allowUnevaluated' => true, + 'allowRelativeJsonPointerInRef' => false, + 'allowExclusiveMinMaxAsBool' => false, + 'keepDependenciesKeyword' => false, + 'keepAdditionalItemsKeyword' => false, + ); - // Set parser options - $parser = $this->parser(); - foreach (static::COMPLIANT_OPTIONS as $name => $value) { - $parser->setOption($name, $value); - } - } + public function __construct( $loader = null, int $max_errors = 1 ) { + parent::__construct( $loader, $max_errors ); + + // Set parser options + $parser = $this->parser(); + foreach ( static::COMPLIANT_OPTIONS as $name => $value ) { + $parser->setOption( $name, $value ); + } + } } diff --git a/src/opis/json-schema/src/ContentEncoding.php b/src/opis/json-schema/src/ContentEncoding.php index 9c8255cf..83dfc026 100644 --- a/src/opis/json-schema/src/ContentEncoding.php +++ b/src/opis/json-schema/src/ContentEncoding.php @@ -1,5 +1,6 @@ args = $args; - } + /** + * @var mixed[] + */ + protected $args; - public function getArgs(): array { - return $this->args; - } + public function __construct( string $message, array $args = array() ) { + parent::__construct( $message ); + $this->args = $args; + } + + public function getArgs(): array { + return $this->args; + } } diff --git a/src/opis/json-schema/src/Errors/ErrorContainer.php b/src/opis/json-schema/src/Errors/ErrorContainer.php index 48e4b4d9..1013c322 100644 --- a/src/opis/json-schema/src/Errors/ErrorContainer.php +++ b/src/opis/json-schema/src/Errors/ErrorContainer.php @@ -1,5 +1,6 @@ getErrors($error) as $error => $message) { - $key = $key_formatter($error); - - if ($multiple) { - if (!isset($list[$key])) { - $list[$key] = []; - } - $list[$key][] = $formatter($error, $message); - } else { - if (!isset($list[$key])) { - $list[$key] = $formatter($error, $message); - } - } - } - - return $list; - } - - /** - * @param ValidationError|null $error - * @param string $mode One of: flag, basic, detailed or verbose - * @return array - */ - public function formatOutput($error, $mode = "flag"): array - { - if ($error === null) { - return ['valid' => true]; - } - - if ($mode === 'flag') { - return ['valid' => false]; - } - - if ($mode === 'basic') { - return [ - 'valid' => false, - 'errors' => $this->formatFlat($error, [$this, 'formatOutputError']), - ]; - } - - if ($mode === 'detailed' || $mode === 'verbose') { - $isVerbose = $mode === 'verbose'; - - return $this->getNestedErrors($error, function (ValidationError $error, $subErrors = null) use ($isVerbose) { - $info = $this->formatOutputError($error); - - $info['valid'] = false; - - if ($isVerbose) { - $id = $error->schema()->info(); - $id = $id->root() ?? $id->id(); - if ($id) { - $id = rtrim($id, '#'); - } - $info['absoluteKeywordLocation'] = $id . $info['keywordLocation']; - } - - if ($subErrors) { - $info['errors'] = $subErrors; - if (!$isVerbose) { - unset($info['error']); - } - } - - return $info; - } - ); - } - - return ['valid' => false]; - } - - /** - * @param ValidationError $error - * @param ?callable(ValidationError,?array):mixed $formatter - * @return mixed - */ - public function formatNested($error, $formatter = null) - { - if (!$formatter) { - $formatter = function (ValidationError $error, $subErrors = null): array { - $ret = [ - 'message' => $this->formatErrorMessage($error), - 'keyword' => $error->keyword(), - 'path' => $this->formatErrorKey($error), - ]; - - if ($subErrors) { - $ret['errors'] = $subErrors; - } - - return $ret; - }; - } - - return $this->getNestedErrors($error, $formatter); - } - - /** - * @param ValidationError $error - * @param ?callable(ValidationError):mixed $formatter - * @return array - */ - public function formatFlat($error, $formatter = null): array - { - if (!$formatter) { - $formatter = [$this, 'formatErrorMessage']; - } - - $list = []; - - foreach ($this->getFlatErrors($error) as $error) { - $list[] = $formatter($error); - } - - return $list; - } - - /** - * @param ValidationError $error - * @param ?callable(ValidationError):mixed $formatter - * @param ?callable(ValidationError):string $key_formatter - * @return array - */ - public function formatKeyed( - $error, - $formatter = null, - $key_formatter = null - ): array { - if (!$formatter) { - $formatter = [$this, 'formatErrorMessage']; - } - - if (!$key_formatter) { - $key_formatter = [$this, 'formatErrorKey']; - } - - $list = []; - - foreach ($this->getLeafErrors($error) as $error) { - $key = $key_formatter($error); - - if (!isset($list[$key])) { - $list[$key] = []; - } - - $list[$key][] = $formatter($error); - } - - return $list; - } - - /** - * @param ValidationError $error - * @param string|null $message The message to use, if null $error->message() is used - * @return string - */ - public function formatErrorMessage($error, $message = null): string - { - $message = $message ?? $error->message(); - $args = $this->getDefaultArgs($error) + $error->args(); - - if (!$args) { - return $message; - } - - return preg_replace_callback( - '~{([^}]+)}~imu', - static function (array $m) use ($args) { - if (!isset($args[$m[1]])) { - return $m[0]; - } - - $value = $args[$m[1]]; - - if (is_array($value)) { - return implode(', ', $value); - } - - return (string) $value; - }, - $message - ); - } - - /** - * @param \Opis\JsonSchema\Errors\ValidationError $error - */ - public function formatErrorKey($error): string - { - return JsonPointer::pathToString($error->data()->fullPath()); - } - - /** - * @param \Opis\JsonSchema\Errors\ValidationError $error - */ - protected function getDefaultArgs($error): array - { - $data = $error->data(); - $info = $error->schema()->info(); - - $path = $info->path(); - $path[] = $error->keyword(); - - return [ - 'data:type' => $data->type(), - 'data:value' => $data->value(), - 'data:path' => JsonPointer::pathToString($data->fullPath()), - - 'schema:id' => $info->id(), - 'schema:root' => $info->root(), - 'schema:base' => $info->base(), - 'schema:draft' => $info->draft(), - 'schema:keyword' => $error->keyword(), - 'schema:path' => JsonPointer::pathToString($path), - ]; - } - - /** - * @param \Opis\JsonSchema\Errors\ValidationError $error - */ - protected function formatOutputError($error): array - { - $path = $error->schema()->info()->path(); - - $path[] = $error->keyword(); - - return [ - 'keywordLocation' => JsonPointer::pathToFragment($path), - 'instanceLocation' => JsonPointer::pathToFragment($error->data()->fullPath()), - 'error' => $this->formatErrorMessage($error), - ]; - } - - /** - * @param ValidationError $error - * @param callable(ValidationError,?array):mixed $formatter - * @return mixed - */ - protected function getNestedErrors($error, $formatter) - { - if ($subErrors = $error->subErrors()) { - foreach ($subErrors as &$subError) { - $subError = $this->getNestedErrors($subError, $formatter); - unset($subError); - } - } - - return $formatter($error, $subErrors); - } - - /** - * @param ValidationError $error - * @return iterable|ValidationError[] - */ - protected function getFlatErrors($error) - { - yield $error; - - foreach ($error->subErrors() as $subError) { - yield from $this->getFlatErrors($subError); - } - } - - /** - * @param ValidationError $error - * @return iterable|ValidationError[] - */ - protected function getLeafErrors($error) - { - if ($subErrors = $error->subErrors()) { - foreach ($subErrors as $subError) { - yield from $this->getLeafErrors($subError); - } - } else { - yield $error; - } - } - - /** - * @param ValidationError $error - * @return iterable - */ - protected function getErrors($error) - { - $data = $error->schema()->info()->data(); - - $map = null; - $pMap = null; - - if (is_object($data)) { - switch ($error->keyword()) { - case 'required': - if (isset($data->{'$error'}->required) && is_object($data->{'$error'}->required)) { - $e = $data->{'$error'}->required; - $found = false; - foreach ($error->args()['missing'] as $prop) { - if (isset($e->{$prop})) { - yield $error => $e->{$prop}; - $found = true; - } - } - if ($found) { - return; - } - if (isset($e->{'*'})) { - yield $error => $e->{'*'}; - return; - } - unset($e, $found, $prop); - } - break; - case '$filters': - if (($args = $error->args()) && isset($args['args']['$error'])) { - yield $error => $args['args']['$error']; - return; - } - unset($args); - break; - } - - if (isset($data->{'$error'})) { - $map = $data->{'$error'}; - - if (is_string($map)) { - // We have an global error - yield $error => $map; - return; - } - - if (is_object($map)) { - if (isset($map->{$error->keyword()})) { - $pMap = $map->{'*'} ?? null; - $map = $map->{$error->keyword()}; - if (is_string($map)) { - yield $error => $map; - return; - } - } elseif (isset($map->{'*'})) { - yield $error => $map->{'*'}; - return; - } - } - } - } - - if (!is_object($map)) { - $map = null; - } - - $subErrors = $error->subErrors(); - - if (!$subErrors) { - yield $error => $pMap ?? $error->message(); - return; - } - - if (!$map) { - foreach ($subErrors as $subError) { - yield from $this->getErrors($subError); - } - return; - } - - foreach ($subErrors as $subError) { - $path = $subError->data()->path(); - if (count($path) !== 1) { - yield from $this->getErrors($subError); - } else { - $path = $path[0]; - if (isset($map->{$path})) { - yield $subError => $map->{$path}; - } elseif (isset($map->{'*'})) { - yield $subError => $map->{'*'}; - } else { - yield from $this->getErrors($subError); - } - } - } - } +class ErrorFormatter { + + /** + * @param ValidationError $error + * @param bool $multiple True if the same key can have multiple errors + * @param ?callable(ValidationError,?string=null):mixed $formatter + * @param ?callable(ValidationError):string $key_formatter + * @return array + */ + public function format( + $error, + $multiple = true, + $formatter = null, + $key_formatter = null + ): array { + if ( ! $formatter ) { + $formatter = array( $this, 'formatErrorMessage' ); + } + + if ( ! $key_formatter ) { + $key_formatter = array( $this, 'formatErrorKey' ); + } + + $list = array(); + + /** + * @var ValidationError $error + * @var string $message + */ + + foreach ( $this->getErrors( $error ) as $error => $message ) { + $key = $key_formatter( $error ); + + if ( $multiple ) { + if ( ! isset( $list[ $key ] ) ) { + $list[ $key ] = array(); + } + $list[ $key ][] = $formatter( $error, $message ); + } elseif ( ! isset( $list[ $key ] ) ) { + $list[ $key ] = $formatter( $error, $message ); + } + } + + return $list; + } + + /** + * @param ValidationError|null $error + * @param string $mode One of: flag, basic, detailed or verbose + * @return array + */ + public function formatOutput( $error, $mode = 'flag' ): array { + if ( $error === null ) { + return array( 'valid' => true ); + } + + if ( $mode === 'flag' ) { + return array( 'valid' => false ); + } + + if ( $mode === 'basic' ) { + return array( + 'valid' => false, + 'errors' => $this->formatFlat( $error, array( $this, 'formatOutputError' ) ), + ); + } + + if ( $mode === 'detailed' || $mode === 'verbose' ) { + $isVerbose = $mode === 'verbose'; + + return $this->getNestedErrors( + $error, + function ( ValidationError $error, $subErrors = null ) use ( $isVerbose ) { + $info = $this->formatOutputError( $error ); + + $info['valid'] = false; + + if ( $isVerbose ) { + $id = $error->schema()->info(); + $id = $id->root() ?? $id->id(); + if ( $id ) { + $id = rtrim( $id, '#' ); + } + $info['absoluteKeywordLocation'] = $id . $info['keywordLocation']; + } + + if ( $subErrors ) { + $info['errors'] = $subErrors; + if ( ! $isVerbose ) { + unset( $info['error'] ); + } + } + + return $info; + } + ); + } + + return array( 'valid' => false ); + } + + /** + * @param ValidationError $error + * @param ?callable(ValidationError,?array):mixed $formatter + * @return mixed + */ + public function formatNested( $error, $formatter = null ) { + if ( ! $formatter ) { + $formatter = function ( ValidationError $error, $subErrors = null ): array { + $ret = array( + 'message' => $this->formatErrorMessage( $error ), + 'keyword' => $error->keyword(), + 'path' => $this->formatErrorKey( $error ), + ); + + if ( $subErrors ) { + $ret['errors'] = $subErrors; + } + + return $ret; + }; + } + + return $this->getNestedErrors( $error, $formatter ); + } + + /** + * @param ValidationError $error + * @param ?callable(ValidationError):mixed $formatter + * @return array + */ + public function formatFlat( $error, $formatter = null ): array { + if ( ! $formatter ) { + $formatter = array( $this, 'formatErrorMessage' ); + } + + $list = array(); + + foreach ( $this->getFlatErrors( $error ) as $error ) { + $list[] = $formatter( $error ); + } + + return $list; + } + + /** + * @param ValidationError $error + * @param ?callable(ValidationError):mixed $formatter + * @param ?callable(ValidationError):string $key_formatter + * @return array + */ + public function formatKeyed( + $error, + $formatter = null, + $key_formatter = null + ): array { + if ( ! $formatter ) { + $formatter = array( $this, 'formatErrorMessage' ); + } + + if ( ! $key_formatter ) { + $key_formatter = array( $this, 'formatErrorKey' ); + } + + $list = array(); + + foreach ( $this->getLeafErrors( $error ) as $error ) { + $key = $key_formatter( $error ); + + if ( ! isset( $list[ $key ] ) ) { + $list[ $key ] = array(); + } + + $list[ $key ][] = $formatter( $error ); + } + + return $list; + } + + /** + * @param ValidationError $error + * @param string|null $message The message to use, if null $error->message() is used + * @return string + */ + public function formatErrorMessage( $error, $message = null ): string { + $message = $message ?? $error->message(); + $args = $this->getDefaultArgs( $error ) + $error->args(); + + if ( ! $args ) { + return $message; + } + + return preg_replace_callback( + '~{([^}]+)}~imu', + static function ( array $m ) use ( $args ) { + if ( ! isset( $args[ $m[1] ] ) ) { + return $m[0]; + } + + $value = $args[ $m[1] ]; + + if ( is_array( $value ) ) { + return implode( ', ', $value ); + } + + return (string) $value; + }, + $message + ); + } + + /** + * @param \Opis\JsonSchema\Errors\ValidationError $error + */ + public function formatErrorKey( $error ): string { + return JsonPointer::pathToString( $error->data()->fullPath() ); + } + + /** + * @param \Opis\JsonSchema\Errors\ValidationError $error + */ + protected function getDefaultArgs( $error ): array { + $data = $error->data(); + $info = $error->schema()->info(); + + $path = $info->path(); + $path[] = $error->keyword(); + + return array( + 'data:type' => $data->type(), + 'data:value' => $data->value(), + 'data:path' => JsonPointer::pathToString( $data->fullPath() ), + + 'schema:id' => $info->id(), + 'schema:root' => $info->root(), + 'schema:base' => $info->base(), + 'schema:draft' => $info->draft(), + 'schema:keyword' => $error->keyword(), + 'schema:path' => JsonPointer::pathToString( $path ), + ); + } + + /** + * @param \Opis\JsonSchema\Errors\ValidationError $error + */ + protected function formatOutputError( $error ): array { + $path = $error->schema()->info()->path(); + + $path[] = $error->keyword(); + + return array( + 'keywordLocation' => JsonPointer::pathToFragment( $path ), + 'instanceLocation' => JsonPointer::pathToFragment( $error->data()->fullPath() ), + 'error' => $this->formatErrorMessage( $error ), + ); + } + + /** + * @param ValidationError $error + * @param callable(ValidationError,?array):mixed $formatter + * @return mixed + */ + protected function getNestedErrors( $error, $formatter ) { + if ( $subErrors = $error->subErrors() ) { + foreach ( $subErrors as &$subError ) { + $subError = $this->getNestedErrors( $subError, $formatter ); + unset( $subError ); + } + } + + return $formatter( $error, $subErrors ); + } + + /** + * @param ValidationError $error + * @return iterable|ValidationError[] + */ + protected function getFlatErrors( $error ) { + yield $error; + + foreach ( $error->subErrors() as $subError ) { + yield from $this->getFlatErrors( $subError ); + } + } + + /** + * @param ValidationError $error + * @return iterable|ValidationError[] + */ + protected function getLeafErrors( $error ) { + if ( $subErrors = $error->subErrors() ) { + foreach ( $subErrors as $subError ) { + yield from $this->getLeafErrors( $subError ); + } + } else { + yield $error; + } + } + + /** + * @param ValidationError $error + * @return iterable + */ + protected function getErrors( $error ) { + $data = $error->schema()->info()->data(); + + $map = null; + $pMap = null; + + if ( is_object( $data ) ) { + switch ( $error->keyword() ) { + case 'required': + if ( isset( $data->{'$error'}->required ) && is_object( $data->{'$error'}->required ) ) { + $e = $data->{'$error'}->required; + $found = false; + foreach ( $error->args()['missing'] as $prop ) { + if ( isset( $e->{$prop} ) ) { + yield $error => $e->{$prop}; + $found = true; + } + } + if ( $found ) { + return; + } + if ( isset( $e->{'*'} ) ) { + yield $error => $e->{'*'}; + return; + } + unset( $e, $found, $prop ); + } + break; + case '$filters': + if ( ( $args = $error->args() ) && isset( $args['args']['$error'] ) ) { + yield $error => $args['args']['$error']; + return; + } + unset( $args ); + break; + } + + if ( isset( $data->{'$error'} ) ) { + $map = $data->{'$error'}; + + if ( is_string( $map ) ) { + // We have an global error + yield $error => $map; + return; + } + + if ( is_object( $map ) ) { + if ( isset( $map->{$error->keyword()} ) ) { + $pMap = $map->{'*'} ?? null; + $map = $map->{$error->keyword()}; + if ( is_string( $map ) ) { + yield $error => $map; + return; + } + } elseif ( isset( $map->{'*'} ) ) { + yield $error => $map->{'*'}; + return; + } + } + } + } + + if ( ! is_object( $map ) ) { + $map = null; + } + + $subErrors = $error->subErrors(); + + if ( ! $subErrors ) { + yield $error => $pMap ?? $error->message(); + return; + } + + if ( ! $map ) { + foreach ( $subErrors as $subError ) { + yield from $this->getErrors( $subError ); + } + return; + } + + foreach ( $subErrors as $subError ) { + $path = $subError->data()->path(); + if ( count( $path ) !== 1 ) { + yield from $this->getErrors( $subError ); + } else { + $path = $path[0]; + if ( isset( $map->{$path} ) ) { + yield $subError => $map->{$path}; + } elseif ( isset( $map->{'*'} ) ) { + yield $subError => $map->{'*'}; + } else { + yield from $this->getErrors( $subError ); + } + } + } + } } diff --git a/src/opis/json-schema/src/Errors/ValidationError.php b/src/opis/json-schema/src/Errors/ValidationError.php index 84fd5f7f..9fc978bb 100644 --- a/src/opis/json-schema/src/Errors/ValidationError.php +++ b/src/opis/json-schema/src/Errors/ValidationError.php @@ -1,5 +1,6 @@ keyword = $keyword; - $this->schema = $schema; - $this->data = $data; - $this->message = $message; - $this->args = $args; - $this->subErrors = $subErrors; - } - - public function keyword(): string - { - return $this->keyword; - } - - public function schema(): Schema - { - return $this->schema; - } - - public function data(): DataInfo - { - return $this->data; - } - - public function args(): array - { - return $this->args; - } - - public function message(): string - { - return $this->message; - } - - public function subErrors(): array - { - return $this->subErrors; - } - - public function __toString(): string - { - return $this->message; - } -} \ No newline at end of file +class ValidationError { + + /** + * @var string + */ + protected $keyword; + + /** + * @var \Opis\JsonSchema\Schema + */ + protected $schema; + + /** + * @var \Opis\JsonSchema\Info\DataInfo + */ + protected $data; + + /** + * @var mixed[] + */ + protected $args; + + /** + * @var string + */ + protected $message; + + /** @var ValidationError[] */ + protected $subErrors; + + /** + * @param string $keyword + * @param Schema $schema + * @param DataInfo $data + * @param string $message + * @param array $args + * @param ValidationError[] $subErrors + */ + public function __construct( + string $keyword, + Schema $schema, + DataInfo $data, + string $message, + array $args = array(), + array $subErrors = array() + ) { + $this->keyword = $keyword; + $this->schema = $schema; + $this->data = $data; + $this->message = $message; + $this->args = $args; + $this->subErrors = $subErrors; + } + + public function keyword(): string { + return $this->keyword; + } + + public function schema(): Schema { + return $this->schema; + } + + public function data(): DataInfo { + return $this->data; + } + + public function args(): array { + return $this->args; + } + + public function message(): string { + return $this->message; + } + + public function subErrors(): array { + return $this->subErrors; + } + + public function __toString(): string { + return $this->message; + } +} diff --git a/src/opis/json-schema/src/Exceptions/DuplicateSchemaIdException.php b/src/opis/json-schema/src/Exceptions/DuplicateSchemaIdException.php index a6cc228c..baa7981f 100644 --- a/src/opis/json-schema/src/Exceptions/DuplicateSchemaIdException.php +++ b/src/opis/json-schema/src/Exceptions/DuplicateSchemaIdException.php @@ -1,5 +1,6 @@ id = $id; - $this->data = $data; - } - - /** - * @return null|object - */ - public function getData() - { - return $this->data; - } - - /** - * @return Uri - */ - public function getId(): Uri - { - return $this->id; - } -} \ No newline at end of file +class DuplicateSchemaIdException extends RuntimeException implements SchemaException { + + + /** + * @var \Opis\JsonSchema\Uri + */ + protected $id; + + /** + * @var object|null + */ + protected $data; + + /** + * DuplicateSchemaIdException constructor. + * + * @param Uri $id + * @param object|null $data + */ + public function __construct( Uri $id, $data = null ) { + parent::__construct( "Duplicate schema id: {$id}", 0 ); + $this->id = $id; + $this->data = $data; + } + + /** + * @return null|object + */ + public function getData() { + return $this->data; + } + + /** + * @return Uri + */ + public function getId(): Uri { + return $this->id; + } +} diff --git a/src/opis/json-schema/src/Exceptions/InvalidKeywordException.php b/src/opis/json-schema/src/Exceptions/InvalidKeywordException.php index f17bd002..7ea69bcf 100644 --- a/src/opis/json-schema/src/Exceptions/InvalidKeywordException.php +++ b/src/opis/json-schema/src/Exceptions/InvalidKeywordException.php @@ -1,5 +1,6 @@ keyword = $keyword; - } + /** + * @var string + */ + protected $keyword; - /** - * @return string - */ - public function keyword(): string - { - return $this->keyword; - } + /** + * InvalidKeywordException constructor. + * + * @param string $message + * @param string $keyword + * @param SchemaInfo|null $info + */ + public function __construct( string $message, string $keyword, $info = null ) { + parent::__construct( $message, $info ); + $this->keyword = $keyword; + } + + /** + * @return string + */ + public function keyword(): string { + return $this->keyword; + } } diff --git a/src/opis/json-schema/src/Exceptions/InvalidPragmaException.php b/src/opis/json-schema/src/Exceptions/InvalidPragmaException.php index 87f89895..78efa0c4 100644 --- a/src/opis/json-schema/src/Exceptions/InvalidPragmaException.php +++ b/src/opis/json-schema/src/Exceptions/InvalidPragmaException.php @@ -1,5 +1,6 @@ pragma = $pragma; - } + /** + * @var string + */ + protected $pragma; - /** - * @return string - */ - public function pragma(): string - { - return $this->pragma; - } + /** + * InvalidPragmaException constructor. + * + * @param string $message + * @param string $pragma + * @param SchemaInfo|null $info + */ + public function __construct( string $message, string $pragma, $info = null ) { + parent::__construct( $message, '$pragma', $info ); + $this->pragma = $pragma; + } + + /** + * @return string + */ + public function pragma(): string { + return $this->pragma; + } } diff --git a/src/opis/json-schema/src/Exceptions/ParseException.php b/src/opis/json-schema/src/Exceptions/ParseException.php index c77c4c71..b388c437 100644 --- a/src/opis/json-schema/src/Exceptions/ParseException.php +++ b/src/opis/json-schema/src/Exceptions/ParseException.php @@ -1,5 +1,6 @@ info = $info; - } + /** + * @var \Opis\JsonSchema\Info\SchemaInfo|null + */ + protected $info; - /** - * @return SchemaInfo|null - */ - public function schemaInfo() - { - return $this->info; - } + /** + * @param string $message + * @param SchemaInfo|null $info + */ + public function __construct( string $message, $info = null ) { + parent::__construct( $message, 0 ); + $this->info = $info; + } + + /** + * @return SchemaInfo|null + */ + public function schemaInfo() { + return $this->info; + } } diff --git a/src/opis/json-schema/src/Exceptions/SchemaException.php b/src/opis/json-schema/src/Exceptions/SchemaException.php index feedbcd4..40f56911 100644 --- a/src/opis/json-schema/src/Exceptions/SchemaException.php +++ b/src/opis/json-schema/src/Exceptions/SchemaException.php @@ -1,5 +1,6 @@ encoding = $encoding; - } + /** + * @var string + */ + protected $encoding; - /** - * @return string - */ - public function getContentEncoding(): string - { - return $this->encoding; - } -} \ No newline at end of file + /** + * @param string $encoding + * @param Schema $schema + * @param ValidationContext $context + */ + public function __construct( string $encoding, Schema $schema, ValidationContext $context ) { + parent::__construct( "Cannot resolve '{$encoding}' content encoding", $schema, $context ); + $this->encoding = $encoding; + } + + /** + * @return string + */ + public function getContentEncoding(): string { + return $this->encoding; + } +} diff --git a/src/opis/json-schema/src/Exceptions/UnresolvedContentMediaTypeException.php b/src/opis/json-schema/src/Exceptions/UnresolvedContentMediaTypeException.php index 1f23677c..ffc5c3b8 100644 --- a/src/opis/json-schema/src/Exceptions/UnresolvedContentMediaTypeException.php +++ b/src/opis/json-schema/src/Exceptions/UnresolvedContentMediaTypeException.php @@ -1,5 +1,6 @@ media = $media; - } + /** + * @var string + */ + protected $media; - /** - * @return string - */ - public function getContentMediaType(): string - { - return $this->media; - } -} \ No newline at end of file + /** + * @param string $media + * @param Schema $schema + * @param ValidationContext $context + */ + public function __construct( string $media, Schema $schema, ValidationContext $context ) { + parent::__construct( "Cannot resolve '{$media}' content media type", $schema, $context ); + $this->media = $media; + } + + /** + * @return string + */ + public function getContentMediaType(): string { + return $this->media; + } +} diff --git a/src/opis/json-schema/src/Exceptions/UnresolvedException.php b/src/opis/json-schema/src/Exceptions/UnresolvedException.php index e122cae2..76a848ee 100644 --- a/src/opis/json-schema/src/Exceptions/UnresolvedException.php +++ b/src/opis/json-schema/src/Exceptions/UnresolvedException.php @@ -1,5 +1,6 @@ schema = $schema; - $this->context = $context; - } - - /** - * @return Schema - */ - public function getSchema(): Schema - { - return $this->schema; - } - - /** - * @return ValidationContext - */ - public function getContext(): ValidationContext - { - return $this->context; - } -} \ No newline at end of file +class UnresolvedException extends RuntimeException implements SchemaException { + + + /** + * @var \Opis\JsonSchema\Schema + */ + protected $schema; + + /** + * @var \Opis\JsonSchema\ValidationContext + */ + protected $context; + + /** + * @param string $message + * @param Schema $schema + * @param ValidationContext $context + */ + public function __construct( string $message, Schema $schema, ValidationContext $context ) { + parent::__construct( $message ); + $this->schema = $schema; + $this->context = $context; + } + + /** + * @return Schema + */ + public function getSchema(): Schema { + return $this->schema; + } + + /** + * @return ValidationContext + */ + public function getContext(): ValidationContext { + return $this->context; + } +} diff --git a/src/opis/json-schema/src/Exceptions/UnresolvedFilterException.php b/src/opis/json-schema/src/Exceptions/UnresolvedFilterException.php index 3acddb7b..c983e4dc 100644 --- a/src/opis/json-schema/src/Exceptions/UnresolvedFilterException.php +++ b/src/opis/json-schema/src/Exceptions/UnresolvedFilterException.php @@ -1,5 +1,6 @@ filter = $filter; - $this->type = $type; - } - - /** - * @return string - */ - public function getFilter(): string - { - return $this->filter; - } - - /** - * @return string - */ - public function getType(): string - { - return $this->type; - } -} \ No newline at end of file +class UnresolvedFilterException extends UnresolvedException { + + + /** + * @var string + */ + protected $filter; + + /** + * @var string + */ + protected $type; + + /** + * @param string $filter + * @param string $type + * @param Schema $schema + * @param ValidationContext $context + */ + public function __construct( string $filter, string $type, Schema $schema, ValidationContext $context ) { + parent::__construct( "Cannot resolve filter '{$filter}' for type '{$type}'", $schema, $context ); + $this->filter = $filter; + $this->type = $type; + } + + /** + * @return string + */ + public function getFilter(): string { + return $this->filter; + } + + /** + * @return string + */ + public function getType(): string { + return $this->type; + } +} diff --git a/src/opis/json-schema/src/Exceptions/UnresolvedReferenceException.php b/src/opis/json-schema/src/Exceptions/UnresolvedReferenceException.php index 7b9c2f92..c6a0b9e8 100644 --- a/src/opis/json-schema/src/Exceptions/UnresolvedReferenceException.php +++ b/src/opis/json-schema/src/Exceptions/UnresolvedReferenceException.php @@ -1,5 +1,6 @@ ref = $ref; - } + /** + * @var string + */ + protected $ref; - /** - * @return string - */ - public function getRef(): string - { - return $this->ref; - } -} \ No newline at end of file + /** + * @param string $ref + * @param Schema $schema + * @param ValidationContext $context + */ + public function __construct( string $ref, Schema $schema, ValidationContext $context ) { + parent::__construct( "Unresolved reference: {$ref}", $schema, $context ); + $this->ref = $ref; + } + + /** + * @return string + */ + public function getRef(): string { + return $this->ref; + } +} diff --git a/src/opis/json-schema/src/Filter.php b/src/opis/json-schema/src/Filter.php index 90583080..8ce12551 100644 --- a/src/opis/json-schema/src/Filter.php +++ b/src/opis/json-schema/src/Filter.php @@ -1,5 +1,6 @@ currentData(); - if (!is_string($ref)) { - return false; - } +class DataExistsFilter implements Filter { - $ref = JsonPointer::parse($ref); - if ($ref === null) { - return false; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + * @param mixed[] $args + */ + public function validate( $context, $schema, $args = array() ): bool { + $ref = $args['ref'] ?? $context->currentData(); + if ( ! is_string( $ref ) ) { + return false; + } - return $ref->data($context->rootData(), $context->currentDataPath(), $this) !== $this; - } -} \ No newline at end of file + $ref = JsonPointer::parse( $ref ); + if ( $ref === null ) { + return false; + } + + return $ref->data( $context->rootData(), $context->currentDataPath(), $this ) !== $this; + } +} diff --git a/src/opis/json-schema/src/Filters/DateTimeFilters.php b/src/opis/json-schema/src/Filters/DateTimeFilters.php index 1df0fe2e..f770f0db 100644 --- a/src/opis/json-schema/src/Filters/DateTimeFilters.php +++ b/src/opis/json-schema/src/Filters/DateTimeFilters.php @@ -1,5 +1,6 @@ = self::CreateDate($min, $tz, false); - } - - public static function MaxDate(string $date, array $args): bool - { - $max = $args['value']; - $tz = $args['timezone'] ?? null; - - return self::CreateDate($date, $tz, false) <= self::CreateDate($max, $tz, false); - } - - public static function NotDate(string $date, array $args): bool - { - $not = $args['value']; - $tz = $args['timezone'] ?? null; - - if (!is_array($not)) { - $not = [$not]; - } - - $date = self::CreateDate($date, $tz, false); - - foreach ($not as $d) { - if ($date == self::CreateDate($d, $tz, false)) { - return false; - } - } - - return true; - } - - public static function MinDateTime(string $date, array $args): bool - { - $min = $args['value']; - $tz = $args['timezone'] ?? null; - - return self::CreateDate($date, $tz) >= self::CreateDate($min, $tz); - } - - public static function MaxDateTime(string $date, array $args): bool - { - $max = $args['value']; - $tz = $args['timezone'] ?? null; - - return self::CreateDate($date, $tz) <= self::CreateDate($max, $tz); - } - - public static function MinTime(string $time, array $args): bool - { - $min = $args['value']; - $prefix = '1970-01-01 '; - - return self::CreateDate($prefix . $time) >= self::CreateDate($prefix . $min); - } - - public static function MaxTime(string $time, array $args): bool - { - $max = $args['value']; - $prefix = '1970-01-01 '; - - return self::CreateDate($prefix . $time) <= self::CreateDate($prefix . $max); - } - - private static function CreateDate(string $value, $timezone = null, bool $time = true): DateTime - { - $date = new DateTime($value, $timezone); - if (!$time) { - return $date->setTime(0, 0, 0, 0); - } - return $date; - } +final class DateTimeFilters { + + public static function MinDate( string $date, array $args ): bool { + $min = $args['value']; + $tz = $args['timezone'] ?? null; + + return self::CreateDate( $date, $tz, false ) >= self::CreateDate( $min, $tz, false ); + } + + public static function MaxDate( string $date, array $args ): bool { + $max = $args['value']; + $tz = $args['timezone'] ?? null; + + return self::CreateDate( $date, $tz, false ) <= self::CreateDate( $max, $tz, false ); + } + + public static function NotDate( string $date, array $args ): bool { + $not = $args['value']; + $tz = $args['timezone'] ?? null; + + if ( ! is_array( $not ) ) { + $not = array( $not ); + } + + $date = self::CreateDate( $date, $tz, false ); + + foreach ( $not as $d ) { + if ( $date == self::CreateDate( $d, $tz, false ) ) { + return false; + } + } + + return true; + } + + public static function MinDateTime( string $date, array $args ): bool { + $min = $args['value']; + $tz = $args['timezone'] ?? null; + + return self::CreateDate( $date, $tz ) >= self::CreateDate( $min, $tz ); + } + + public static function MaxDateTime( string $date, array $args ): bool { + $max = $args['value']; + $tz = $args['timezone'] ?? null; + + return self::CreateDate( $date, $tz ) <= self::CreateDate( $max, $tz ); + } + + public static function MinTime( string $time, array $args ): bool { + $min = $args['value']; + $prefix = '1970-01-01 '; + + return self::CreateDate( $prefix . $time ) >= self::CreateDate( $prefix . $min ); + } + + public static function MaxTime( string $time, array $args ): bool { + $max = $args['value']; + $prefix = '1970-01-01 '; + + return self::CreateDate( $prefix . $time ) <= self::CreateDate( $prefix . $max ); + } + + private static function CreateDate( string $value, $timezone = null, bool $time = true ): DateTime { + $date = new DateTime( $value, $timezone ); + if ( ! $time ) { + return $date->setTime( 0, 0, 0, 0 ); + } + return $date; + } } diff --git a/src/opis/json-schema/src/Filters/FilterExistsFilter.php b/src/opis/json-schema/src/Filters/FilterExistsFilter.php index 3ce635d3..bad51708 100644 --- a/src/opis/json-schema/src/Filters/FilterExistsFilter.php +++ b/src/opis/json-schema/src/Filters/FilterExistsFilter.php @@ -1,5 +1,6 @@ currentData(); - if (!is_string($filter)) { - return false; - } - - $type = null; - if (isset($args['type'])) { - if (!is_string($args['type'])) { - return false; - } - $type = $args['type']; - } - - $resolver = $context->loader()->parser()->getFilterResolver(); - - if (!$resolver) { - return false; - } - - if ($type === null) { - return (bool)$resolver->resolveAll($filter); - } - - return (bool)$resolver->resolve($filter, $type); - } -} \ No newline at end of file +class FilterExistsFilter implements Filter { + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + * @param mixed[] $args + */ + public function validate( $context, $schema, $args = array() ): bool { + $filter = $args['filter'] ?? $context->currentData(); + if ( ! is_string( $filter ) ) { + return false; + } + + $type = null; + if ( isset( $args['type'] ) ) { + if ( ! is_string( $args['type'] ) ) { + return false; + } + $type = $args['type']; + } + + $resolver = $context->loader()->parser()->getFilterResolver(); + + if ( ! $resolver ) { + return false; + } + + if ( $type === null ) { + return (bool) $resolver->resolveAll( $filter ); + } + + return (bool) $resolver->resolve( $filter, $type ); + } +} diff --git a/src/opis/json-schema/src/Filters/FormatExistsFilter.php b/src/opis/json-schema/src/Filters/FormatExistsFilter.php index 7dc7f4e2..dde33510 100644 --- a/src/opis/json-schema/src/Filters/FormatExistsFilter.php +++ b/src/opis/json-schema/src/Filters/FormatExistsFilter.php @@ -1,5 +1,6 @@ currentData(); - if (!is_string($format)) { - return false; - } - - $type = null; - if (isset($args['type'])) { - if (!is_string($args['type'])) { - return false; - } - $type = $args['type']; - } - - $resolver = $context->loader()->parser()->getFormatResolver(); - - if (!$resolver) { - return false; - } - - if ($type === null) { - return (bool)$resolver->resolveAll($format); - } - - return (bool)$resolver->resolve($format, $type); - } -} \ No newline at end of file +class FormatExistsFilter implements Filter { + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + * @param mixed[] $args + */ + public function validate( $context, $schema, $args = array() ): bool { + $format = $args['format'] ?? $context->currentData(); + if ( ! is_string( $format ) ) { + return false; + } + + $type = null; + if ( isset( $args['type'] ) ) { + if ( ! is_string( $args['type'] ) ) { + return false; + } + $type = $args['type']; + } + + $resolver = $context->loader()->parser()->getFormatResolver(); + + if ( ! $resolver ) { + return false; + } + + if ( $type === null ) { + return (bool) $resolver->resolveAll( $format ); + } + + return (bool) $resolver->resolve( $format, $type ); + } +} diff --git a/src/opis/json-schema/src/Filters/GlobalVarExistsFilter.php b/src/opis/json-schema/src/Filters/GlobalVarExistsFilter.php index e8393d40..e6ea6fa2 100644 --- a/src/opis/json-schema/src/Filters/GlobalVarExistsFilter.php +++ b/src/opis/json-schema/src/Filters/GlobalVarExistsFilter.php @@ -1,5 +1,6 @@ currentData(); - - if (!is_string($var)) { - return false; - } - - $globals = $context->globals(); - - if (!array_key_exists($var, $globals)) { - return false; - } - - if (array_key_exists('value', $args)) { - return $globals[$var] == $args['value']; - } - - return true; - } -} \ No newline at end of file +class GlobalVarExistsFilter implements Filter { + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + * @param mixed[] $args + */ + public function validate( $context, $schema, $args = array() ): bool { + $var = $args['var'] ?? $context->currentData(); + + if ( ! is_string( $var ) ) { + return false; + } + + $globals = $context->globals(); + + if ( ! array_key_exists( $var, $globals ) ) { + return false; + } + + if ( array_key_exists( 'value', $args ) ) { + return $globals[ $var ] == $args['value']; + } + + return true; + } +} diff --git a/src/opis/json-schema/src/Filters/SchemaExistsFilter.php b/src/opis/json-schema/src/Filters/SchemaExistsFilter.php index 071d7448..828bf794 100644 --- a/src/opis/json-schema/src/Filters/SchemaExistsFilter.php +++ b/src/opis/json-schema/src/Filters/SchemaExistsFilter.php @@ -1,5 +1,6 @@ currentData(); - if (!is_string($ref)) { - return false; - } +class SchemaExistsFilter implements Filter { - if (UriTemplate::isTemplate($ref)) { - if (isset($args['vars']) && is_object($args['vars'])) { - $vars = new VariablesContainer($args['vars'], false); - $vars = $vars->resolve($context->rootData(), $context->currentDataPath()); - if (!is_array($vars)) { - $vars = (array)$vars; - } - $vars += $context->globals(); - } else { - $vars = $context->globals(); - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + * @param mixed[] $args + */ + public function validate( $context, $schema, $args = array() ): bool { + $ref = $args['ref'] ?? $context->currentData(); + if ( ! is_string( $ref ) ) { + return false; + } - $ref = (new UriTemplate($ref))->resolve($vars); + if ( UriTemplate::isTemplate( $ref ) ) { + if ( isset( $args['vars'] ) && is_object( $args['vars'] ) ) { + $vars = new VariablesContainer( $args['vars'], false ); + $vars = $vars->resolve( $context->rootData(), $context->currentDataPath() ); + if ( ! is_array( $vars ) ) { + $vars = (array) $vars; + } + $vars += $context->globals(); + } else { + $vars = $context->globals(); + } - unset($vars); - } + $ref = ( new UriTemplate( $ref ) )->resolve( $vars ); - unset($args); + unset( $vars ); + } - return $this->refExists($ref, $context, $schema); - } + unset( $args ); - /** - * @param string $ref - * @param ValidationContext $context - * @param Schema $schema - * @return bool - */ - protected function refExists($ref, $context, $schema): bool - { - if ($ref === '') { - return false; - } + return $this->refExists( $ref, $context, $schema ); + } - if ($ref === '#') { - return true; - } + /** + * @param string $ref + * @param ValidationContext $context + * @param Schema $schema + * @return bool + */ + protected function refExists( $ref, $context, $schema ): bool { + if ( $ref === '' ) { + return false; + } - $info = $schema->info(); + if ( $ref === '#' ) { + return true; + } - $id = Uri::merge($ref, $info->idBaseRoot(), true); + $info = $schema->info(); - if ($id === null) { - return false; - } + $id = Uri::merge( $ref, $info->idBaseRoot(), true ); - return $context->loader()->loadSchemaById($id) !== null; - } -} \ No newline at end of file + if ( $id === null ) { + return false; + } + + return $context->loader()->loadSchemaById( $id ) !== null; + } +} diff --git a/src/opis/json-schema/src/Filters/SlotExistsFilter.php b/src/opis/json-schema/src/Filters/SlotExistsFilter.php index af4c8cb6..dac8a10c 100644 --- a/src/opis/json-schema/src/Filters/SlotExistsFilter.php +++ b/src/opis/json-schema/src/Filters/SlotExistsFilter.php @@ -1,5 +1,6 @@ currentData(); - if (!is_string($slot)) { - return false; - } +class SlotExistsFilter implements Filter { - return $context->slot($slot) !== null; - } -} \ No newline at end of file + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + * @param mixed[] $args + */ + public function validate( $context, $schema, $args = array() ): bool { + $slot = $args['slot'] ?? $context->currentData(); + if ( ! is_string( $slot ) ) { + return false; + } + + return $context->slot( $slot ) !== null; + } +} diff --git a/src/opis/json-schema/src/Format.php b/src/opis/json-schema/src/Format.php index 577a5349..e487a6f4 100644 --- a/src/opis/json-schema/src/Format.php +++ b/src/opis/json-schema/src/Format.php @@ -1,5 +1,6 @@ .+)@(?.+)$/u', $value, $m)) { - return false; - } - - $m['name'] = $idn($m['name']); - if ($m['name'] === null) { - return false; - } - - $m['domain'] = $idn($m['domain']); - if ($m['domain'] === null) { - return false; - } - - $value = $m['name'] . '@' . $m['domain']; - } - - return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; - } - - /** - * @return callable|null - */ - public static function idn() - { - if (static::$idn === false) { - if (function_exists('idn_to_ascii')) { - static::$idn = static function (string $value) { - /** @noinspection PhpComposerExtensionStubsInspection */ - $value = idn_to_ascii($value, 0, INTL_IDNA_VARIANT_UTS46); - - return is_string($value) ? $value : null; - }; - } else { - static::$idn = null; - } - } - - return static::$idn; - } +class IriFormats { + + const SKIP = array( 0x23, 0x26, 0x2F, 0x3A, 0x3D, 0x3F, 0x40, 0x5B, 0x5C, 0x5D ); + + /** @var bool|null|callable */ + private static $idn = false; + + /** + * @param string $value + * @return bool + */ + public static function iri( $value ): bool { + if ( $value === '' ) { + return false; + } + + try { + $components = Uri::parseComponents( Uri::encodeComponent( $value, self::SKIP ), true, true ); + } catch ( Throwable $e ) { + return false; + } + + return isset( $components['scheme'] ) && $components['scheme'] !== ''; + } + + /** + * @param string $value + * @return bool + */ + public static function iriReference( $value ): bool { + if ( $value === '' ) { + return true; + } + + try { + return Uri::parseComponents( Uri::encodeComponent( $value, self::SKIP ), true, true ) !== null; + } catch ( Throwable $e ) { + return false; + } + } + + /** + * @param string $value + * @param callable|null $idn + * @return bool + */ + public static function idnHostname( $value, $idn = null ): bool { + $idn = $idn ?? static::idn(); + + if ( $idn ) { + $value = $idn( $value ); + if ( $value === null ) { + return false; + } + } + + return Uri::isValidHost( $value ); + } + + /** + * @param string $value + * @param callable|null $idn + * @return bool + */ + public static function idnEmail( $value, $idn = null ): bool { + $idn = $idn ?? static::idn(); + + if ( $idn ) { + if ( ! preg_match( '/^(?.+)@(?.+)$/u', $value, $m ) ) { + return false; + } + + $m['name'] = $idn( $m['name'] ); + if ( $m['name'] === null ) { + return false; + } + + $m['domain'] = $idn( $m['domain'] ); + if ( $m['domain'] === null ) { + return false; + } + + $value = $m['name'] . '@' . $m['domain']; + } + + return filter_var( $value, FILTER_VALIDATE_EMAIL ) !== false; + } + + /** + * @return callable|null + */ + public static function idn() { + if ( static::$idn === false ) { + if ( function_exists( 'idn_to_ascii' ) ) { + static::$idn = static function ( string $value ) { + /** @noinspection PhpComposerExtensionStubsInspection */ + $value = idn_to_ascii( $value, 0, INTL_IDNA_VARIANT_UTS46 ); + + return is_string( $value ) ? $value : null; + }; + } else { + static::$idn = null; + } + } + + return static::$idn; + } } diff --git a/src/opis/json-schema/src/Formats/MiscFormats.php b/src/opis/json-schema/src/Formats/MiscFormats.php index a5098907..e5030830 100644 --- a/src/opis/json-schema/src/Formats/MiscFormats.php +++ b/src/opis/json-schema/src/Formats/MiscFormats.php @@ -1,5 +1,6 @@ isAbsolute(); - } + $uri = Uri::parse( $value ); - /** - * @param string $value - * @return bool - */ - public static function uriReference($value): bool - { - if ($value === '') { - return true; - } + return $uri !== null && $uri->isAbsolute(); + } - return Uri::parse($value) !== null; - } + /** + * @param string $value + * @return bool + */ + public static function uriReference( $value ): bool { + if ( $value === '' ) { + return true; + } - /** - * @param string $value - * @return bool - */ - public static function uriTemplate($value): bool - { - if ($value === '') { - return true; - } + return Uri::parse( $value ) !== null; + } - if (UriTemplate::isTemplate($value)) { - return true; - } + /** + * @param string $value + * @return bool + */ + public static function uriTemplate( $value ): bool { + if ( $value === '' ) { + return true; + } - return Uri::parse($value) !== null; - } -} \ No newline at end of file + if ( UriTemplate::isTemplate( $value ) ) { + return true; + } + + return Uri::parse( $value ) !== null; + } +} diff --git a/src/opis/json-schema/src/Helper.php b/src/opis/json-schema/src/Helper.php index 7ba5578b..98511464 100644 --- a/src/opis/json-schema/src/Helper.php +++ b/src/opis/json-schema/src/Helper.php @@ -1,5 +1,6 @@ 'number']; - - /** @var string[] */ - const PHP_TYPE_MAP = [ - 'NULL' => 'null', - 'integer' => 'integer', - 'double' => 'number', - 'boolean' => 'boolean', - 'array' => 'array', - 'object' => 'object', - 'string' => 'string', - ]; - - /** - * @param string $type - * @return bool - */ - public static function isValidJsonType(string $type): bool - { - if (isset(self::JSON_SUBTYPES[$type])) { - return true; - } - - return in_array($type, self::JSON_TYPES, true); - } - - /** - * @param string $type - * @return null|string - */ - public static function getJsonSuperType(string $type) - { - return self::JSON_SUBTYPES[$type] ?? null; - } - - /** - * @param mixed $value - * @param bool $use_subtypes - * @return null|string - */ - public static function getJsonType($value, bool $use_subtypes = true) - { - $type = self::PHP_TYPE_MAP[gettype($value)] ?? null; - if ($type === null) { - return null; - } elseif ($type === 'array') { - return self::isIndexedArray($value) ? 'array' : null; - } - - if ($use_subtypes) { - if ($type === 'number' && self::isMultipleOf($value, 1)) { - return 'integer'; - } - } elseif ($type === 'integer') { - return 'number'; - } - - return $type; - } - - /** - * @param string $type - * @param string|string[] $allowed - * @return bool - */ - public static function jsonTypeMatches(string $type, $allowed): bool - { - if (!$allowed) { - return false; - } - - if (is_string($allowed)) { - if ($type === $allowed) { - return true; - } - - return $allowed === self::getJsonSuperType($type); - } - - if (is_array($allowed)) { - if (in_array($type, $allowed, true)) { - return true; - } - - if ($type = self::getJsonSuperType($type)) { - return in_array($type, $allowed, true); - } - } - - return false; - } - - /** - * @param mixed $value - * @param string|string[] $type - * @return bool - */ - public static function valueIsOfJsonType($value, $type): bool - { - $t = self::getJsonType($value); - if ($t === null) { - return false; - } - - return self::jsonTypeMatches($t, $type); - } - - /** - * @param array $array - * @return bool - */ - public static function isIndexedArray(array $array): bool - { - for ($i = 0, $max = count($array); $i < $max; $i++) { - if (!array_key_exists($i, $array)) { - return false; - } - } - - return true; - } - - /** - * Converts assoc-arrays to objects (recursive) - * @param scalar|object|array|null $schema - * @return scalar|object|array|null - */ - public static function convertAssocArrayToObject($schema) - { - if (is_null($schema) || is_scalar($schema)) { - return $schema; - } - - $keepArray = is_array($schema) && self::isIndexedArray($schema); - - $data = []; - - foreach ($schema as $key => $value) { - $data[$key] = is_array($value) || is_object($value) ? self::convertAssocArrayToObject($value) : $value; - } - - return $keepArray ? $data : (object) $data; - } - - /** - * @param mixed $a - * @param mixed $b - * @return bool - */ - public static function equals($a, $b): bool - { - if ($a === $b) { - return true; - } - - $type = self::getJsonType($a, false); - if ($type === null || $type !== self::getJsonType($b, false)) { - return false; - } - - if ($type === 'number') { - return $a == $b; - } - - if ($type === "array") { - $count = count($a); - if ($count !== count($b)) { - return false; - } - - for ($i = 0; $i < $count; $i++) { - if (!array_key_exists($i, $a) || !array_key_exists($i, $b)) { - return false; - } - if (!self::equals($a[$i], $b[$i])) { - return false; - } - } - - return true; - } - - if ($type === "object") { - $a = get_object_vars($a); - if ($a === null) { - return false; - } - - $b = get_object_vars($b); - if ($b === null) { - return false; - } - - if (count($a) !== count($b)) { - return false; - } - - foreach ($a as $prop => $value) { - if (!array_key_exists($prop, $b)) { - return false; - } - if (!self::equals($value, $b[$prop])) { - return false; - } - } - - return true; - } - - return false; - } - - /** - * @param $number - * @param $divisor - * @param int $scale - * @return bool - */ - public static function isMultipleOf($number, $divisor, int $scale = 14): bool - { - static $bcMath = null; - if ($bcMath === null) { - $bcMath = extension_loaded('bcmath'); - } - if ($divisor == 0) { - return $number == 0; - } - - if ($bcMath) { - $number = number_format($number, $scale, '.', ''); - $divisor = number_format($divisor, $scale, '.', ''); - - /** @noinspection PhpComposerExtensionStubsInspection */ - $x = bcdiv($number, $divisor, 0); - /** @noinspection PhpComposerExtensionStubsInspection */ - $x = bcmul($divisor, $x, $scale); - /** @noinspection PhpComposerExtensionStubsInspection */ - $x = bcsub($number, $x, $scale); - - /** @noinspection PhpComposerExtensionStubsInspection */ - return 0 === bccomp($x, 0, $scale); - } - - $div = $number / $divisor; - - return $div == (int)$div; - } - - /** - * @param $value - * @return mixed - */ - public static function cloneValue($value) - { - if ($value === null || is_scalar($value)) { - return $value; - } - - if (is_array($value)) { - return array_map(self::class . '::cloneValue', $value); - } - - if (is_object($value)) { - return (object)array_map(self::class . '::cloneValue', get_object_vars($value)); - } - - return null; - } - - /** - * @param string $pattern - * @return bool - */ - public static function isValidPattern(string $pattern): bool - { - if (strpos($pattern, '\Z') !== false) { - return false; - } - - return @preg_match("\x07{$pattern}\x07u", '') !== false; - } - - /** - * @param string $pattern - * @return string - */ - public static function patternToRegex(string $pattern): string - { - return "\x07{$pattern}\x07uD"; - } - - /** - * @param mixed $data - * @return mixed - */ - public static function toJSON($data) - { - if ($data === null || is_scalar($data)) { - return $data; - } - - $map = []; - - $isArray = true; - $index = 0; - foreach ($data as $key => $value) { - $map[$key] = self::toJSON($value); - if ($isArray) { - if ($index !== $key) { - $isArray = false; - } else { - $index++; - } - } - } - - if ($isArray) { - if (!$map && is_object($data)) { - return (object) $map; - } - return $map; - } - - return (object) $map; - } +final class Helper { + + /** @var string[] */ + const JSON_TYPES = array( 'string', 'number', 'boolean', 'null', 'object', 'array' ); + + /** @var string[] */ + const JSON_SUBTYPES = array( 'integer' => 'number' ); + + /** @var string[] */ + const PHP_TYPE_MAP = array( + 'NULL' => 'null', + 'integer' => 'integer', + 'double' => 'number', + 'boolean' => 'boolean', + 'array' => 'array', + 'object' => 'object', + 'string' => 'string', + ); + + /** + * @param string $type + * @return bool + */ + public static function isValidJsonType( string $type ): bool { + if ( isset( self::JSON_SUBTYPES[ $type ] ) ) { + return true; + } + + return in_array( $type, self::JSON_TYPES, true ); + } + + /** + * @param string $type + * @return null|string + */ + public static function getJsonSuperType( string $type ) { + return self::JSON_SUBTYPES[ $type ] ?? null; + } + + /** + * @param mixed $value + * @param bool $use_subtypes + * @return null|string + */ + public static function getJsonType( $value, bool $use_subtypes = true ) { + $type = self::PHP_TYPE_MAP[ gettype( $value ) ] ?? null; + if ( $type === null ) { + return null; + } elseif ( $type === 'array' ) { + return self::isIndexedArray( $value ) ? 'array' : null; + } + + if ( $use_subtypes ) { + if ( $type === 'number' && self::isMultipleOf( $value, 1 ) ) { + return 'integer'; + } + } elseif ( $type === 'integer' ) { + return 'number'; + } + + return $type; + } + + /** + * @param string $type + * @param string|string[] $allowed + * @return bool + */ + public static function jsonTypeMatches( string $type, $allowed ): bool { + if ( ! $allowed ) { + return false; + } + + if ( is_string( $allowed ) ) { + if ( $type === $allowed ) { + return true; + } + + return $allowed === self::getJsonSuperType( $type ); + } + + if ( is_array( $allowed ) ) { + if ( in_array( $type, $allowed, true ) ) { + return true; + } + + if ( $type = self::getJsonSuperType( $type ) ) { + return in_array( $type, $allowed, true ); + } + } + + return false; + } + + /** + * @param mixed $value + * @param string|string[] $type + * @return bool + */ + public static function valueIsOfJsonType( $value, $type ): bool { + $t = self::getJsonType( $value ); + if ( $t === null ) { + return false; + } + + return self::jsonTypeMatches( $t, $type ); + } + + /** + * @param array $array + * @return bool + */ + public static function isIndexedArray( array $array ): bool { + for ( $i = 0, $max = count( $array ); $i < $max; $i++ ) { + if ( ! array_key_exists( $i, $array ) ) { + return false; + } + } + + return true; + } + + /** + * Converts assoc-arrays to objects (recursive) + * + * @param scalar|object|array|null $schema + * @return scalar|object|array|null + */ + public static function convertAssocArrayToObject( $schema ) { + if ( is_null( $schema ) || is_scalar( $schema ) ) { + return $schema; + } + + $keepArray = is_array( $schema ) && self::isIndexedArray( $schema ); + + $data = array(); + + foreach ( $schema as $key => $value ) { + $data[ $key ] = is_array( $value ) || is_object( $value ) ? self::convertAssocArrayToObject( $value ) : $value; + } + + return $keepArray ? $data : (object) $data; + } + + /** + * @param mixed $a + * @param mixed $b + * @return bool + */ + public static function equals( $a, $b ): bool { + if ( $a === $b ) { + return true; + } + + $type = self::getJsonType( $a, false ); + if ( $type === null || $type !== self::getJsonType( $b, false ) ) { + return false; + } + + if ( $type === 'number' ) { + return $a == $b; + } + + if ( $type === 'array' ) { + $count = count( $a ); + if ( $count !== count( $b ) ) { + return false; + } + + for ( $i = 0; $i < $count; $i++ ) { + if ( ! array_key_exists( $i, $a ) || ! array_key_exists( $i, $b ) ) { + return false; + } + if ( ! self::equals( $a[ $i ], $b[ $i ] ) ) { + return false; + } + } + + return true; + } + + if ( $type === 'object' ) { + $a = get_object_vars( $a ); + if ( $a === null ) { + return false; + } + + $b = get_object_vars( $b ); + if ( $b === null ) { + return false; + } + + if ( count( $a ) !== count( $b ) ) { + return false; + } + + foreach ( $a as $prop => $value ) { + if ( ! array_key_exists( $prop, $b ) ) { + return false; + } + if ( ! self::equals( $value, $b[ $prop ] ) ) { + return false; + } + } + + return true; + } + + return false; + } + + /** + * @param $number + * @param $divisor + * @param int $scale + * @return bool + */ + public static function isMultipleOf( $number, $divisor, int $scale = 14 ): bool { + static $bcMath = null; + if ( $bcMath === null ) { + $bcMath = extension_loaded( 'bcmath' ); + } + if ( $divisor == 0 ) { + return $number == 0; + } + + if ( $bcMath ) { + $number = number_format( $number, $scale, '.', '' ); + $divisor = number_format( $divisor, $scale, '.', '' ); + + /** @noinspection PhpComposerExtensionStubsInspection */ + $x = bcdiv( $number, $divisor, 0 ); + /** @noinspection PhpComposerExtensionStubsInspection */ + $x = bcmul( $divisor, $x, $scale ); + /** @noinspection PhpComposerExtensionStubsInspection */ + $x = bcsub( $number, $x, $scale ); + + /** @noinspection PhpComposerExtensionStubsInspection */ + return 0 === bccomp( $x, 0, $scale ); + } + + $div = $number / $divisor; + + return $div == (int) $div; + } + + /** + * @param $value + * @return mixed + */ + public static function cloneValue( $value ) { + if ( $value === null || is_scalar( $value ) ) { + return $value; + } + + if ( is_array( $value ) ) { + return array_map( self::class . '::cloneValue', $value ); + } + + if ( is_object( $value ) ) { + return (object) array_map( self::class . '::cloneValue', get_object_vars( $value ) ); + } + + return null; + } + + /** + * @param string $pattern + * @return bool + */ + public static function isValidPattern( string $pattern ): bool { + if ( strpos( $pattern, '\Z' ) !== false ) { + return false; + } + + return @preg_match( "\x07{$pattern}\x07u", '' ) !== false; + } + + /** + * @param string $pattern + * @return string + */ + public static function patternToRegex( string $pattern ): string { + return "\x07{$pattern}\x07uD"; + } + + /** + * @param mixed $data + * @return mixed + */ + public static function toJSON( $data ) { + if ( $data === null || is_scalar( $data ) ) { + return $data; + } + + $map = array(); + + $isArray = true; + $index = 0; + foreach ( $data as $key => $value ) { + $map[ $key ] = self::toJSON( $value ); + if ( $isArray ) { + if ( $index !== $key ) { + $isArray = false; + } else { + ++$index; + } + } + } + + if ( $isArray ) { + if ( ! $map && is_object( $data ) ) { + return (object) $map; + } + return $map; + } + + return (object) $map; + } } diff --git a/src/opis/json-schema/src/Info/DataInfo.php b/src/opis/json-schema/src/Info/DataInfo.php index d2560ab0..fb28b87c 100644 --- a/src/opis/json-schema/src/Info/DataInfo.php +++ b/src/opis/json-schema/src/Info/DataInfo.php @@ -1,5 +1,6 @@ value = $value; - $this->type = $type; - $this->root = $root; - $this->path = $path; - $this->parent = $parent; - } - - public function value() - { - return $this->value; - } - - public function type() - { - return $this->type; - } - - public function root() - { - return $this->root; - } - - /** - * @return int[]|string[] - */ - public function path(): array - { - return $this->path; - } - - public function parent() - { - return $this->parent; - } - - /** - * @return int[]|string[] - */ - public function fullPath(): array - { - if ($this->parent === null) { - return $this->path; - } - - if ($this->fullPath === null) { - $this->fullPath = array_merge($this->parent->fullPath(), $this->path); - } - - return $this->fullPath; - } - - /** - * @param ValidationContext $context - * @return static - */ - public static function fromContext($context): self - { - if ($parent = $context->parent()) { - $parent = self::fromContext($parent); - } - - return new self($context->currentData(), $context->currentDataType(), $context->rootData(), - $context->currentDataPath(), $parent); - } +class DataInfo { + + /** @var mixed */ + protected $value; + + /** + * @var string|null + */ + protected $type; + + /** @var mixed */ + protected $root; + + /** @var string[]|int[] */ + protected $path; + + /** + * @var \Opis\JsonSchema\Info\DataInfo|null + */ + protected $parent; + + /** @var string[]|int[]|null */ + protected $fullPath; + + /** + * DataInfo constructor. + * + * @param $value + * @param string|null $type + * @param $root + * @param string[]|int[] $path + * @param DataInfo|null $parent + */ + public function __construct( $value, $type, $root, array $path = array(), $parent = null ) { + $this->value = $value; + $this->type = $type; + $this->root = $root; + $this->path = $path; + $this->parent = $parent; + } + + public function value() { + return $this->value; + } + + public function type() { + return $this->type; + } + + public function root() { + return $this->root; + } + + /** + * @return int[]|string[] + */ + public function path(): array { + return $this->path; + } + + public function parent() { + return $this->parent; + } + + /** + * @return int[]|string[] + */ + public function fullPath(): array { + if ( $this->parent === null ) { + return $this->path; + } + + if ( $this->fullPath === null ) { + $this->fullPath = array_merge( $this->parent->fullPath(), $this->path ); + } + + return $this->fullPath; + } + + /** + * @param ValidationContext $context + * @return static + */ + public static function fromContext( $context ): self { + if ( $parent = $context->parent() ) { + $parent = self::fromContext( $parent ); + } + + return new self( + $context->currentData(), + $context->currentDataType(), + $context->rootData(), + $context->currentDataPath(), + $parent + ); + } } diff --git a/src/opis/json-schema/src/Info/SchemaInfo.php b/src/opis/json-schema/src/Info/SchemaInfo.php index fb381403..ec7a116e 100644 --- a/src/opis/json-schema/src/Info/SchemaInfo.php +++ b/src/opis/json-schema/src/Info/SchemaInfo.php @@ -1,5 +1,6 @@ data = $data; - $this->id = $id; - $this->root = $root; - $this->base = $base; - $this->path = $path; - $this->draft = $draft; - } - - public function id() - { - return $this->id; - } - - public function root() - { - return $this->root; - } - - public function base() - { - return $this->base; - } - - public function draft() - { - return $this->draft; - } - - public function data() - { - return $this->data; - } - - public function path(): array - { - return $this->path; - } - - /** - * Returns first non-null property: id, base or root - * @return Uri|null - */ - public function idBaseRoot() - { - return $this->id ?? $this->base ?? $this->root; - } - - public function isBoolean(): bool - { - return is_bool($this->data); - } - - public function isObject(): bool - { - return is_object($this->data); - } - - public function isDocumentRoot(): bool - { - return $this->id && !$this->root && !$this->base; - } +class SchemaInfo { + + /** @var bool|object */ + protected $data; + + /** + * @var \Opis\JsonSchema\Uri|null + */ + protected $id; + + /** + * @var \Opis\JsonSchema\Uri|null + */ + protected $root; + + /** + * @var \Opis\JsonSchema\Uri|null + */ + protected $base; + + /** @var string[]|int[] */ + protected $path; + + /** + * @var string|null + */ + protected $draft; + + /** + * @param object|bool $data + * @param Uri|null $id + * @param Uri|null $base + * @param Uri|null $root + * @param string[]|int[] $path + * @param string|null $draft + */ + public function __construct( $data, $id, $base = null, $root = null, array $path = array(), $draft = null ) { + if ( $root === $id || ( (string) $root === (string) $id ) ) { + $root = null; + } + + if ( $root === null ) { + $base = null; + } + + $this->data = $data; + $this->id = $id; + $this->root = $root; + $this->base = $base; + $this->path = $path; + $this->draft = $draft; + } + + public function id() { + return $this->id; + } + + public function root() { + return $this->root; + } + + public function base() { + return $this->base; + } + + public function draft() { + return $this->draft; + } + + public function data() { + return $this->data; + } + + public function path(): array { + return $this->path; + } + + /** + * Returns first non-null property: id, base or root + * + * @return Uri|null + */ + public function idBaseRoot() { + return $this->id ?? $this->base ?? $this->root; + } + + public function isBoolean(): bool { + return is_bool( $this->data ); + } + + public function isObject(): bool { + return is_object( $this->data ); + } + + public function isDocumentRoot(): bool { + return $this->id && ! $this->root && ! $this->base; + } } diff --git a/src/opis/json-schema/src/JsonPointer.php b/src/opis/json-schema/src/JsonPointer.php index f9baa545..d79e174a 100644 --- a/src/opis/json-schema/src/JsonPointer.php +++ b/src/opis/json-schema/src/JsonPointer.php @@ -1,5 +1,6 @@ 0|[1-9][0-9]*)(?(?:\+|-)(?:0|[1-9][0-9]*))?)?(?(?:/[^/#]*)*)(?#)?$~'; - - /** @var string */ - const UNESCAPED = '/~([^01]|$)/'; - - /** - * @var int - */ - protected $level = -1; - - /** - * @var int - */ - protected $shift = 0; - - /** - * @var bool - */ - protected $fragment = false; - - /** @var string[]|int[] */ - protected $path; - - /** - * @var string|null - */ - protected $str; - - final protected function __construct(array $path, int $level = -1, int $shift = 0, bool $fragment = false) - { - $this->path = $path; - $this->level = $level < 0 ? -1 : $level; - $this->shift = $shift; - $this->fragment = $level >= 0 && $fragment; - } - - public function isRelative(): bool - { - return $this->level >= 0; - } - - public function isAbsolute(): bool - { - return $this->level < 0; - } - - public function level(): int - { - return $this->level; - } - - public function shift(): int - { - return $this->shift; - } - - /** - * @return string[] - */ - public function path(): array - { - return $this->path; - } - - /** - * @return bool - */ - public function hasFragment(): bool - { - return $this->fragment; - } - - /** - * @return string - */ - public function __toString(): string - { - if ($this->str === null) { - if ($this->level >= 0) { - $this->str = (string)$this->level; - - if ($this->shift !== 0) { - if ($this->shift > 0) { - $this->str .= '+'; - } - $this->str .= $this->shift; - } - - if ($this->path) { - $this->str .= '/'; - $this->str .= implode('/', self::encodePath($this->path)); - } - - if ($this->fragment) { - $this->str .= '#'; - } - } else { - $this->str = '/'; - $this->str .= implode('/', self::encodePath($this->path)); - } - } - - return $this->str; - } - - /** - * @param $data - * @param array|null $path - * @param null $default - * @return mixed - */ - public function data($data, $path = null, $default = null) - { - if ($this->level < 0) { - return self::getData($data, $this->path, false, $default); - } - - if ($path !== null) { - $path = $this->absolutePath($path); - } - - if ($path === null) { - return $default; - } - - return self::getData($data, $path, $this->fragment, $default); - } - - /** - * @param array $path - * @return array|null - */ - public function absolutePath($path = []) - { - if ($this->level < 0) { - // Absolute pointer - return $this->path; - } - - if ($this->level === 0) { - if ($this->shift && !$this->handleShift($path)) { - return null; - } - return $this->path ? array_merge($path, $this->path) : $path; - } - - $count = count($path); - if ($count === $this->level) { - if ($this->shift) { - return null; - } - return $this->path; - } - - if ($count > $this->level) { - $count -= $this->level; - - /** @var array $path */ - $path = array_slice($path, 0, $count); - - if ($this->shift && !$this->handleShift($path, $count)) { - return null; - } - - return $this->path ? array_merge($path, $this->path) : $path; - } - - return null; - } - - /** - * @param mixed[] $path - * @param int|null $count - */ - protected function handleShift(&$path, $count = null): bool - { - if (!$path) { - return false; - } - - $count = $count ?? count($path); - - $last = $path[$count - 1]; - - if (is_string($last) && preg_match('/^[1-9]\d*$/', $last)) { - $last = (int) $last; - } - - if (!is_int($last)) { - return false; - } - - $path[$count - 1] = $last + $this->shift; - - return true; - } - - /** - * @param string $pointer - * @param bool $decode - */ - public static function parse($pointer, $decode = true) - { - if ($pointer === '' || !preg_match(self::PATTERN, $pointer, $m)) { - // Not a pointer - return null; - } - - $pointer = $m['pointer'] ?? null; - - // Check if the pointer is escaped - if ($decode && $pointer && preg_match(self::UNESCAPED, $pointer)) { - // Invalid pointer - return null; - } - - $level = isset($m['level']) && $m['level'] !== '' - ? (int)$m['level'] - : -1; - - $shift = 0; - if ($level >= 0 && isset($m['shift']) && $m['shift'] !== '') { - $shift = (int) $m['shift']; - } - - $fragment = isset($m['fragment']) && $m['fragment'] === '#'; - unset($m); - - if ($fragment && $level < 0) { - return null; - } - - if ($pointer === '') { - $pointer = null; - } elseif ($pointer !== null) { - // Remove leading slash - $pointer = substr($pointer, 1); - - if ($pointer !== '') { - $pointer = self::decodePath(explode('/', $pointer)); - } else { - $pointer = null; - } - } - - return new self($pointer ?? [], $level, $shift, $fragment); - } - - /** - * @param $data - * @param array|null $path - * @param bool $fragment - * @param null $default - * @return mixed - */ - public static function getData($data, $path = null, $fragment = false, $default = null) - { - if ($path === null) { - return $default; - } - - if (!$path) { - return $fragment ? $default : $data; - } - - if ($fragment) { - return end($path); - } - - foreach ($path as $key) { - if (is_array($data)) { - if (!array_key_exists($key, $data)) { - return $default; - } - $data = $data[$key]; - } elseif (is_object($data)) { - if (!property_exists($data, $key)) { - return $default; - } - $data = $data->{$key}; - } else { - return $default; - } - } - - return $data; - } - - /** - * @param string|string[] $path - * @return string|string[] - */ - public static function encodePath($path) - { - $path = str_replace('~', '~0', $path); - $path = str_replace('/', '~1', $path); - - if (is_array($path)) { - return array_map('rawurlencode', $path); - } - - return rawurlencode($path); - } - - /** - * @param string|string[] $path - * @return string|string[] - */ - public static function decodePath($path) - { - if (is_array($path)) { - $path = array_map('rawurldecode', $path); - } else { - $path = rawurldecode($path); - } - - $path = str_replace('~1', '/', $path); - $path = str_replace('~0', '~', $path); - - return $path; - } - - /** - * @param array $path - * @return string - */ - public static function pathToString($path): string - { - if (!$path) { - return '/'; - } - - return '/' . implode('/', self::encodePath($path)); - } - - /** - * @param array $path - * @return string - */ - public static function pathToFragment($path): string - { - if (!$path) { - return '#'; - } - - return '#/' . implode('/', self::encodePath($path)); - } - - /** - * @param string $pointer - * @return bool - */ - public static function isAbsolutePointer($pointer): bool - { - if ($pointer === '/') { - return true; - } - - if (!preg_match(self::PATTERN, $pointer, $m)) { - return false; - } - - if (isset($m['fragment']) || isset($m['level']) && $m['level'] !== '') { - return false; - } - - if (!isset($m['pointer']) || $m['pointer'] === '') { - return true; - } - - return !preg_match(self::UNESCAPED, $m['pointer']); - } - - /** - * @param string $pointer - * @return bool - */ - public static function isRelativePointer($pointer): bool - { - if ($pointer === '') { - return false; - } - - if (!preg_match(self::PATTERN, $pointer, $m)) { - return false; - } - - if (!isset($m['level']) || $m['level'] === '' || (int)$m['level'] < 0) { - return false; - } - - if (!isset($m['pointer']) || $m['pointer'] === '') { - return true; - } - - return !preg_match(self::UNESCAPED, $m['pointer']); - } - - /** - * @param mixed[] $path - */ - public static function createAbsolute($path): self - { - return new self($path, -1, 0, false); - } - - /** - * @param int $level - * @param mixed[] $path - * @param int $shift - * @param bool $fragment - */ - public static function createRelative($level, $path = [], $shift = 0, $fragment = false): self - { - return new self($path, $level, $shift, $fragment); - } +final class JsonPointer { + + /** @var string */ + const PATTERN = '~^(?:(?0|[1-9][0-9]*)(?(?:\+|-)(?:0|[1-9][0-9]*))?)?(?(?:/[^/#]*)*)(?#)?$~'; + + /** @var string */ + const UNESCAPED = '/~([^01]|$)/'; + + /** + * @var int + */ + protected $level = -1; + + /** + * @var int + */ + protected $shift = 0; + + /** + * @var bool + */ + protected $fragment = false; + + /** @var string[]|int[] */ + protected $path; + + /** + * @var string|null + */ + protected $str; + + final protected function __construct( array $path, int $level = -1, int $shift = 0, bool $fragment = false ) { + $this->path = $path; + $this->level = $level < 0 ? -1 : $level; + $this->shift = $shift; + $this->fragment = $level >= 0 && $fragment; + } + + public function isRelative(): bool { + return $this->level >= 0; + } + + public function isAbsolute(): bool { + return $this->level < 0; + } + + public function level(): int { + return $this->level; + } + + public function shift(): int { + return $this->shift; + } + + /** + * @return string[] + */ + public function path(): array { + return $this->path; + } + + /** + * @return bool + */ + public function hasFragment(): bool { + return $this->fragment; + } + + /** + * @return string + */ + public function __toString(): string { + if ( $this->str === null ) { + if ( $this->level >= 0 ) { + $this->str = (string) $this->level; + + if ( $this->shift !== 0 ) { + if ( $this->shift > 0 ) { + $this->str .= '+'; + } + $this->str .= $this->shift; + } + + if ( $this->path ) { + $this->str .= '/'; + $this->str .= implode( '/', self::encodePath( $this->path ) ); + } + + if ( $this->fragment ) { + $this->str .= '#'; + } + } else { + $this->str = '/'; + $this->str .= implode( '/', self::encodePath( $this->path ) ); + } + } + + return $this->str; + } + + /** + * @param $data + * @param array|null $path + * @param null $default + * @return mixed + */ + public function data( $data, $path = null, $default = null ) { + if ( $this->level < 0 ) { + return self::getData( $data, $this->path, false, $default ); + } + + if ( $path !== null ) { + $path = $this->absolutePath( $path ); + } + + if ( $path === null ) { + return $default; + } + + return self::getData( $data, $path, $this->fragment, $default ); + } + + /** + * @param array $path + * @return array|null + */ + public function absolutePath( $path = array() ) { + if ( $this->level < 0 ) { + // Absolute pointer + return $this->path; + } + + if ( $this->level === 0 ) { + if ( $this->shift && ! $this->handleShift( $path ) ) { + return null; + } + return $this->path ? array_merge( $path, $this->path ) : $path; + } + + $count = count( $path ); + if ( $count === $this->level ) { + if ( $this->shift ) { + return null; + } + return $this->path; + } + + if ( $count > $this->level ) { + $count -= $this->level; + + /** @var array $path */ + $path = array_slice( $path, 0, $count ); + + if ( $this->shift && ! $this->handleShift( $path, $count ) ) { + return null; + } + + return $this->path ? array_merge( $path, $this->path ) : $path; + } + + return null; + } + + /** + * @param mixed[] $path + * @param int|null $count + */ + protected function handleShift( &$path, $count = null ): bool { + if ( ! $path ) { + return false; + } + + $count = $count ?? count( $path ); + + $last = $path[ $count - 1 ]; + + if ( is_string( $last ) && preg_match( '/^[1-9]\d*$/', $last ) ) { + $last = (int) $last; + } + + if ( ! is_int( $last ) ) { + return false; + } + + $path[ $count - 1 ] = $last + $this->shift; + + return true; + } + + /** + * @param string $pointer + * @param bool $decode + */ + public static function parse( $pointer, $decode = true ) { + if ( $pointer === '' || ! preg_match( self::PATTERN, $pointer, $m ) ) { + // Not a pointer + return null; + } + + $pointer = $m['pointer'] ?? null; + + // Check if the pointer is escaped + if ( $decode && $pointer && preg_match( self::UNESCAPED, $pointer ) ) { + // Invalid pointer + return null; + } + + $level = isset( $m['level'] ) && $m['level'] !== '' + ? (int) $m['level'] + : -1; + + $shift = 0; + if ( $level >= 0 && isset( $m['shift'] ) && $m['shift'] !== '' ) { + $shift = (int) $m['shift']; + } + + $fragment = isset( $m['fragment'] ) && $m['fragment'] === '#'; + unset( $m ); + + if ( $fragment && $level < 0 ) { + return null; + } + + if ( $pointer === '' ) { + $pointer = null; + } elseif ( $pointer !== null ) { + // Remove leading slash + $pointer = substr( $pointer, 1 ); + + if ( $pointer !== '' ) { + $pointer = self::decodePath( explode( '/', $pointer ) ); + } else { + $pointer = null; + } + } + + return new self( $pointer ?? array(), $level, $shift, $fragment ); + } + + /** + * @param $data + * @param array|null $path + * @param bool $fragment + * @param null $default + * @return mixed + */ + public static function getData( $data, $path = null, $fragment = false, $default = null ) { + if ( $path === null ) { + return $default; + } + + if ( ! $path ) { + return $fragment ? $default : $data; + } + + if ( $fragment ) { + return end( $path ); + } + + foreach ( $path as $key ) { + if ( is_array( $data ) ) { + if ( ! array_key_exists( $key, $data ) ) { + return $default; + } + $data = $data[ $key ]; + } elseif ( is_object( $data ) ) { + if ( ! property_exists( $data, $key ) ) { + return $default; + } + $data = $data->{$key}; + } else { + return $default; + } + } + + return $data; + } + + /** + * @param string|string[] $path + * @return string|string[] + */ + public static function encodePath( $path ) { + $path = str_replace( '~', '~0', $path ); + $path = str_replace( '/', '~1', $path ); + + if ( is_array( $path ) ) { + return array_map( 'rawurlencode', $path ); + } + + return rawurlencode( $path ); + } + + /** + * @param string|string[] $path + * @return string|string[] + */ + public static function decodePath( $path ) { + if ( is_array( $path ) ) { + $path = array_map( 'rawurldecode', $path ); + } else { + $path = rawurldecode( $path ); + } + + $path = str_replace( '~1', '/', $path ); + $path = str_replace( '~0', '~', $path ); + + return $path; + } + + /** + * @param array $path + * @return string + */ + public static function pathToString( $path ): string { + if ( ! $path ) { + return '/'; + } + + return '/' . implode( '/', self::encodePath( $path ) ); + } + + /** + * @param array $path + * @return string + */ + public static function pathToFragment( $path ): string { + if ( ! $path ) { + return '#'; + } + + return '#/' . implode( '/', self::encodePath( $path ) ); + } + + /** + * @param string $pointer + * @return bool + */ + public static function isAbsolutePointer( $pointer ): bool { + if ( $pointer === '/' ) { + return true; + } + + if ( ! preg_match( self::PATTERN, $pointer, $m ) ) { + return false; + } + + if ( isset( $m['fragment'] ) || isset( $m['level'] ) && $m['level'] !== '' ) { + return false; + } + + if ( ! isset( $m['pointer'] ) || $m['pointer'] === '' ) { + return true; + } + + return ! preg_match( self::UNESCAPED, $m['pointer'] ); + } + + /** + * @param string $pointer + * @return bool + */ + public static function isRelativePointer( $pointer ): bool { + if ( $pointer === '' ) { + return false; + } + + if ( ! preg_match( self::PATTERN, $pointer, $m ) ) { + return false; + } + + if ( ! isset( $m['level'] ) || $m['level'] === '' || (int) $m['level'] < 0 ) { + return false; + } + + if ( ! isset( $m['pointer'] ) || $m['pointer'] === '' ) { + return true; + } + + return ! preg_match( self::UNESCAPED, $m['pointer'] ); + } + + /** + * @param mixed[] $path + */ + public static function createAbsolute( $path ): self { + return new self( $path, -1, 0, false ); + } + + /** + * @param int $level + * @param mixed[] $path + * @param int $shift + * @param bool $fragment + */ + public static function createRelative( $level, $path = array(), $shift = 0, $fragment = false ): self { + return new self( $path, $level, $shift, $fragment ); + } } diff --git a/src/opis/json-schema/src/Keyword.php b/src/opis/json-schema/src/Keyword.php index 3fd9acb0..3c63c39e 100644 --- a/src/opis/json-schema/src/Keyword.php +++ b/src/opis/json-schema/src/Keyword.php @@ -1,5 +1,6 @@ next; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\KeywordValidator|null $next - */ - public function setNext($next): KeywordValidator - { - $this->next = $next; - - return $this; - } +abstract class AbstractKeywordValidator implements KeywordValidator { + + + /** + * @var \Opis\JsonSchema\KeywordValidator|null + */ + protected $next; + + /** + * @inheritDoc + */ + public function next() { + return $this->next; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\KeywordValidator|null $next + */ + public function setNext( $next ): KeywordValidator { + $this->next = $next; + + return $this; + } } diff --git a/src/opis/json-schema/src/KeywordValidators/CallbackKeywordValidator.php b/src/opis/json-schema/src/KeywordValidators/CallbackKeywordValidator.php index 4d20d6ea..6e6703af 100644 --- a/src/opis/json-schema/src/KeywordValidators/CallbackKeywordValidator.php +++ b/src/opis/json-schema/src/KeywordValidators/CallbackKeywordValidator.php @@ -1,5 +1,6 @@ callback = $callback; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function validate($context) - { - return ($this->callback)($context); - } - - /** - * @inheritDoc - */ - public function next() - { - return null; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\KeywordValidator|null $next - */ - public function setNext($next): KeywordValidator - { - return $this; - } +final class CallbackKeywordValidator implements KeywordValidator { + + /** @var callable */ + private $callback; + + /** + * @param callable $callback + */ + public function __construct( callable $callback ) { + $this->callback = $callback; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function validate( $context ) { + return ( $this->callback )( $context ); + } + + /** + * @inheritDoc + */ + public function next() { + return null; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\KeywordValidator|null $next + */ + public function setNext( $next ): KeywordValidator { + return $this; + } } diff --git a/src/opis/json-schema/src/KeywordValidators/PragmaKeywordValidator.php b/src/opis/json-schema/src/KeywordValidators/PragmaKeywordValidator.php index 5613c92a..f0992b69 100644 --- a/src/opis/json-schema/src/KeywordValidators/PragmaKeywordValidator.php +++ b/src/opis/json-schema/src/KeywordValidators/PragmaKeywordValidator.php @@ -1,5 +1,6 @@ pragmas = $pragmas; - } + /** @var Pragma[] */ + protected $pragmas = array(); - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function validate($context) - { - if (!$this->next) { - return null; - } + /** + * @param Pragma[] $pragmas + */ + public function __construct( array $pragmas ) { + $this->pragmas = $pragmas; + } - if (!$this->pragmas) { - return $this->next->validate($context); - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function validate( $context ) { + if ( ! $this->next ) { + return null; + } - $data = []; + if ( ! $this->pragmas ) { + return $this->next->validate( $context ); + } - foreach ($this->pragmas as $key => $handler) { - $data[$key] = $handler->enter($context); - } + $data = array(); - $error = $this->next->validate($context); + foreach ( $this->pragmas as $key => $handler ) { + $data[ $key ] = $handler->enter( $context ); + } - foreach (array_reverse($this->pragmas, true) as $key => $handler) { - $handler->leave($context, $data[$key] ?? null); - } + $error = $this->next->validate( $context ); - return $error; - } + foreach ( array_reverse( $this->pragmas, true ) as $key => $handler ) { + $handler->leave( $context, $data[ $key ] ?? null ); + } + + return $error; + } } diff --git a/src/opis/json-schema/src/Keywords/AbstractRefKeyword.php b/src/opis/json-schema/src/Keywords/AbstractRefKeyword.php index 29ea1674..c6a35370 100644 --- a/src/opis/json-schema/src/Keywords/AbstractRefKeyword.php +++ b/src/opis/json-schema/src/Keywords/AbstractRefKeyword.php @@ -1,5 +1,6 @@ mapper = $mapper; - $this->globals = $globals; - $this->slots = $slots; - $this->keyword = $keyword; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($error = $this->doValidate($context, $schema)) { - $uri = $this->lastRefUri; - $this->lastRefUri = null; - - return $this->error($schema, $context, $this->keyword, 'The data must match {keyword}', [ - 'keyword' => $this->keyword, - 'uri' => (string) $uri, - ], $error); - } - - $this->lastRefUri = null; - - return null; - } - - - /** - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - abstract protected function doValidate($context, $schema); - - /** - * @param \Opis\JsonSchema\Uri|null $uri - */ - protected function setLastRefUri($uri) - { - $this->lastRefUri = $uri; - } - - /** - * @param \Opis\JsonSchema\Schema $schema - */ - protected function setLastRefSchema($schema) - { - $info = $schema->info(); - - if ($info->id()) { - $this->lastRefUri = $info->id(); - } else { - $this->lastRefUri = Uri::merge(JsonPointer::pathToFragment($info->path()), $info->idBaseRoot()); - } - } - - /** - * @param ValidationContext $context - * @param Schema $schema - * @return ValidationContext - */ - protected function createContext($context, $schema): ValidationContext - { - return $context->create($schema, $this->mapper, $this->globals, $this->slots); - } - - /** - * @param SchemaLoader $repo - * @param JsonPointer $pointer - * @param Uri $base - * @param array|null $path - * @return null|Schema - */ - protected function resolvePointer($repo, $pointer, - $base, $path = null) - { - if ($pointer->isAbsolute()) { - $path = (string)$pointer; - } else { - if ($pointer->hasFragment()) { - return null; - } - - $path = $path ? $pointer->absolutePath($path) : $pointer->path(); - if ($path === null) { - return null; - } - - $path = JsonPointer::pathToString($path); - } - - return $repo->loadSchemaById(Uri::merge('#' . $path, $base)); - } + JsonPointer, + Keyword, + Schema, + SchemaLoader, + Uri, + ValidationContext, + Variables}; + +abstract class AbstractRefKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var string + */ + protected $keyword; + /** + * @var \Opis\JsonSchema\Variables|null + */ + protected $mapper; + /** + * @var \Opis\JsonSchema\Variables|null + */ + protected $globals; + /** + * @var mixed[]|null + */ + protected $slots; + /** + * @var \Opis\JsonSchema\Uri|null + */ + protected $lastRefUri; + + /** + * @param Variables|null $mapper + * @param Variables|null $globals + * @param array|null $slots + * @param string $keyword + */ + protected function __construct( $mapper, $globals, $slots = null, string $keyword = '$ref' ) { + $this->mapper = $mapper; + $this->globals = $globals; + $this->slots = $slots; + $this->keyword = $keyword; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $error = $this->doValidate( $context, $schema ) ) { + $uri = $this->lastRefUri; + $this->lastRefUri = null; + + return $this->error( + $schema, + $context, + $this->keyword, + 'The data must match {keyword}', + array( + 'keyword' => $this->keyword, + 'uri' => (string) $uri, + ), + $error + ); + } + + $this->lastRefUri = null; + + return null; + } + + + /** + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + abstract protected function doValidate( $context, $schema ); + + /** + * @param \Opis\JsonSchema\Uri|null $uri + */ + protected function setLastRefUri( $uri ) { + $this->lastRefUri = $uri; + } + + /** + * @param \Opis\JsonSchema\Schema $schema + */ + protected function setLastRefSchema( $schema ) { + $info = $schema->info(); + + if ( $info->id() ) { + $this->lastRefUri = $info->id(); + } else { + $this->lastRefUri = Uri::merge( JsonPointer::pathToFragment( $info->path() ), $info->idBaseRoot() ); + } + } + + /** + * @param ValidationContext $context + * @param Schema $schema + * @return ValidationContext + */ + protected function createContext( $context, $schema ): ValidationContext { + return $context->create( $schema, $this->mapper, $this->globals, $this->slots ); + } + + /** + * @param SchemaLoader $repo + * @param JsonPointer $pointer + * @param Uri $base + * @param array|null $path + * @return null|Schema + */ + protected function resolvePointer( + $repo, + $pointer, + $base, + $path = null + ) { + if ( $pointer->isAbsolute() ) { + $path = (string) $pointer; + } else { + if ( $pointer->hasFragment() ) { + return null; + } + + $path = $path ? $pointer->absolutePath( $path ) : $pointer->path(); + if ( $path === null ) { + return null; + } + + $path = JsonPointer::pathToString( $path ); + } + + return $repo->loadSchemaById( Uri::merge( '#' . $path, $base ) ); + } } diff --git a/src/opis/json-schema/src/Keywords/AdditionalItemsKeyword.php b/src/opis/json-schema/src/Keywords/AdditionalItemsKeyword.php index a3422b29..adb90b99 100644 --- a/src/opis/json-schema/src/Keywords/AdditionalItemsKeyword.php +++ b/src/opis/json-schema/src/Keywords/AdditionalItemsKeyword.php @@ -1,5 +1,6 @@ value = $value; - $this->index = $startIndex; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($this->value === true) { - $context->markAllAsEvaluatedItems(); - return null; - } - - $data = $context->currentData(); - $count = count($data); - - if ($this->index >= $count) { - return null; - } - - if ($this->value === false) { - return $this->error($schema, $context, 'additionalItems', 'Array should not have additional items', [ - 'index' => $this->index, - ]); - } - - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } - - $object = $this->createArrayObject($context); - - $error = $this->validateIterableData($schema, $this->value, $context, $this->indexes($this->index, $count), - 'additionalItems', 'All additional array items must match schema', [], $object); - - if ($object && $object->count()) { - $context->addEvaluatedItems($object->getArrayCopy()); - } - - return $error; - } - - /** - * @param int $start - * @param int $max - * @return iterable|int[] - */ - protected function indexes($start, $max) - { - for ($i = $start; $i < $max; $i++) { - yield $i; - } - } +class AdditionalItemsKeyword implements Keyword { + + use OfTrait; + use IterableDataValidationTrait; + + /** @var bool|object|Schema */ + protected $value; + + /** + * @var int + */ + protected $index; + + /** + * @param bool|object $value + * @param int $startIndex + */ + public function __construct( $value, int $startIndex ) { + $this->value = $value; + $this->index = $startIndex; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $this->value === true ) { + $context->markAllAsEvaluatedItems(); + return null; + } + + $data = $context->currentData(); + $count = count( $data ); + + if ( $this->index >= $count ) { + return null; + } + + if ( $this->value === false ) { + return $this->error( + $schema, + $context, + 'additionalItems', + 'Array should not have additional items', + array( + 'index' => $this->index, + ) + ); + } + + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } + + $object = $this->createArrayObject( $context ); + + $error = $this->validateIterableData( + $schema, + $this->value, + $context, + $this->indexes( $this->index, $count ), + 'additionalItems', + 'All additional array items must match schema', + array(), + $object + ); + + if ( $object && $object->count() ) { + $context->addEvaluatedItems( $object->getArrayCopy() ); + } + + return $error; + } + + /** + * @param int $start + * @param int $max + * @return iterable|int[] + */ + protected function indexes( $start, $max ) { + for ( $i = $start; $i < $max; $i++ ) { + yield $i; + } + } } diff --git a/src/opis/json-schema/src/Keywords/AdditionalPropertiesKeyword.php b/src/opis/json-schema/src/Keywords/AdditionalPropertiesKeyword.php index 71b7db6f..b2a1a4c1 100644 --- a/src/opis/json-schema/src/Keywords/AdditionalPropertiesKeyword.php +++ b/src/opis/json-schema/src/Keywords/AdditionalPropertiesKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var bool|object|Schema */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($this->value === true) { - $context->markAllAsEvaluatedProperties(); - return null; - } + /** + * @param bool|object|Schema $value + */ + public function __construct( $value ) { + $this->value = $value; + } - $props = $context->getUncheckedProperties(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $this->value === true ) { + $context->markAllAsEvaluatedProperties(); + return null; + } - if (!$props) { - return null; - } + $props = $context->getUncheckedProperties(); - if ($this->value === false) { - return $this->error($schema, $context, - 'additionalProperties', 'Additional object properties are not allowed: {properties}', [ - 'properties' => $props - ]); - } + if ( ! $props ) { + return null; + } - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } + if ( $this->value === false ) { + return $this->error( + $schema, + $context, + 'additionalProperties', + 'Additional object properties are not allowed: {properties}', + array( + 'properties' => $props, + ) + ); + } - $object = $this->createArrayObject($context); + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } - $error = $this->validateIterableData($schema, $this->value, $context, $props, - 'additionalProperties', 'All additional object properties must match schema: {properties}', [ - 'properties' => $props - ], $object); + $object = $this->createArrayObject( $context ); - if ($object && $object->count()) { - $context->addEvaluatedProperties($object->getArrayCopy()); - } + $error = $this->validateIterableData( + $schema, + $this->value, + $context, + $props, + 'additionalProperties', + 'All additional object properties must match schema: {properties}', + array( + 'properties' => $props, + ), + $object + ); - return $error; - } + if ( $object && $object->count() ) { + $context->addEvaluatedProperties( $object->getArrayCopy() ); + } + + return $error; + } } diff --git a/src/opis/json-schema/src/Keywords/AllOfKeyword.php b/src/opis/json-schema/src/Keywords/AllOfKeyword.php index 298d6405..10fcec1f 100644 --- a/src/opis/json-schema/src/Keywords/AllOfKeyword.php +++ b/src/opis/json-schema/src/Keywords/AllOfKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var bool[]|object[] */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $object = $this->createArrayObject($context); + /** + * @param bool[]|object[] $value + */ + public function __construct( array $value ) { + $this->value = $value; + } - foreach ($this->value as $index => $value) { - if ($value === true) { - continue; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $object = $this->createArrayObject( $context ); - if ($value === false) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'allOf', 'The data should match all schemas', [ - 'index' => $index, - ]); - } + foreach ( $this->value as $index => $value ) { + if ( $value === true ) { + continue; + } - if (is_object($value) && !($value instanceof Schema)) { - $value = $this->value[$index] = $context->loader()->loadObjectSchema($value); - } + if ( $value === false ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'allOf', + 'The data should match all schemas', + array( + 'index' => $index, + ) + ); + } - if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'allOf', 'The data should match all schemas', [ - 'index' => $index, - ], $error); - } - } + if ( is_object( $value ) && ! ( $value instanceof Schema ) ) { + $value = $this->value[ $index ] = $context->loader()->loadObjectSchema( $value ); + } - $this->addEvaluatedFromArrayObject($object, $context); + if ( $error = $context->validateSchemaWithoutEvaluated( $value, null, false, $object ) ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'allOf', + 'The data should match all schemas', + array( + 'index' => $index, + ), + $error + ); + } + } - return null; - } + $this->addEvaluatedFromArrayObject( $object, $context ); + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/AnyOfKeyword.php b/src/opis/json-schema/src/Keywords/AnyOfKeyword.php index 855594e6..c207a136 100644 --- a/src/opis/json-schema/src/Keywords/AnyOfKeyword.php +++ b/src/opis/json-schema/src/Keywords/AnyOfKeyword.php @@ -1,5 +1,6 @@ value = $value; - $this->alwaysValid = $alwaysValid; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $object = $this->createArrayObject($context); - if ($this->alwaysValid && !$object) { - return null; - } - - $errors = []; - $ok = false; - - foreach ($this->value as $index => $value) { - if ($value === true) { - $ok = true; - if ($object) { - continue; - } - return null; - } - - if ($value === false) { - continue; - } - - if (is_object($value) && !($value instanceof Schema)) { - $value = $this->value[$index] = $context->loader()->loadObjectSchema($value); - } - - if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) { - $errors[] = $error; - continue; - } - - if (!$object) { - return null; - } - $ok = true; - } - - $this->addEvaluatedFromArrayObject($object, $context); - - if ($ok) { - return null; - } - - return $this->error($schema, $context, 'anyOf', 'The data should match at least one schema', [], $errors); - } +class AnyOfKeyword implements Keyword { + + use OfTrait; + use ErrorTrait; + + /** @var bool[]|object[] */ + protected $value; + /** + * @var bool + */ + protected $alwaysValid; + + /** + * @param bool[]|object[] $value + */ + public function __construct( array $value, bool $alwaysValid = false ) { + $this->value = $value; + $this->alwaysValid = $alwaysValid; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $object = $this->createArrayObject( $context ); + if ( $this->alwaysValid && ! $object ) { + return null; + } + + $errors = array(); + $ok = false; + + foreach ( $this->value as $index => $value ) { + if ( $value === true ) { + $ok = true; + if ( $object ) { + continue; + } + return null; + } + + if ( $value === false ) { + continue; + } + + if ( is_object( $value ) && ! ( $value instanceof Schema ) ) { + $value = $this->value[ $index ] = $context->loader()->loadObjectSchema( $value ); + } + + if ( $error = $context->validateSchemaWithoutEvaluated( $value, null, false, $object ) ) { + $errors[] = $error; + continue; + } + + if ( ! $object ) { + return null; + } + $ok = true; + } + + $this->addEvaluatedFromArrayObject( $object, $context ); + + if ( $ok ) { + return null; + } + + return $this->error( $schema, $context, 'anyOf', 'The data should match at least one schema', array(), $errors ); + } } diff --git a/src/opis/json-schema/src/Keywords/ConstDataKeyword.php b/src/opis/json-schema/src/Keywords/ConstDataKeyword.php index 3e6b8f11..d391c9c4 100644 --- a/src/opis/json-schema/src/Keywords/ConstDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/ConstDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(null); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $value = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - if ($value === $this) { - return $this->error($schema, $context, 'const', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->const = $value; - $ret = parent::validate($context, $schema); - $this->const = null; - - return $ret; - } +class ConstDataKeyword extends ConstKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( null ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $value = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + if ( $value === $this ) { + return $this->error( + $schema, + $context, + 'const', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->const = $value; + $ret = parent::validate( $context, $schema ); + $this->const = null; + + return $ret; + } } diff --git a/src/opis/json-schema/src/Keywords/ConstKeyword.php b/src/opis/json-schema/src/Keywords/ConstKeyword.php index 7cf7ff18..8e6b6eea 100644 --- a/src/opis/json-schema/src/Keywords/ConstKeyword.php +++ b/src/opis/json-schema/src/Keywords/ConstKeyword.php @@ -1,5 +1,6 @@ const = $const; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if (Helper::equals($this->const, $context->currentData())) { - return null; - } - - return $this->error($schema, $context, 'const', 'The data must must match the const value', [ - 'const' => $this->const - ]); - } +class ConstKeyword implements Keyword { + + use ErrorTrait; + + /** @var mixed */ + protected $const; + + /** + * @param $const + */ + public function __construct( $const ) { + $this->const = $const; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( Helper::equals( $this->const, $context->currentData() ) ) { + return null; + } + + return $this->error( + $schema, + $context, + 'const', + 'The data must must match the const value', + array( + 'const' => $this->const, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/ContainsKeyword.php b/src/opis/json-schema/src/Keywords/ContainsKeyword.php index 76223426..c8e2a85f 100644 --- a/src/opis/json-schema/src/Keywords/ContainsKeyword.php +++ b/src/opis/json-schema/src/Keywords/ContainsKeyword.php @@ -1,5 +1,6 @@ value = $value; - $this->min = $min; - $this->max = $max; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $data = $context->currentData(); - $count = count($data); - - $context->markAllAsEvaluatedItems(); - - if ($this->min > $count) { - return $this->error($schema, $context, 'minContains', 'Array must have at least {min} items', [ - 'min' => $this->min, - 'count' => $count, - ]); - } - - $isMaxNull = $this->max === null; - - if ($this->value === true) { - if ($count) { - if (!$isMaxNull && $count > $this->max) { - return $this->error($schema, $context, 'maxContains', 'Array must have at most {max} items', [ - 'max' => $this->max, - 'count' => $count, - ]); - } - return null; - } - - return $this->error($schema, $context, 'contains', 'Array must not be empty'); - } - - if ($this->value === false) { - return $this->error($schema, $context, 'contains', 'Any array is invalid'); - } - - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } - - $errors = []; - $valid = 0; - - $isMinNull = $this->min === null; - - if ($isMaxNull && $isMinNull) { - foreach ($data as $key => $item) { - $context->pushDataPath($key); - $error = $this->value->validate($context); - $context->popDataPath(); - if ($error) { - $errors[] = $error; - } else { - return null; - } - } - - return $this->error($schema, $context, 'contains', 'At least one array item must match schema', [], - $errors); - } - - foreach ($data as $key => $item) { - $context->pushDataPath($key); - $error = $this->value->validate($context); - $context->popDataPath(); - - if ($error) { - $errors[] = $error; - } else { - $valid++; - } - } - - if (!$isMinNull && $valid < $this->min) { - return $this->error($schema, $context, 'minContains', 'At least {min} array items must match schema', [ - 'min' => $this->min, - 'count' => $valid, - ]); - } - - if (!$isMaxNull && $valid > $this->max) { - return $this->error($schema, $context, 'maxContains', 'At most {max} array items must match schema', [ - 'max' => $this->max, - 'count' => $valid, - ]); - } - - if ($valid) { - return null; - } - - return $this->error($schema, $context, 'contains', 'At least one array item must match schema', [], - $errors); - } +class ContainsKeyword implements Keyword { + + use ErrorTrait; + + /** @var bool|object */ + protected $value; + /** + * @var int|null + */ + protected $min; + /** + * @var int|null + */ + protected $max; + + /** + * @param bool|object $value + * @param int|null $min + * @param int|null $max + */ + public function __construct( $value, $min = null, $max = null ) { + $this->value = $value; + $this->min = $min; + $this->max = $max; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $data = $context->currentData(); + $count = count( $data ); + + $context->markAllAsEvaluatedItems(); + + if ( $this->min > $count ) { + return $this->error( + $schema, + $context, + 'minContains', + 'Array must have at least {min} items', + array( + 'min' => $this->min, + 'count' => $count, + ) + ); + } + + $isMaxNull = $this->max === null; + + if ( $this->value === true ) { + if ( $count ) { + if ( ! $isMaxNull && $count > $this->max ) { + return $this->error( + $schema, + $context, + 'maxContains', + 'Array must have at most {max} items', + array( + 'max' => $this->max, + 'count' => $count, + ) + ); + } + return null; + } + + return $this->error( $schema, $context, 'contains', 'Array must not be empty' ); + } + + if ( $this->value === false ) { + return $this->error( $schema, $context, 'contains', 'Any array is invalid' ); + } + + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } + + $errors = array(); + $valid = 0; + + $isMinNull = $this->min === null; + + if ( $isMaxNull && $isMinNull ) { + foreach ( $data as $key => $item ) { + $context->pushDataPath( $key ); + $error = $this->value->validate( $context ); + $context->popDataPath(); + if ( $error ) { + $errors[] = $error; + } else { + return null; + } + } + + return $this->error( + $schema, + $context, + 'contains', + 'At least one array item must match schema', + array(), + $errors + ); + } + + foreach ( $data as $key => $item ) { + $context->pushDataPath( $key ); + $error = $this->value->validate( $context ); + $context->popDataPath(); + + if ( $error ) { + $errors[] = $error; + } else { + ++$valid; + } + } + + if ( ! $isMinNull && $valid < $this->min ) { + return $this->error( + $schema, + $context, + 'minContains', + 'At least {min} array items must match schema', + array( + 'min' => $this->min, + 'count' => $valid, + ) + ); + } + + if ( ! $isMaxNull && $valid > $this->max ) { + return $this->error( + $schema, + $context, + 'maxContains', + 'At most {max} array items must match schema', + array( + 'max' => $this->max, + 'count' => $valid, + ) + ); + } + + if ( $valid ) { + return null; + } + + return $this->error( + $schema, + $context, + 'contains', + 'At least one array item must match schema', + array(), + $errors + ); + } } diff --git a/src/opis/json-schema/src/Keywords/ContentEncodingKeyword.php b/src/opis/json-schema/src/Keywords/ContentEncodingKeyword.php index ca74a628..94e6dd73 100644 --- a/src/opis/json-schema/src/Keywords/ContentEncodingKeyword.php +++ b/src/opis/json-schema/src/Keywords/ContentEncodingKeyword.php @@ -1,5 +1,6 @@ name = $name; - $this->resolver = $resolver; - } + /** @var bool|null|callable|ContentEncoding */ + protected $encoding = false; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if (!$this->resolver) { - return null; - } + /** + * @param string $name + * @param null|ContentEncodingResolver $resolver + */ + public function __construct( string $name, $resolver = null ) { + $this->name = $name; + $this->resolver = $resolver; + } - if ($this->encoding === false) { - $this->encoding = $this->resolver->resolve($this->name); - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( ! $this->resolver ) { + return null; + } - if ($this->encoding === null) { - throw new UnresolvedContentEncodingException($this->name, $schema, $context); - } + if ( $this->encoding === false ) { + $this->encoding = $this->resolver->resolve( $this->name ); + } - $result = $this->encoding instanceof ContentEncoding - ? $this->encoding->decode($context->currentData(), $this->name) - : ($this->encoding)($context->currentData(), $this->name); + if ( $this->encoding === null ) { + throw new UnresolvedContentEncodingException( $this->name, $schema, $context ); + } - if ($result === null) { - return $this->error($schema, $context, 'contentEncoding', "The value must be encoded as '{encoding}'", [ - 'encoding' => $this->name, - ]); - } + $result = $this->encoding instanceof ContentEncoding + ? $this->encoding->decode( $context->currentData(), $this->name ) + : ( $this->encoding )( $context->currentData(), $this->name ); - $context->setDecodedContent($result); + if ( $result === null ) { + return $this->error( + $schema, + $context, + 'contentEncoding', + "The value must be encoded as '{encoding}'", + array( + 'encoding' => $this->name, + ) + ); + } - return null; - } + $context->setDecodedContent( $result ); + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/ContentMediaTypeKeyword.php b/src/opis/json-schema/src/Keywords/ContentMediaTypeKeyword.php index 411e75b7..12e169b8 100644 --- a/src/opis/json-schema/src/Keywords/ContentMediaTypeKeyword.php +++ b/src/opis/json-schema/src/Keywords/ContentMediaTypeKeyword.php @@ -1,5 +1,6 @@ name = $name; - $this->resolver = $resolver; - } + /** + * @var \Opis\JsonSchema\Resolvers\ContentMediaTypeResolver|null + */ + protected $resolver; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if (!$this->resolver) { - return null; - } + /** + * @param string $name + * @param null|ContentMediaTypeResolver $resolver + */ + public function __construct( string $name, $resolver ) { + $this->name = $name; + $this->resolver = $resolver; + } - if ($this->media === false) { - $this->media = $this->resolver->resolve($this->name); - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( ! $this->resolver ) { + return null; + } - if ($this->media === null) { - throw new UnresolvedContentMediaTypeException($this->name, $schema, $context); - } + if ( $this->media === false ) { + $this->media = $this->resolver->resolve( $this->name ); + } - $data = $context->getDecodedContent(); + if ( $this->media === null ) { + throw new UnresolvedContentMediaTypeException( $this->name, $schema, $context ); + } - $ok = $this->media instanceof ContentMediaType - ? $this->media->validate($data, $this->name) - : ($this->media)($data, $this->name); - if ($ok) { - return null; - } + $data = $context->getDecodedContent(); - unset($data); + $ok = $this->media instanceof ContentMediaType + ? $this->media->validate( $data, $this->name ) + : ( $this->media )( $data, $this->name ); + if ( $ok ) { + return null; + } - return $this->error($schema, $context, 'contentMediaType', "The media type of the data must be '{media}'", [ - 'media' => $this->name, - ]); - } + unset( $data ); + + return $this->error( + $schema, + $context, + 'contentMediaType', + "The media type of the data must be '{media}'", + array( + 'media' => $this->name, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/ContentSchemaKeyword.php b/src/opis/json-schema/src/Keywords/ContentSchemaKeyword.php index 2ee3abea..bed6ec24 100644 --- a/src/opis/json-schema/src/Keywords/ContentSchemaKeyword.php +++ b/src/opis/json-schema/src/Keywords/ContentSchemaKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var bool|object */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $data = json_decode($context->getDecodedContent(), false); + /** + * @param object $value + */ + public function __construct( $value ) { + $this->value = $value; + } - if ($error = json_last_error() !== JSON_ERROR_NONE) { - $message = json_last_error_msg(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $data = json_decode( $context->getDecodedContent(), false ); - return $this->error($schema, $context, 'contentSchema', "Invalid JSON content: {message}", [ - 'error' => $error, - 'message' => $message, - ]); - } + if ( $error = json_last_error() !== JSON_ERROR_NONE ) { + $message = json_last_error_msg(); - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } + return $this->error( + $schema, + $context, + 'contentSchema', + 'Invalid JSON content: {message}', + array( + 'error' => $error, + 'message' => $message, + ) + ); + } - if ($error = $this->value->validate($context->newInstance($data, $schema))) { - return $this->error($schema, $context, 'contentSchema', "The JSON content must match schema", [], $error); - } + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } - return null; - } + if ( $error = $this->value->validate( $context->newInstance( $data, $schema ) ) ) { + return $this->error( $schema, $context, 'contentSchema', 'The JSON content must match schema', array(), $error ); + } + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/DefaultKeyword.php b/src/opis/json-schema/src/Keywords/DefaultKeyword.php index 85cf6397..b6a8ce77 100644 --- a/src/opis/json-schema/src/Keywords/DefaultKeyword.php +++ b/src/opis/json-schema/src/Keywords/DefaultKeyword.php @@ -1,5 +1,6 @@ defaults = $defaults; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $data = $context->currentData(); - - if (is_object($data)) { - foreach ($this->defaults as $name => $value) { - if (!property_exists($data, $name)) { - $data->{$name} = Helper::cloneValue($value); - } - } - } - - return null; - } +class DefaultKeyword implements Keyword { + + + /** + * @var mixed[] + */ + protected $defaults; + + /** + * @param array $defaults + */ + public function __construct( array $defaults ) { + $this->defaults = $defaults; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $data = $context->currentData(); + + if ( is_object( $data ) ) { + foreach ( $this->defaults as $name => $value ) { + if ( ! property_exists( $data, $name ) ) { + $data->{$name} = Helper::cloneValue( $value ); + } + } + } + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/DependenciesKeyword.php b/src/opis/json-schema/src/Keywords/DependenciesKeyword.php index 82bc778b..98741b19 100644 --- a/src/opis/json-schema/src/Keywords/DependenciesKeyword.php +++ b/src/opis/json-schema/src/Keywords/DependenciesKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var array|object[]|string[][] */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $data = $context->currentData(); - $object = $this->createArrayObject($context); + /** + * @param object[]|string[][] $value + */ + public function __construct( array $value ) { + $this->value = $value; + } - foreach ($this->value as $name => $value) { - if ($value === true || !property_exists($data, $name)) { - continue; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $data = $context->currentData(); + $object = $this->createArrayObject( $context ); - if ($value === false) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'dependencies', "Property '{property}' is not allowed", [ - 'property' => $name, - ]); - } + foreach ( $this->value as $name => $value ) { + if ( $value === true || ! property_exists( $data, $name ) ) { + continue; + } - if (is_array($value)) { - foreach ($value as $prop) { - if (!property_exists($data, $prop)) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'dependencies', - "Property '{missing}' property is required by property '{property}'", [ - 'property' => $name, - 'missing' => $prop, - ]); - } - } + if ( $value === false ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'dependencies', + "Property '{property}' is not allowed", + array( + 'property' => $name, + ) + ); + } - continue; - } + if ( is_array( $value ) ) { + foreach ( $value as $prop ) { + if ( ! property_exists( $data, $prop ) ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'dependencies', + "Property '{missing}' property is required by property '{property}'", + array( + 'property' => $name, + 'missing' => $prop, + ) + ); + } + } - if (is_object($value) && !($value instanceof Schema)) { - $value = $this->value[$name] = $context->loader()->loadObjectSchema($value); - } + continue; + } - if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'dependencies', - "The object must match dependency schema defined on property '{property}'", [ - 'property' => $name, - ], $error); - } - } + if ( is_object( $value ) && ! ( $value instanceof Schema ) ) { + $value = $this->value[ $name ] = $context->loader()->loadObjectSchema( $value ); + } - $this->addEvaluatedFromArrayObject($object, $context); + if ( $error = $context->validateSchemaWithoutEvaluated( $value, null, false, $object ) ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'dependencies', + "The object must match dependency schema defined on property '{property}'", + array( + 'property' => $name, + ), + $error + ); + } + } - return null; - } + $this->addEvaluatedFromArrayObject( $object, $context ); + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/DependentRequiredKeyword.php b/src/opis/json-schema/src/Keywords/DependentRequiredKeyword.php index 0b64822b..d3a02f51 100644 --- a/src/opis/json-schema/src/Keywords/DependentRequiredKeyword.php +++ b/src/opis/json-schema/src/Keywords/DependentRequiredKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var string[][] */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $data = $context->currentData(); + /** + * @param string[][] $value + */ + public function __construct( array $value ) { + $this->value = $value; + } - foreach ($this->value as $name => $value) { - if (!property_exists($data, $name)) { - continue; - } - foreach ($value as $prop) { - if (!property_exists($data, $prop)) { - return $this->error($schema, $context, 'dependentRequired', - "'{$prop}' property is required by '{$name}' property", [ - 'property' => $name, - 'missing' => $prop, - ]); - } - } - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $data = $context->currentData(); - return null; - } + foreach ( $this->value as $name => $value ) { + if ( ! property_exists( $data, $name ) ) { + continue; + } + foreach ( $value as $prop ) { + if ( ! property_exists( $data, $prop ) ) { + return $this->error( + $schema, + $context, + 'dependentRequired', + "'{$prop}' property is required by '{$name}' property", + array( + 'property' => $name, + 'missing' => $prop, + ) + ); + } + } + } + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/DependentSchemasKeyword.php b/src/opis/json-schema/src/Keywords/DependentSchemasKeyword.php index 4d44b23b..caf0c042 100644 --- a/src/opis/json-schema/src/Keywords/DependentSchemasKeyword.php +++ b/src/opis/json-schema/src/Keywords/DependentSchemasKeyword.php @@ -1,5 +1,6 @@ value = (array)$value; - } + /** + * @var mixed[] + */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $data = $context->currentData(); - $object = $this->createArrayObject($context); + /** + * @param object $value + */ + public function __construct( $value ) { + $this->value = (array) $value; + } - foreach ($this->value as $name => $value) { - if ($value === true || !property_exists($data, $name)) { - continue; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $data = $context->currentData(); + $object = $this->createArrayObject( $context ); - if ($value === false) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'dependentSchemas', "'{$name}' property is not allowed", [ - 'property' => $name, - ]); - } + foreach ( $this->value as $name => $value ) { + if ( $value === true || ! property_exists( $data, $name ) ) { + continue; + } - if (is_object($value) && !($value instanceof Schema)) { - $value = $this->value[$name] = $context->loader()->loadObjectSchema($value); - } + if ( $value === false ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'dependentSchemas', + "'{$name}' property is not allowed", + array( + 'property' => $name, + ) + ); + } - if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'dependentSchemas', - "The object must match dependency schema defined on property '{$name}'", [ - 'property' => $name, - ], $error); - } - } + if ( is_object( $value ) && ! ( $value instanceof Schema ) ) { + $value = $this->value[ $name ] = $context->loader()->loadObjectSchema( $value ); + } - $this->addEvaluatedFromArrayObject($object, $context); + if ( $error = $context->validateSchemaWithoutEvaluated( $value, null, false, $object ) ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'dependentSchemas', + "The object must match dependency schema defined on property '{$name}'", + array( + 'property' => $name, + ), + $error + ); + } + } - return null; - } + $this->addEvaluatedFromArrayObject( $object, $context ); + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/EnumDataKeyword.php b/src/opis/json-schema/src/Keywords/EnumDataKeyword.php index e5ec2e89..3a4375d5 100644 --- a/src/opis/json-schema/src/Keywords/EnumDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/EnumDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct([]); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $value = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - if ($value === $this || !is_array($value) || empty($value)) { - return $this->error($schema, $context, 'enum', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->enum = $this->listByType($value); - $ret = parent::validate($context, $schema); - $this->enum = null; - - return $ret; - } +class EnumDataKeyword extends EnumKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( array() ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $value = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + if ( $value === $this || ! is_array( $value ) || empty( $value ) ) { + return $this->error( + $schema, + $context, + 'enum', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->enum = $this->listByType( $value ); + $ret = parent::validate( $context, $schema ); + $this->enum = null; + + return $ret; + } } diff --git a/src/opis/json-schema/src/Keywords/EnumKeyword.php b/src/opis/json-schema/src/Keywords/EnumKeyword.php index c182cdce..1bfd9327 100644 --- a/src/opis/json-schema/src/Keywords/EnumKeyword.php +++ b/src/opis/json-schema/src/Keywords/EnumKeyword.php @@ -1,5 +1,6 @@ enum = $this->listByType($enum); - } + /** + * @var mixed[]|null + */ + protected $enum; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $type = $context->currentDataType(); - $data = $context->currentData(); + /** + * @param array $enum + */ + public function __construct( array $enum ) { + $this->enum = $this->listByType( $enum ); + } - if (isset($this->enum[$type])) { - foreach ($this->enum[$type] as $value) { - if (Helper::equals($value, $data)) { - return null; - } - } - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $type = $context->currentDataType(); + $data = $context->currentData(); - return $this->error($schema, $context, 'enum', 'The data should match one item from enum'); - } + if ( isset( $this->enum[ $type ] ) ) { + foreach ( $this->enum[ $type ] as $value ) { + if ( Helper::equals( $value, $data ) ) { + return null; + } + } + } - /** - * @param array $values - * @return array - */ - protected function listByType($values): array - { - $list = []; + return $this->error( $schema, $context, 'enum', 'The data should match one item from enum' ); + } - foreach ($values as $value) { - $type = Helper::getJsonType($value); - if (!isset($list[$type])) { - $list[$type] = []; - } - $list[$type][] = $value; - } + /** + * @param array $values + * @return array + */ + protected function listByType( $values ): array { + $list = array(); - return $list; - } + foreach ( $values as $value ) { + $type = Helper::getJsonType( $value ); + if ( ! isset( $list[ $type ] ) ) { + $list[ $type ] = array(); + } + $list[ $type ][] = $value; + } + + return $list; + } } diff --git a/src/opis/json-schema/src/Keywords/ErrorTrait.php b/src/opis/json-schema/src/Keywords/ErrorTrait.php index d7ba5a91..aa5ef165 100644 --- a/src/opis/json-schema/src/Keywords/ErrorTrait.php +++ b/src/opis/json-schema/src/Keywords/ErrorTrait.php @@ -1,5 +1,6 @@ all(); - } - } +trait ErrorTrait { - return new ValidationError($keyword, $schema, DataInfo::fromContext($context), $message, $args, - is_array($errors) ? $errors : []); - } -} \ No newline at end of file + /** + * @param Schema $schema + * @param ValidationContext $context + * @param string $keyword + * @param string $message + * @param array $args + * @param ErrorContainer|ValidationError|ValidationError[]|null $errors + * @return ValidationError + */ + protected function error( + $schema, + $context, + $keyword, + $message, + $args = array(), + $errors = null + ): ValidationError { + if ( $errors ) { + if ( $errors instanceof ValidationError ) { + $errors = array( $errors ); + } elseif ( $errors instanceof ErrorContainer ) { + $errors = $errors->all(); + } + } + + return new ValidationError( + $keyword, + $schema, + DataInfo::fromContext( $context ), + $message, + $args, + is_array( $errors ) ? $errors : array() + ); + } +} diff --git a/src/opis/json-schema/src/Keywords/ExclusiveMaximumDataKeyword.php b/src/opis/json-schema/src/Keywords/ExclusiveMaximumDataKeyword.php index 7912ec94..2e066da2 100644 --- a/src/opis/json-schema/src/Keywords/ExclusiveMaximumDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/ExclusiveMaximumDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var float|int $number */ - $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($number === $this || !(is_float($number) || is_int($number))) { - return $this->error($schema, $context, 'exclusiveMaximum', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->number = $number; - - return parent::validate($context, $schema); - } +class ExclusiveMaximumDataKeyword extends ExclusiveMaximumKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var float|int $number */ + $number = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $number === $this || ! ( is_float( $number ) || is_int( $number ) ) ) { + return $this->error( + $schema, + $context, + 'exclusiveMaximum', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->number = $number; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/ExclusiveMaximumKeyword.php b/src/opis/json-schema/src/Keywords/ExclusiveMaximumKeyword.php index 442dd407..76e5e402 100644 --- a/src/opis/json-schema/src/Keywords/ExclusiveMaximumKeyword.php +++ b/src/opis/json-schema/src/Keywords/ExclusiveMaximumKeyword.php @@ -1,5 +1,6 @@ number = $number; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($context->currentData() < $this->number) { - return null; - } - - return $this->error($schema, $context, 'exclusiveMaximum', "Number must be lower than {max}", [ - 'max' => $this->number, - ]); - } +class ExclusiveMaximumKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var float + */ + protected $number; + + /** + * @param float $number + */ + public function __construct( float $number ) { + $this->number = $number; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $context->currentData() < $this->number ) { + return null; + } + + return $this->error( + $schema, + $context, + 'exclusiveMaximum', + 'Number must be lower than {max}', + array( + 'max' => $this->number, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/ExclusiveMinimumDataKeyword.php b/src/opis/json-schema/src/Keywords/ExclusiveMinimumDataKeyword.php index a27b853e..fab64cf1 100644 --- a/src/opis/json-schema/src/Keywords/ExclusiveMinimumDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/ExclusiveMinimumDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var float|int $number */ - $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($number === $this || !(is_float($number) || is_int($number))) { - return $this->error($schema, $context, 'exclusiveMinimum', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->number = $number; - - return parent::validate($context, $schema); - } +class ExclusiveMinimumDataKeyword extends ExclusiveMinimumKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var float|int $number */ + $number = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $number === $this || ! ( is_float( $number ) || is_int( $number ) ) ) { + return $this->error( + $schema, + $context, + 'exclusiveMinimum', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->number = $number; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/ExclusiveMinimumKeyword.php b/src/opis/json-schema/src/Keywords/ExclusiveMinimumKeyword.php index 2c123b05..11c64938 100644 --- a/src/opis/json-schema/src/Keywords/ExclusiveMinimumKeyword.php +++ b/src/opis/json-schema/src/Keywords/ExclusiveMinimumKeyword.php @@ -1,5 +1,6 @@ number = $number; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($context->currentData() > $this->number) { - return null; - } - - return $this->error($schema, $context, 'exclusiveMinimum', "Number must be greater than {min}", [ - 'min' => $this->number, - ]); - } +class ExclusiveMinimumKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var float + */ + protected $number; + + /** + * @param float $number + */ + public function __construct( float $number ) { + $this->number = $number; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $context->currentData() > $this->number ) { + return null; + } + + return $this->error( + $schema, + $context, + 'exclusiveMinimum', + 'Number must be greater than {min}', + array( + 'min' => $this->number, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/FiltersKeyword.php b/src/opis/json-schema/src/Keywords/FiltersKeyword.php index 8a39f5c9..3ffb8156 100644 --- a/src/opis/json-schema/src/Keywords/FiltersKeyword.php +++ b/src/opis/json-schema/src/Keywords/FiltersKeyword.php @@ -1,5 +1,6 @@ filters = $filters; - } + /** @var array|object[] */ + protected $filters; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $type = $context->currentDataType(); + /** + * @param object[] $filters + */ + public function __construct( array $filters ) { + $this->filters = $filters; + } - foreach ($this->filters as $filter) { - if (!isset($filter->types[$type])) { - throw new UnresolvedFilterException($filter->name, $type, $schema, $context); - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $type = $context->currentDataType(); - $func = $filter->types[$type]; + foreach ( $this->filters as $filter ) { + if ( ! isset( $filter->types[ $type ] ) ) { + throw new UnresolvedFilterException( $filter->name, $type, $schema, $context ); + } - if ($filter->args) { - $args = (array)$filter->args->resolve($context->rootData(), $context->currentDataPath()); - $args += $context->globals(); - } else { - $args = $context->globals(); - } + $func = $filter->types[ $type ]; - try { - if ($func instanceof Filter) { - $ok = $func->validate($context, $schema, $args); - } else { - $ok = $func($context->currentData(), $args); - } - } catch (CustomError $error) { - return $this->error($schema, $context, '$filters', $error->getMessage(), $error->getArgs() + [ - 'filter' => $filter->name, - 'type' => $type, - 'args' => $args, - ]); - } + if ( $filter->args ) { + $args = (array) $filter->args->resolve( $context->rootData(), $context->currentDataPath() ); + $args += $context->globals(); + } else { + $args = $context->globals(); + } - if ($ok) { - unset($func, $args, $ok); - continue; - } + try { + if ( $func instanceof Filter ) { + $ok = $func->validate( $context, $schema, $args ); + } else { + $ok = $func( $context->currentData(), $args ); + } + } catch ( CustomError $error ) { + return $this->error( + $schema, + $context, + '$filters', + $error->getMessage(), + $error->getArgs() + array( + 'filter' => $filter->name, + 'type' => $type, + 'args' => $args, + ) + ); + } - return $this->error($schema, $context, '$filters', "Filter '{filter}' ({type}) was not passed", [ - 'filter' => $filter->name, - 'type' => $type, - 'args' => $args, - ]); - } + if ( $ok ) { + unset( $func, $args, $ok ); + continue; + } - return null; - } + return $this->error( + $schema, + $context, + '$filters', + "Filter '{filter}' ({type}) was not passed", + array( + 'filter' => $filter->name, + 'type' => $type, + 'args' => $args, + ) + ); + } + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/FormatDataKeyword.php b/src/opis/json-schema/src/Keywords/FormatDataKeyword.php index 8adf8922..e87b6e8e 100644 --- a/src/opis/json-schema/src/Keywords/FormatDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/FormatDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - $this->resolver = $resolver; - parent::__construct('', []); - } + /** + * @var \Opis\JsonSchema\Resolvers\FormatResolver + */ + protected $resolver; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $value = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - if ($value === $this || !is_string($value)) { - return $this->error($schema, $context, 'format', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } + /** + * @param JsonPointer $value + * @param FormatResolver $resolver + */ + public function __construct( JsonPointer $value, FormatResolver $resolver ) { + $this->value = $value; + $this->resolver = $resolver; + parent::__construct( '', array() ); + } - /** @var string $value */ + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $value = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + if ( $value === $this || ! is_string( $value ) ) { + return $this->error( + $schema, + $context, + 'format', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } - $type = $context->currentDataType(); + /** @var string $value */ - $types = [ - $type => $this->resolver->resolve($value, $type), - ]; + $type = $context->currentDataType(); - if (!$types[$type] && ($super = Helper::getJsonSuperType($type))) { - $types[$super] = $this->resolver->resolve($value, $super); - unset($super); - } + $types = array( + $type => $this->resolver->resolve( $value, $type ), + ); - unset($type); + if ( ! $types[ $type ] && ( $super = Helper::getJsonSuperType( $type ) ) ) { + $types[ $super ] = $this->resolver->resolve( $value, $super ); + unset( $super ); + } - $this->name = $value; - $this->types = $types; - $ret = parent::validate($context, $schema); - $this->name = $this->types = null; + unset( $type ); - return $ret; - } + $this->name = $value; + $this->types = $types; + $ret = parent::validate( $context, $schema ); + $this->name = $this->types = null; + + return $ret; + } } diff --git a/src/opis/json-schema/src/Keywords/FormatKeyword.php b/src/opis/json-schema/src/Keywords/FormatKeyword.php index 93f8c168..1ce65b2d 100644 --- a/src/opis/json-schema/src/Keywords/FormatKeyword.php +++ b/src/opis/json-schema/src/Keywords/FormatKeyword.php @@ -1,5 +1,6 @@ name = $name; - $this->types = $types; - } + /** @var callable[]|Format[] */ + protected $types; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $type = $context->currentDataType(); + /** + * @param string $name + * @param callable[]|Format[] $types + */ + public function __construct( string $name, array $types ) { + $this->name = $name; + $this->types = $types; + } - if (!isset($this->types[$type])) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $type = $context->currentDataType(); - $format = $this->types[$type]; + if ( ! isset( $this->types[ $type ] ) ) { + return null; + } - try { - if ($format instanceof Format) { - $ok = $format->validate($context->currentData()); - } else { - $ok = $format($context->currentData()); - } - } catch (CustomError $error) { - return $this->error($schema, $context, 'format', $error->getMessage(), $error->getArgs() + [ - 'format' => $this->name, - 'type' => $type, - ]); - } + $format = $this->types[ $type ]; - if ($ok) { - return null; - } + try { + if ( $format instanceof Format ) { + $ok = $format->validate( $context->currentData() ); + } else { + $ok = $format( $context->currentData() ); + } + } catch ( CustomError $error ) { + return $this->error( + $schema, + $context, + 'format', + $error->getMessage(), + $error->getArgs() + array( + 'format' => $this->name, + 'type' => $type, + ) + ); + } - return $this->error($schema, $context, 'format', "The data must match the '{format}' format", [ - 'format' => $this->name, - 'type' => $type, - ]); - } + if ( $ok ) { + return null; + } + + return $this->error( + $schema, + $context, + 'format', + "The data must match the '{format}' format", + array( + 'format' => $this->name, + 'type' => $type, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/IfThenElseKeyword.php b/src/opis/json-schema/src/Keywords/IfThenElseKeyword.php index ef3650fa..f744a35a 100644 --- a/src/opis/json-schema/src/Keywords/IfThenElseKeyword.php +++ b/src/opis/json-schema/src/Keywords/IfThenElseKeyword.php @@ -1,5 +1,6 @@ if = $if; - $this->then = $then; - $this->else = $else; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($this->if === true) { - return $this->validateBranch('then', $context, $schema); - } elseif ($this->if === false) { - return $this->validateBranch('else', $context, $schema); - } - - if (is_object($this->if) && !($this->if instanceof Schema)) { - $this->if = $context->loader()->loadObjectSchema($this->if); - } - - if ($context->validateSchemaWithoutEvaluated($this->if, null, true)) { - return $this->validateBranch('else', $context, $schema); - } - - return $this->validateBranch('then', $context, $schema); - } - - /** - * @param string $branch - * @param ValidationContext $context - * @param Schema $schema - * @return ValidationError|null - */ - protected function validateBranch($branch, $context, $schema) - { - $value = $this->{$branch}; - - if ($value === true) { - return null; - } elseif ($value === false) { - return $this->error($schema, $context, $branch, "The data is never valid on '{branch}' branch", [ - 'branch' => $branch, - ]); - } - - if (is_object($value) && !($value instanceof Schema)) { - $value = $this->{$branch} = $context->loader()->loadObjectSchema($value); - } - - if ($error = $value->validate($context)) { - return $this->error($schema, $context, $branch, "The data is not valid on '{branch}' branch", [ - 'branch' => $branch, - ], $error); - } - - return null; - } +class IfThenElseKeyword implements Keyword { + + use ErrorTrait; + + /** @var bool|object */ + protected $if; + + /** @var bool|object */ + protected $then; + + /** @var bool|object */ + protected $else; + + /** + * @param bool|object $if + * @param bool|object $then + * @param bool|object $else + */ + public function __construct( $if, $then, $else ) { + $this->if = $if; + $this->then = $then; + $this->else = $else; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $this->if === true ) { + return $this->validateBranch( 'then', $context, $schema ); + } elseif ( $this->if === false ) { + return $this->validateBranch( 'else', $context, $schema ); + } + + if ( is_object( $this->if ) && ! ( $this->if instanceof Schema ) ) { + $this->if = $context->loader()->loadObjectSchema( $this->if ); + } + + if ( $context->validateSchemaWithoutEvaluated( $this->if, null, true ) ) { + return $this->validateBranch( 'else', $context, $schema ); + } + + return $this->validateBranch( 'then', $context, $schema ); + } + + /** + * @param string $branch + * @param ValidationContext $context + * @param Schema $schema + * @return ValidationError|null + */ + protected function validateBranch( $branch, $context, $schema ) { + $value = $this->{$branch}; + + if ( $value === true ) { + return null; + } elseif ( $value === false ) { + return $this->error( + $schema, + $context, + $branch, + "The data is never valid on '{branch}' branch", + array( + 'branch' => $branch, + ) + ); + } + + if ( is_object( $value ) && ! ( $value instanceof Schema ) ) { + $value = $this->{$branch} = $context->loader()->loadObjectSchema( $value ); + } + + if ( $error = $value->validate( $context ) ) { + return $this->error( + $schema, + $context, + $branch, + "The data is not valid on '{branch}' branch", + array( + 'branch' => $branch, + ), + $error + ); + } + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/ItemsKeyword.php b/src/opis/json-schema/src/Keywords/ItemsKeyword.php index ce4bc32e..7dac10dc 100644 --- a/src/opis/json-schema/src/Keywords/ItemsKeyword.php +++ b/src/opis/json-schema/src/Keywords/ItemsKeyword.php @@ -1,5 +1,6 @@ value = $value; - $this->alwaysValid = $alwaysValid; - - if (is_array($value)) { - $this->count = count($value); - } - - $this->keyword = $keyword; - $this->startIndex = $startIndex; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($this->alwaysValid || $this->value === true) { - if ($this->count === -1) { - $context->markAllAsEvaluatedItems(); - } else { - $context->markCountAsEvaluatedItems($this->count); - } - return null; - } - - $count = count($context->currentData()); - - if ($this->startIndex >= $count) { - // Already validated by other keyword - return null; - } - - if ($this->value === false) { - if ($count === 0) { - return null; - } - - return $this->error($schema, $context, $this->keyword, 'Array must be empty'); - } - - if ($this->count >= 0) { - - $errors = $this->errorContainer($context->maxErrors()); - $max = min($count, $this->count); - $evaluated = []; - - for ($i = $this->startIndex; $i < $max; $i++) { - if ($this->value[$i] === true) { - $evaluated[] = $i; - continue; - } - - if ($this->value[$i] === false) { - $context->addEvaluatedItems($evaluated); - return $this->error($schema, $context, $this->keyword, "Array item at index {index} is not allowed", [ - 'index' => $i, - ]); - } - - if (is_object($this->value[$i]) && !($this->value[$i] instanceof Schema)) { - $this->value[$i] = $context->loader()->loadObjectSchema($this->value[$i]); - } - - $context->pushDataPath($i); - $error = $this->value[$i]->validate($context); - $context->popDataPath(); - - if ($error) { - $errors->add($error); - if ($errors->isFull()) { - break; - } - } else { - $evaluated[] = $i; - } - } - - $context->addEvaluatedItems($evaluated); - - if ($errors->isEmpty()) { - return null; - } - - return $this->error($schema, $context, $this->keyword, 'Array items must match corresponding schemas', [], - $errors); - } - - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } - - $object = $this->createArrayObject($context); - - $error = $this->validateIterableData($schema, $this->value, $context, $this->indexes($this->startIndex, $count), - $this->keyword, 'All array items must match schema', [], $object); - - if ($object && $object->count()) { - $context->addEvaluatedItems($object->getArrayCopy()); - } - - return $error; - } - - /** - * @param int $start - * @param int $max - * @return iterable|int[] - */ - protected function indexes($start, $max) - { - for ($i = $start; $i < $max; $i++) { - yield $i; - } - } +class ItemsKeyword implements Keyword { + + use OfTrait; + use IterableDataValidationTrait; + + /** @var bool|object|Schema|bool[]|object[]|Schema[] */ + protected $value; + + /** + * @var int + */ + protected $count = -1; + /** + * @var bool + */ + protected $alwaysValid; + /** + * @var string + */ + protected $keyword; + /** + * @var int + */ + protected $startIndex; + + /** + * @param bool|object|Schema|bool[]|object[]|Schema[] $value + * @param bool $alwaysValid + * @param string $keyword + * @param int $startIndex + */ + public function __construct( $value, bool $alwaysValid = false, string $keyword = 'items', int $startIndex = 0 ) { + $this->value = $value; + $this->alwaysValid = $alwaysValid; + + if ( is_array( $value ) ) { + $this->count = count( $value ); + } + + $this->keyword = $keyword; + $this->startIndex = $startIndex; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $this->alwaysValid || $this->value === true ) { + if ( $this->count === -1 ) { + $context->markAllAsEvaluatedItems(); + } else { + $context->markCountAsEvaluatedItems( $this->count ); + } + return null; + } + + $count = count( $context->currentData() ); + + if ( $this->startIndex >= $count ) { + // Already validated by other keyword + return null; + } + + if ( $this->value === false ) { + if ( $count === 0 ) { + return null; + } + + return $this->error( $schema, $context, $this->keyword, 'Array must be empty' ); + } + + if ( $this->count >= 0 ) { + + $errors = $this->errorContainer( $context->maxErrors() ); + $max = min( $count, $this->count ); + $evaluated = array(); + + for ( $i = $this->startIndex; $i < $max; $i++ ) { + if ( $this->value[ $i ] === true ) { + $evaluated[] = $i; + continue; + } + + if ( $this->value[ $i ] === false ) { + $context->addEvaluatedItems( $evaluated ); + return $this->error( + $schema, + $context, + $this->keyword, + 'Array item at index {index} is not allowed', + array( + 'index' => $i, + ) + ); + } + + if ( is_object( $this->value[ $i ] ) && ! ( $this->value[ $i ] instanceof Schema ) ) { + $this->value[ $i ] = $context->loader()->loadObjectSchema( $this->value[ $i ] ); + } + + $context->pushDataPath( $i ); + $error = $this->value[ $i ]->validate( $context ); + $context->popDataPath(); + + if ( $error ) { + $errors->add( $error ); + if ( $errors->isFull() ) { + break; + } + } else { + $evaluated[] = $i; + } + } + + $context->addEvaluatedItems( $evaluated ); + + if ( $errors->isEmpty() ) { + return null; + } + + return $this->error( + $schema, + $context, + $this->keyword, + 'Array items must match corresponding schemas', + array(), + $errors + ); + } + + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } + + $object = $this->createArrayObject( $context ); + + $error = $this->validateIterableData( + $schema, + $this->value, + $context, + $this->indexes( $this->startIndex, $count ), + $this->keyword, + 'All array items must match schema', + array(), + $object + ); + + if ( $object && $object->count() ) { + $context->addEvaluatedItems( $object->getArrayCopy() ); + } + + return $error; + } + + /** + * @param int $start + * @param int $max + * @return iterable|int[] + */ + protected function indexes( $start, $max ) { + for ( $i = $start; $i < $max; $i++ ) { + yield $i; + } + } } diff --git a/src/opis/json-schema/src/Keywords/IterableDataValidationTrait.php b/src/opis/json-schema/src/Keywords/IterableDataValidationTrait.php index d9f4780e..a9ae4c4f 100644 --- a/src/opis/json-schema/src/Keywords/IterableDataValidationTrait.php +++ b/src/opis/json-schema/src/Keywords/IterableDataValidationTrait.php @@ -1,5 +1,6 @@ errorContainer($context->maxErrors()); + /** + * @param int $maxErrors + * @return ErrorContainer + */ + protected function errorContainer( $maxErrors = 1 ): ErrorContainer { + return new ErrorContainer( $maxErrors ); + } - if ($keys) { - foreach ($iterator as $key) { - $context->pushDataPath($key); - $error = $schema->validate($context); - $context->popDataPath(); + /** + * @param Schema $schema + * @param ValidationContext $context + * @param iterable $iterator + * @param ArrayObject|null $keys + * @return ErrorContainer + */ + protected function iterateAndValidate( + $schema, + $context, + $iterator, + $keys = null + ): ErrorContainer { + $container = $this->errorContainer( $context->maxErrors() ); - if ($error) { - if (!$container->isFull()) { - $container->add($error); - } - } else { - $keys[] = $key; - } - } - } else { - foreach ($iterator as $key) { - $context->pushDataPath($key); - $error = $schema->validate($context); - $context->popDataPath(); + if ( $keys ) { + foreach ( $iterator as $key ) { + $context->pushDataPath( $key ); + $error = $schema->validate( $context ); + $context->popDataPath(); - if ($error && $container->add($error)->isFull()) { - break; - } - } - } + if ( $error ) { + if ( ! $container->isFull() ) { + $container->add( $error ); + } + } else { + $keys[] = $key; + } + } + } else { + foreach ( $iterator as $key ) { + $context->pushDataPath( $key ); + $error = $schema->validate( $context ); + $context->popDataPath(); - return $container; - } + if ( $error && $container->add( $error )->isFull() ) { + break; + } + } + } - /** - * @param Schema $parentSchema - * @param Schema $schema - * @param ValidationContext $context - * @param iterable $iterator - * @param string $keyword - * @param string $message - * @param array $args - * @param ArrayObject|null $visited_keys - * @return ValidationError|null - */ - protected function validateIterableData( - $parentSchema, - $schema, - $context, - $iterator, - $keyword, - $message, - $args = [], - $visited_keys = null - ) { - $errors = $this->iterateAndValidate($schema, $context, $iterator, $visited_keys); + return $container; + } - if ($errors->isEmpty()) { - return null; - } + /** + * @param Schema $parentSchema + * @param Schema $schema + * @param ValidationContext $context + * @param iterable $iterator + * @param string $keyword + * @param string $message + * @param array $args + * @param ArrayObject|null $visited_keys + * @return ValidationError|null + */ + protected function validateIterableData( + $parentSchema, + $schema, + $context, + $iterator, + $keyword, + $message, + $args = array(), + $visited_keys = null + ) { + $errors = $this->iterateAndValidate( $schema, $context, $iterator, $visited_keys ); - return $this->error($parentSchema, $context, $keyword, $message, $args, $errors); - } + if ( $errors->isEmpty() ) { + return null; + } + + return $this->error( $parentSchema, $context, $keyword, $message, $args, $errors ); + } } diff --git a/src/opis/json-schema/src/Keywords/MaxItemsDataKeyword.php b/src/opis/json-schema/src/Keywords/MaxItemsDataKeyword.php index f846fdba..5c98c13f 100644 --- a/src/opis/json-schema/src/Keywords/MaxItemsDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MaxItemsDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var int $count */ - $count = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($count === $this || !is_int($count) || $count < 0) { - return $this->error($schema, $context, 'maxItems', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->count = $count; - - return parent::validate($context, $schema); - } +class MaxItemsDataKeyword extends MaxItemsKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var int $count */ + $count = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $count === $this || ! is_int( $count ) || $count < 0 ) { + return $this->error( + $schema, + $context, + 'maxItems', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->count = $count; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MaxItemsKeyword.php b/src/opis/json-schema/src/Keywords/MaxItemsKeyword.php index feb3fce0..3f7f0b04 100644 --- a/src/opis/json-schema/src/Keywords/MaxItemsKeyword.php +++ b/src/opis/json-schema/src/Keywords/MaxItemsKeyword.php @@ -1,5 +1,6 @@ count = $count; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $count = count($context->currentData()); - - if ($count <= $this->count) { - return null; - } - - return $this->error($schema, $context, "maxItems", - "Array should have at most {max} items, {count} found", [ - 'max' => $this->count, - 'count' => $count, - ]); - } +class MaxItemsKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var int + */ + protected $count; + + /** + * @param int $count + */ + public function __construct( int $count ) { + $this->count = $count; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $count = count( $context->currentData() ); + + if ( $count <= $this->count ) { + return null; + } + + return $this->error( + $schema, + $context, + 'maxItems', + 'Array should have at most {max} items, {count} found', + array( + 'max' => $this->count, + 'count' => $count, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/MaxLengthDataKeyword.php b/src/opis/json-schema/src/Keywords/MaxLengthDataKeyword.php index acf42128..fbf5734d 100644 --- a/src/opis/json-schema/src/Keywords/MaxLengthDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MaxLengthDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var int $length */ - $length = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($length === $this || !is_int($length) || $length < 0) { - return $this->error($schema, $context, 'maxLength', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->length = $length; - - return parent::validate($context, $schema); - } +class MaxLengthDataKeyword extends MaxLengthKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var int $length */ + $length = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $length === $this || ! is_int( $length ) || $length < 0 ) { + return $this->error( + $schema, + $context, + 'maxLength', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->length = $length; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MaxLengthKeyword.php b/src/opis/json-schema/src/Keywords/MaxLengthKeyword.php index b1978559..72777a99 100644 --- a/src/opis/json-schema/src/Keywords/MaxLengthKeyword.php +++ b/src/opis/json-schema/src/Keywords/MaxLengthKeyword.php @@ -1,5 +1,6 @@ length = $length; - } + /** + * @var int + */ + protected $length; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($this->length === 0) { - return null; - } + /** + * @param int $length + */ + public function __construct( int $length ) { + $this->length = $length; + } - $length = $context->getStringLength(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $this->length === 0 ) { + return null; + } - if ($length <= $this->length) { - return null; - } + $length = $context->getStringLength(); - return $this->error($schema, $context, 'maxLength', "Maximum string length is {max}, found {length}", - [ - 'max' => $this->length, - 'length' => $length, - ]); - } + if ( $length <= $this->length ) { + return null; + } + + return $this->error( + $schema, + $context, + 'maxLength', + 'Maximum string length is {max}, found {length}', + array( + 'max' => $this->length, + 'length' => $length, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/MaxPropertiesDataKeyword.php b/src/opis/json-schema/src/Keywords/MaxPropertiesDataKeyword.php index 14ea37dd..1f31f44c 100644 --- a/src/opis/json-schema/src/Keywords/MaxPropertiesDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MaxPropertiesDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var int $count */ - $count = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($count === $this || !is_int($count) || $count < 0) { - return $this->error($schema, $context, 'maxProperties', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->count = $count; - - return parent::validate($context, $schema); - } +class MaxPropertiesDataKeyword extends MaxPropertiesKeywords { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @inheritDoc + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var int $count */ + $count = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $count === $this || ! is_int( $count ) || $count < 0 ) { + return $this->error( + $schema, + $context, + 'maxProperties', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->count = $count; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MaxPropertiesKeywords.php b/src/opis/json-schema/src/Keywords/MaxPropertiesKeywords.php index e9f948a3..14674529 100644 --- a/src/opis/json-schema/src/Keywords/MaxPropertiesKeywords.php +++ b/src/opis/json-schema/src/Keywords/MaxPropertiesKeywords.php @@ -1,5 +1,6 @@ count = $count; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $count = count($context->getObjectProperties()); - - if ($count <= $this->count) { - return null; - } - - return $this->error($schema, $context, 'maxProperties', - "Object must have at most {max} properties, {count} found", [ - 'max' => $this->count, - 'count' => $count, - ]); - } +class MaxPropertiesKeywords implements Keyword { + + use ErrorTrait; + + /** + * @var int + */ + protected $count; + + /** + * @param int $count + */ + public function __construct( int $count ) { + $this->count = $count; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $count = count( $context->getObjectProperties() ); + + if ( $count <= $this->count ) { + return null; + } + + return $this->error( + $schema, + $context, + 'maxProperties', + 'Object must have at most {max} properties, {count} found', + array( + 'max' => $this->count, + 'count' => $count, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/MaximumDataKeyword.php b/src/opis/json-schema/src/Keywords/MaximumDataKeyword.php index 6cb7dd16..550d696d 100644 --- a/src/opis/json-schema/src/Keywords/MaximumDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MaximumDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var float|int $number */ - $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($number === $this || !(is_float($number) || is_int($number))) { - return $this->error($schema, $context, 'maximum', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->number = $number; - - return parent::validate($context, $schema); - } +class MaximumDataKeyword extends MaximumKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var float|int $number */ + $number = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $number === $this || ! ( is_float( $number ) || is_int( $number ) ) ) { + return $this->error( + $schema, + $context, + 'maximum', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->number = $number; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MaximumKeyword.php b/src/opis/json-schema/src/Keywords/MaximumKeyword.php index a0ab6c4f..7d583931 100644 --- a/src/opis/json-schema/src/Keywords/MaximumKeyword.php +++ b/src/opis/json-schema/src/Keywords/MaximumKeyword.php @@ -1,5 +1,6 @@ number = $number; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($context->currentData() <= $this->number) { - return null; - } - - return $this->error($schema, $context, 'maximum', "Number must be lower than or equal to {max}", [ - 'max' => $this->number, - ]); - } +class MaximumKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var float + */ + protected $number; + + /** + * @param float $number + */ + public function __construct( float $number ) { + $this->number = $number; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $context->currentData() <= $this->number ) { + return null; + } + + return $this->error( + $schema, + $context, + 'maximum', + 'Number must be lower than or equal to {max}', + array( + 'max' => $this->number, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/MinItemsDataKeyword.php b/src/opis/json-schema/src/Keywords/MinItemsDataKeyword.php index c175c9c1..f37f950b 100644 --- a/src/opis/json-schema/src/Keywords/MinItemsDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MinItemsDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var int $count */ - $count = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($count === $this || !is_int($count) || $count < 0) { - return $this->error($schema, $context, 'minItems', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->count = $count; - - return parent::validate($context, $schema); - } +class MinItemsDataKeyword extends MinItemsKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var int $count */ + $count = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $count === $this || ! is_int( $count ) || $count < 0 ) { + return $this->error( + $schema, + $context, + 'minItems', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->count = $count; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MinItemsKeyword.php b/src/opis/json-schema/src/Keywords/MinItemsKeyword.php index 60a86e6f..a690e52e 100644 --- a/src/opis/json-schema/src/Keywords/MinItemsKeyword.php +++ b/src/opis/json-schema/src/Keywords/MinItemsKeyword.php @@ -1,5 +1,6 @@ count = $count; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $count = count($context->currentData()); - - if ($count >= $this->count) { - return null; - } - - return $this->error($schema, $context, "minItems", - "Array should have at least {min} items, {count} found", [ - 'min' => $this->count, - 'count' => $count, - ]); - } +class MinItemsKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var int + */ + protected $count; + + /** + * @param int $count + */ + public function __construct( int $count ) { + $this->count = $count; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $count = count( $context->currentData() ); + + if ( $count >= $this->count ) { + return null; + } + + return $this->error( + $schema, + $context, + 'minItems', + 'Array should have at least {min} items, {count} found', + array( + 'min' => $this->count, + 'count' => $count, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/MinLengthDataKeyword.php b/src/opis/json-schema/src/Keywords/MinLengthDataKeyword.php index c579ba07..3eb0acb6 100644 --- a/src/opis/json-schema/src/Keywords/MinLengthDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MinLengthDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var int $length */ - $length = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($length === $this || !is_int($length) || $length < 0) { - return $this->error($schema, $context, 'minLength', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->length = $length; - - return parent::validate($context, $schema); - } +class MinLengthDataKeyword extends MinLengthKeyword { + + /** @var JsonPointer */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var int $length */ + $length = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $length === $this || ! is_int( $length ) || $length < 0 ) { + return $this->error( + $schema, + $context, + 'minLength', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->length = $length; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MinLengthKeyword.php b/src/opis/json-schema/src/Keywords/MinLengthKeyword.php index 6e0b1596..cb5a0a94 100644 --- a/src/opis/json-schema/src/Keywords/MinLengthKeyword.php +++ b/src/opis/json-schema/src/Keywords/MinLengthKeyword.php @@ -1,5 +1,6 @@ length = $length; - } + /** + * @var int + */ + protected $length; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($this->length === 0) { - return null; - } + /** + * @param int $length + */ + public function __construct( int $length ) { + $this->length = $length; + } - $length = $context->getStringLength(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $this->length === 0 ) { + return null; + } - if ($length >= $this->length) { - return null; - } + $length = $context->getStringLength(); - return $this->error($schema, $context, 'minLength', "Minimum string length is {min}, found {length}", [ - 'min' => $this->length, - 'length' => $length, - ]); - } + if ( $length >= $this->length ) { + return null; + } + + return $this->error( + $schema, + $context, + 'minLength', + 'Minimum string length is {min}, found {length}', + array( + 'min' => $this->length, + 'length' => $length, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/MinPropertiesDataKeyword.php b/src/opis/json-schema/src/Keywords/MinPropertiesDataKeyword.php index b1f06cfe..cd53bfdd 100644 --- a/src/opis/json-schema/src/Keywords/MinPropertiesDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MinPropertiesDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var int $count */ - $count = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($count === $this || !is_int($count) || $count < 0) { - return $this->error($schema, $context, 'minProperties', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->count = $count; - - return parent::validate($context, $schema); - } +class MinPropertiesDataKeyword extends MinPropertiesKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var int $count */ + $count = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $count === $this || ! is_int( $count ) || $count < 0 ) { + return $this->error( + $schema, + $context, + 'minProperties', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->count = $count; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MinPropertiesKeyword.php b/src/opis/json-schema/src/Keywords/MinPropertiesKeyword.php index 0a9eb1e1..8082e133 100644 --- a/src/opis/json-schema/src/Keywords/MinPropertiesKeyword.php +++ b/src/opis/json-schema/src/Keywords/MinPropertiesKeyword.php @@ -1,5 +1,6 @@ count = $count; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $count = count($context->getObjectProperties()); - - if ($this->count <= $count) { - return null; - } - - return $this->error($schema, $context, 'minProperties', - "Object must have at least {min} properties, {count} found", [ - 'min' => $this->count, - 'count' => $count, - ]); - } +class MinPropertiesKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var int + */ + protected $count; + + /** + * @param int $count + */ + public function __construct( int $count ) { + $this->count = $count; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $count = count( $context->getObjectProperties() ); + + if ( $this->count <= $count ) { + return null; + } + + return $this->error( + $schema, + $context, + 'minProperties', + 'Object must have at least {min} properties, {count} found', + array( + 'min' => $this->count, + 'count' => $count, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/MinimumDataKeyword.php b/src/opis/json-schema/src/Keywords/MinimumDataKeyword.php index 77335dd1..48d56654 100644 --- a/src/opis/json-schema/src/Keywords/MinimumDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MinimumDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var float|int $number */ - $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($number === $this || !(is_float($number) || is_int($number))) { - return $this->error($schema, $context, 'minimum', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->number = $number; - - return parent::validate($context, $schema); - } +class MinimumDataKeyword extends MinimumKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var float|int $number */ + $number = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $number === $this || ! ( is_float( $number ) || is_int( $number ) ) ) { + return $this->error( + $schema, + $context, + 'minimum', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->number = $number; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MinimumKeyword.php b/src/opis/json-schema/src/Keywords/MinimumKeyword.php index 99c67ffb..bd4cc412 100644 --- a/src/opis/json-schema/src/Keywords/MinimumKeyword.php +++ b/src/opis/json-schema/src/Keywords/MinimumKeyword.php @@ -1,5 +1,6 @@ number = $number; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($context->currentData() >= $this->number) { - return null; - } - - return $this->error($schema, $context, 'minimum', "Number must be greater than or equal to {min}", [ - 'min' => $this->number, - ]); - } +class MinimumKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var float + */ + protected $number; + + /** + * @param float $number + */ + public function __construct( float $number ) { + $this->number = $number; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $context->currentData() >= $this->number ) { + return null; + } + + return $this->error( + $schema, + $context, + 'minimum', + 'Number must be greater than or equal to {min}', + array( + 'min' => $this->number, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/MultipleOfDataKeyword.php b/src/opis/json-schema/src/Keywords/MultipleOfDataKeyword.php index a32df2b0..87d61b4b 100644 --- a/src/opis/json-schema/src/Keywords/MultipleOfDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/MultipleOfDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(0); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - /** @var float|int $number */ - $number = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($number === $this || !(is_float($number) || is_int($number)) || $number <= 0) { - return $this->error($schema, $context, 'multipleOf', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->number = $number; - - return parent::validate($context, $schema); - } +class MultipleOfDataKeyword extends MultipleOfKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( 0 ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + /** @var float|int $number */ + $number = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $number === $this || ! ( is_float( $number ) || is_int( $number ) ) || $number <= 0 ) { + return $this->error( + $schema, + $context, + 'multipleOf', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->number = $number; + + return parent::validate( $context, $schema ); + } } diff --git a/src/opis/json-schema/src/Keywords/MultipleOfKeyword.php b/src/opis/json-schema/src/Keywords/MultipleOfKeyword.php index be324434..734062b7 100644 --- a/src/opis/json-schema/src/Keywords/MultipleOfKeyword.php +++ b/src/opis/json-schema/src/Keywords/MultipleOfKeyword.php @@ -1,5 +1,6 @@ number = $number; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if (Helper::isMultipleOf($context->currentData(), $this->number)) { - return null; - } - - return $this->error($schema, $context, 'multipleOf', "Number must be a multiple of {divisor}", [ - 'divisor' => $this->number, - ]); - } +class MultipleOfKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var float + */ + protected $number; + + /** + * @param float $number + */ + public function __construct( float $number ) { + $this->number = $number; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( Helper::isMultipleOf( $context->currentData(), $this->number ) ) { + return null; + } + + return $this->error( + $schema, + $context, + 'multipleOf', + 'Number must be a multiple of {divisor}', + array( + 'divisor' => $this->number, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/NotKeyword.php b/src/opis/json-schema/src/Keywords/NotKeyword.php index 39dc7751..fbe7958b 100644 --- a/src/opis/json-schema/src/Keywords/NotKeyword.php +++ b/src/opis/json-schema/src/Keywords/NotKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var bool|object|Schema */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($this->value === false) { - return null; - } - if ($this->value === true) { - return $this->error($schema, $context, 'not', "The data is never valid"); - } + /** + * @param bool|object $value + */ + public function __construct( $value ) { + $this->value = $value; + } - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $this->value === false ) { + return null; + } + if ( $this->value === true ) { + return $this->error( $schema, $context, 'not', 'The data is never valid' ); + } - $error = $context->validateSchemaWithoutEvaluated($this->value, 1); + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } - if ($error) { - return null; - } + $error = $context->validateSchemaWithoutEvaluated( $this->value, 1 ); - return $this->error($schema, $context, 'not', 'The data must not match schema'); - } + if ( $error ) { + return null; + } + + return $this->error( $schema, $context, 'not', 'The data must not match schema' ); + } } diff --git a/src/opis/json-schema/src/Keywords/OfTrait.php b/src/opis/json-schema/src/Keywords/OfTrait.php index 2cef55b1..a0c1e81b 100644 --- a/src/opis/json-schema/src/Keywords/OfTrait.php +++ b/src/opis/json-schema/src/Keywords/OfTrait.php @@ -1,5 +1,6 @@ trackUnevaluated() ? new ArrayObject() : null; - } +trait OfTrait { - /** - * @param \ArrayObject|null $object - * @param \Opis\JsonSchema\ValidationContext $context - */ - protected function addEvaluatedFromArrayObject($object, $context) - { - if (!$object || !$object->count()) { - return; - } + /** + * @param \Opis\JsonSchema\ValidationContext $context + */ + protected function createArrayObject( $context ) { + return $context->trackUnevaluated() ? new ArrayObject() : null; + } - foreach ($object as $value) { - if (isset($value['properties'])) { - $context->addEvaluatedProperties($value['properties']); - } - if (isset($value['items'])) { - $context->addEvaluatedItems($value['items']); - } - } - } + /** + * @param \ArrayObject|null $object + * @param \Opis\JsonSchema\ValidationContext $context + */ + protected function addEvaluatedFromArrayObject( $object, $context ) { + if ( ! $object || ! $object->count() ) { + return; + } + + foreach ( $object as $value ) { + if ( isset( $value['properties'] ) ) { + $context->addEvaluatedProperties( $value['properties'] ); + } + if ( isset( $value['items'] ) ) { + $context->addEvaluatedItems( $value['items'] ); + } + } + } } diff --git a/src/opis/json-schema/src/Keywords/OneOfKeyword.php b/src/opis/json-schema/src/Keywords/OneOfKeyword.php index fdbed540..1f7aca2b 100644 --- a/src/opis/json-schema/src/Keywords/OneOfKeyword.php +++ b/src/opis/json-schema/src/Keywords/OneOfKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var bool[]|object[] */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $count = 0; - $matchedIndex = -1; - $object = $this->createArrayObject($context); - $errors = []; + /** + * @param bool[]|object[] $value + */ + public function __construct( array $value ) { + $this->value = $value; + } - foreach ($this->value as $index => $value) { - if ($value === false) { - continue; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $count = 0; + $matchedIndex = -1; + $object = $this->createArrayObject( $context ); + $errors = array(); - if ($value === true) { - if (++$count > 1) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [ - 'matched' => [$matchedIndex, $index], - ]); - } + foreach ( $this->value as $index => $value ) { + if ( $value === false ) { + continue; + } - $matchedIndex = $index; - continue; - } + if ( $value === true ) { + if ( ++$count > 1 ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'oneOf', + 'The data should match exactly one schema', + array( + 'matched' => array( $matchedIndex, $index ), + ) + ); + } - if (is_object($value) && !($value instanceof Schema)) { - $value = $this->value[$index] = $context->loader()->loadObjectSchema($value); - } + $matchedIndex = $index; + continue; + } - $error = $context->validateSchemaWithoutEvaluated($value, null, false, $object); - if ($error) { - $errors[] = $error; - } else { - if (++$count > 1) { - $this->addEvaluatedFromArrayObject($object, $context); - return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [ - 'matched' => [$matchedIndex, $index], - ]); - } - $matchedIndex = $index; - } - } + if ( is_object( $value ) && ! ( $value instanceof Schema ) ) { + $value = $this->value[ $index ] = $context->loader()->loadObjectSchema( $value ); + } - $this->addEvaluatedFromArrayObject($object, $context); + $error = $context->validateSchemaWithoutEvaluated( $value, null, false, $object ); + if ( $error ) { + $errors[] = $error; + } else { + if ( ++$count > 1 ) { + $this->addEvaluatedFromArrayObject( $object, $context ); + return $this->error( + $schema, + $context, + 'oneOf', + 'The data should match exactly one schema', + array( + 'matched' => array( $matchedIndex, $index ), + ) + ); + } + $matchedIndex = $index; + } + } - if ($count === 1) { - return null; - } + $this->addEvaluatedFromArrayObject( $object, $context ); - return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [ - 'matched' => [], - ], $errors); - } + if ( $count === 1 ) { + return null; + } + + return $this->error( + $schema, + $context, + 'oneOf', + 'The data should match exactly one schema', + array( + 'matched' => array(), + ), + $errors + ); + } } diff --git a/src/opis/json-schema/src/Keywords/PatternDataKeyword.php b/src/opis/json-schema/src/Keywords/PatternDataKeyword.php index a64592d0..be2ff775 100644 --- a/src/opis/json-schema/src/Keywords/PatternDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/PatternDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - parent::__construct(''); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $pattern = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - if ($pattern === $this || !is_string($pattern) || !Helper::isValidPattern($pattern)) { - return $this->error($schema, $context, 'pattern', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - $this->pattern = $pattern; - $this->regex = Helper::patternToRegex($pattern); - $ret = parent::validate($context, $schema); - $this->pattern = $this->regex = null; - - return $ret; - } +class PatternDataKeyword extends PatternKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + parent::__construct( '' ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $pattern = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + if ( $pattern === $this || ! is_string( $pattern ) || ! Helper::isValidPattern( $pattern ) ) { + return $this->error( + $schema, + $context, + 'pattern', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + $this->pattern = $pattern; + $this->regex = Helper::patternToRegex( $pattern ); + $ret = parent::validate( $context, $schema ); + $this->pattern = $this->regex = null; + + return $ret; + } } diff --git a/src/opis/json-schema/src/Keywords/PatternKeyword.php b/src/opis/json-schema/src/Keywords/PatternKeyword.php index cb50a622..1f59303d 100644 --- a/src/opis/json-schema/src/Keywords/PatternKeyword.php +++ b/src/opis/json-schema/src/Keywords/PatternKeyword.php @@ -1,5 +1,6 @@ pattern = $pattern; - $this->regex = Helper::patternToRegex($pattern); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if (preg_match($this->regex, $context->currentData())) { - return null; - } - - return $this->error($schema, $context, 'pattern', "The string should match pattern: {pattern}", [ - 'pattern' => $this->pattern, - ]); - } +class PatternKeyword implements Keyword { + + use ErrorTrait; + + /** + * @var string|null + */ + protected $pattern; + + /** + * @var string|null + */ + protected $regex; + + /** + * @param string $pattern + */ + public function __construct( string $pattern ) { + $this->pattern = $pattern; + $this->regex = Helper::patternToRegex( $pattern ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( preg_match( $this->regex, $context->currentData() ) ) { + return null; + } + + return $this->error( + $schema, + $context, + 'pattern', + 'The string should match pattern: {pattern}', + array( + 'pattern' => $this->pattern, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/PatternPropertiesKeyword.php b/src/opis/json-schema/src/Keywords/PatternPropertiesKeyword.php index d1f90580..1ed852d1 100644 --- a/src/opis/json-schema/src/Keywords/PatternPropertiesKeyword.php +++ b/src/opis/json-schema/src/Keywords/PatternPropertiesKeyword.php @@ -1,5 +1,6 @@ value = $value; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $props = $context->getObjectProperties(); - - if (!$props) { - return null; - } - - $checked = []; - - foreach ($this->value as $pattern => $value) { - if ($value === true) { - iterator_to_array($this->matchedProperties($pattern, $props, $checked)); - continue; - } - - if ($value === false) { - $list = iterator_to_array($this->matchedProperties($pattern, $props, $checked)); - - if ($list) { - if ($context->trackUnevaluated()) { - $context->addEvaluatedProperties(array_diff(array_keys($checked), $list)); - } - return $this->error($schema, $context, 'patternProperties', "Object properties that match pattern '{pattern}' are not allowed", [ - 'pattern' => $pattern, - 'forbidden' => $list, - ]); - } - - unset($list); - continue; - } - - if (is_object($value) && !($value instanceof Schema)) { - $value = $this->value[$pattern] = $context->loader()->loadObjectSchema($value); - } - - $subErrors = $this->iterateAndValidate($value, $context, $this->matchedProperties($pattern, $props, $checked)); - - if (!$subErrors->isEmpty()) { - if ($context->trackUnevaluated()) { - $context->addEvaluatedProperties(array_keys($checked)); - } - return $this->error($schema, $context, 'patternProperties', "Object properties that match pattern '{pattern}' must also match pattern's schema", [ - 'pattern' => $pattern, - ], $subErrors); - } - - unset($subErrors); - } - - if ($checked) { - $checked = array_keys($checked); - $context->addCheckedProperties($checked); - $context->addEvaluatedProperties($checked); - } - - return null; - } - - /** - * @param string $pattern - * @param array $props - * @param array $checked - * @return Traversable|string[] - */ - protected function matchedProperties($pattern, $props, &$checked): Traversable - { - $pattern = Helper::patternToRegex($pattern); - - foreach ($props as $prop) { - if (preg_match($pattern, (string)$prop)) { - $checked[$prop] = true; - yield $prop; - } - } - } +class PatternPropertiesKeyword implements Keyword { + + use IterableDataValidationTrait; + + /** @var bool[]|object[] */ + protected $value; + + /** + * @param bool[]|object[] $value + */ + public function __construct( array $value ) { + $this->value = $value; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $props = $context->getObjectProperties(); + + if ( ! $props ) { + return null; + } + + $checked = array(); + + foreach ( $this->value as $pattern => $value ) { + if ( $value === true ) { + iterator_to_array( $this->matchedProperties( $pattern, $props, $checked ) ); + continue; + } + + if ( $value === false ) { + $list = iterator_to_array( $this->matchedProperties( $pattern, $props, $checked ) ); + + if ( $list ) { + if ( $context->trackUnevaluated() ) { + $context->addEvaluatedProperties( array_diff( array_keys( $checked ), $list ) ); + } + return $this->error( + $schema, + $context, + 'patternProperties', + "Object properties that match pattern '{pattern}' are not allowed", + array( + 'pattern' => $pattern, + 'forbidden' => $list, + ) + ); + } + + unset( $list ); + continue; + } + + if ( is_object( $value ) && ! ( $value instanceof Schema ) ) { + $value = $this->value[ $pattern ] = $context->loader()->loadObjectSchema( $value ); + } + + $subErrors = $this->iterateAndValidate( $value, $context, $this->matchedProperties( $pattern, $props, $checked ) ); + + if ( ! $subErrors->isEmpty() ) { + if ( $context->trackUnevaluated() ) { + $context->addEvaluatedProperties( array_keys( $checked ) ); + } + return $this->error( + $schema, + $context, + 'patternProperties', + "Object properties that match pattern '{pattern}' must also match pattern's schema", + array( + 'pattern' => $pattern, + ), + $subErrors + ); + } + + unset( $subErrors ); + } + + if ( $checked ) { + $checked = array_keys( $checked ); + $context->addCheckedProperties( $checked ); + $context->addEvaluatedProperties( $checked ); + } + + return null; + } + + /** + * @param string $pattern + * @param array $props + * @param array $checked + * @return Traversable|string[] + */ + protected function matchedProperties( $pattern, $props, &$checked ): Traversable { + $pattern = Helper::patternToRegex( $pattern ); + + foreach ( $props as $prop ) { + if ( preg_match( $pattern, (string) $prop ) ) { + $checked[ $prop ] = true; + yield $prop; + } + } + } } diff --git a/src/opis/json-schema/src/Keywords/PointerRefKeyword.php b/src/opis/json-schema/src/Keywords/PointerRefKeyword.php index c3396366..2f393548 100644 --- a/src/opis/json-schema/src/Keywords/PointerRefKeyword.php +++ b/src/opis/json-schema/src/Keywords/PointerRefKeyword.php @@ -1,5 +1,6 @@ pointer = $pointer; - } - - /** - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - protected function doValidate($context, $schema) - { - if ($this->resolved === false) { - $info = $schema->info(); - $this->resolved = $this->resolvePointer($context->loader(), $this->pointer, $info->idBaseRoot(), $info->path()); - } - - if ($this->resolved === null) { - throw new UnresolvedReferenceException((string)$this->pointer, $schema, $context); - } - - return $this->resolved->validate($this->createContext($context, $schema)); - } +class PointerRefKeyword extends AbstractRefKeyword { + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $pointer; + /** @var bool|null|Schema */ + protected $resolved = false; + + public function __construct( + JsonPointer $pointer, + $mapper, + $globals, + $slots = null, + string $keyword = '$ref' + ) { + parent::__construct( $mapper, $globals, $slots, $keyword ); + $this->pointer = $pointer; + } + + /** + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + protected function doValidate( $context, $schema ) { + if ( $this->resolved === false ) { + $info = $schema->info(); + $this->resolved = $this->resolvePointer( $context->loader(), $this->pointer, $info->idBaseRoot(), $info->path() ); + } + + if ( $this->resolved === null ) { + throw new UnresolvedReferenceException( (string) $this->pointer, $schema, $context ); + } + + return $this->resolved->validate( $this->createContext( $context, $schema ) ); + } } diff --git a/src/opis/json-schema/src/Keywords/PropertiesKeyword.php b/src/opis/json-schema/src/Keywords/PropertiesKeyword.php index 28ad31e8..a7e40c8a 100644 --- a/src/opis/json-schema/src/Keywords/PropertiesKeyword.php +++ b/src/opis/json-schema/src/Keywords/PropertiesKeyword.php @@ -1,5 +1,6 @@ properties = $properties; - $this->propertyKeys = array_keys($properties); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if (!$this->properties) { - return null; - } - - $checked = []; - $evaluated = []; - - $data = $context->currentData(); - - $errors = $this->errorContainer($context->maxErrors()); - - foreach ($this->properties as $name => $prop) { - if (!property_exists($data, $name)) { - continue; - } - - $checked[] = $name; - - if ($prop === true) { - $evaluated[] = $name; - continue; - } - - if ($prop === false) { - $context->addEvaluatedProperties($evaluated); - return $this->error($schema, $context, 'properties', "Property '{property}' is not allowed", [ - 'property' => $name, - ]); - } - - if (is_object($prop) && !($prop instanceof Schema)) { - $prop = $this->properties[$name] = $context->loader()->loadObjectSchema($prop); - } - - $context->pushDataPath($name); - $error = $prop->validate($context); - $context->popDataPath(); - - if ($error) { - $errors->add($error); - if ($errors->isFull()) { - break; - } - } else { - $evaluated[] = $name; - } - } - - $context->addEvaluatedProperties($evaluated); - - if (!$errors->isEmpty()) { - return $this->error($schema, $context, 'properties', "The properties must match schema: {properties}", [ - 'properties' => array_values(array_diff($checked, $evaluated)) - ], $errors); - } - unset($errors); - - $context->addCheckedProperties($checked); - - return null; - } +class PropertiesKeyword implements Keyword { + + use IterableDataValidationTrait; + + /** + * @var mixed[] + */ + protected $properties; + /** + * @var mixed[] + */ + protected $propertyKeys; + + /** + * @param array $properties + */ + public function __construct( array $properties ) { + $this->properties = $properties; + $this->propertyKeys = array_keys( $properties ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( ! $this->properties ) { + return null; + } + + $checked = array(); + $evaluated = array(); + + $data = $context->currentData(); + + $errors = $this->errorContainer( $context->maxErrors() ); + + foreach ( $this->properties as $name => $prop ) { + if ( ! property_exists( $data, $name ) ) { + continue; + } + + $checked[] = $name; + + if ( $prop === true ) { + $evaluated[] = $name; + continue; + } + + if ( $prop === false ) { + $context->addEvaluatedProperties( $evaluated ); + return $this->error( + $schema, + $context, + 'properties', + "Property '{property}' is not allowed", + array( + 'property' => $name, + ) + ); + } + + if ( is_object( $prop ) && ! ( $prop instanceof Schema ) ) { + $prop = $this->properties[ $name ] = $context->loader()->loadObjectSchema( $prop ); + } + + $context->pushDataPath( $name ); + $error = $prop->validate( $context ); + $context->popDataPath(); + + if ( $error ) { + $errors->add( $error ); + if ( $errors->isFull() ) { + break; + } + } else { + $evaluated[] = $name; + } + } + + $context->addEvaluatedProperties( $evaluated ); + + if ( ! $errors->isEmpty() ) { + return $this->error( + $schema, + $context, + 'properties', + 'The properties must match schema: {properties}', + array( + 'properties' => array_values( array_diff( $checked, $evaluated ) ), + ), + $errors + ); + } + unset( $errors ); + + $context->addCheckedProperties( $checked ); + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/PropertyNamesKeyword.php b/src/opis/json-schema/src/Keywords/PropertyNamesKeyword.php index 6ba547fe..99c1d081 100644 --- a/src/opis/json-schema/src/Keywords/PropertyNamesKeyword.php +++ b/src/opis/json-schema/src/Keywords/PropertyNamesKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var bool|object */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - if ($this->value === true) { - return null; - } + /** + * @param bool|object $value + */ + public function __construct( $value ) { + $this->value = $value; + } - $props = $context->getObjectProperties(); - if (!$props) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + if ( $this->value === true ) { + return null; + } - if ($this->value === false) { - return $this->error($schema, $context, 'propertyNames', "No properties are allowed"); - } + $props = $context->getObjectProperties(); + if ( ! $props ) { + return null; + } - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } + if ( $this->value === false ) { + return $this->error( $schema, $context, 'propertyNames', 'No properties are allowed' ); + } - foreach ($props as $prop) { - if ($error = $this->value->validate($context->newInstance($prop, $schema))) { - return $this->error($schema, $context, 'propertyNames', "Property '{property}' must match schema", [ - 'property' => $prop, - ], $error); - } - } + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } - return null; - } + foreach ( $props as $prop ) { + if ( $error = $this->value->validate( $context->newInstance( $prop, $schema ) ) ) { + return $this->error( + $schema, + $context, + 'propertyNames', + "Property '{property}' must match schema", + array( + 'property' => $prop, + ), + $error + ); + } + } + + return null; + } } diff --git a/src/opis/json-schema/src/Keywords/RecursiveRefKeyword.php b/src/opis/json-schema/src/Keywords/RecursiveRefKeyword.php index 25a6610d..a2913899 100644 --- a/src/opis/json-schema/src/Keywords/RecursiveRefKeyword.php +++ b/src/opis/json-schema/src/Keywords/RecursiveRefKeyword.php @@ -1,5 +1,6 @@ uri = $uri; - $this->anchor = $anchor; - $this->anchorValue = $anchorValue; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function doValidate($context, $schema) - { - if ($this->resolved === false) { - $this->resolved = $context->loader()->loadSchemaById($this->uri); - } - - if ($this->resolved === null) { - throw new UnresolvedReferenceException((string)$this->uri, $schema, $context); - } - - $new_context = $this->createContext($context, $schema); - - if (!$this->hasRecursiveAnchor($this->resolved)) { - $this->setLastRefSchema($this->resolved); - return $this->resolved->validate($new_context); - } - - $ok_sender = $this->resolveSchema($context); - - if (!$ok_sender) { - $this->setLastRefSchema($this->resolved); - return $this->resolved->validate($new_context); - } - - $this->setLastRefSchema($ok_sender); - - return $ok_sender->validate($new_context); - } - - /** - * @param \Opis\JsonSchema\ValidationContext $context - */ - protected function resolveSchema($context) - { - $ok = null; - $loader = $context->loader(); - - while ($context) { - $sender = $context->sender(); - - if (!$sender) { - break; - } - - if (!$this->hasRecursiveAnchor($sender)) { - if ($sender->info()->id()) { - // id without recursiveAnchor - break; - } - - $sender = $loader->loadSchemaById($sender->info()->root()); - if (!$sender || !$this->hasRecursiveAnchor($sender)) { - // root without recursiveAnchor - break; - } - } - - if ($sender->info()->id()) { - // id with recursiveAnchor - $ok = $sender; - } else { - // root with recursiveAnchor - $ok = $loader->loadSchemaById($sender->info()->root()); - } - - $context = $context->parent(); - } - - return $ok; - } - - /** - * @param \Opis\JsonSchema\Schema|null $schema - */ - protected function hasRecursiveAnchor($schema): bool - { - if (!$schema) { - return false; - } - - $info = $schema->info(); - - if (!$info->isObject()) { - return false; - } - - $data = $info->data(); - - if (!property_exists($data, $this->anchor)) { - return false; - } - - return $data->{$this->anchor} === $this->anchorValue; - } +class RecursiveRefKeyword extends AbstractRefKeyword { + + /** + * @var \Opis\JsonSchema\Uri + */ + protected $uri; + /** @var bool|null|Schema */ + protected $resolved = false; + /** + * @var string + */ + protected $anchor; + protected $anchorValue; + + public function __construct( + Uri $uri, + $mapper, + $globals, + $slots = null, + string $keyword = '$recursiveRef', + string $anchor = '$recursiveAnchor', + $anchorValue = true + ) { + parent::__construct( $mapper, $globals, $slots, $keyword ); + $this->uri = $uri; + $this->anchor = $anchor; + $this->anchorValue = $anchorValue; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function doValidate( $context, $schema ) { + if ( $this->resolved === false ) { + $this->resolved = $context->loader()->loadSchemaById( $this->uri ); + } + + if ( $this->resolved === null ) { + throw new UnresolvedReferenceException( (string) $this->uri, $schema, $context ); + } + + $new_context = $this->createContext( $context, $schema ); + + if ( ! $this->hasRecursiveAnchor( $this->resolved ) ) { + $this->setLastRefSchema( $this->resolved ); + return $this->resolved->validate( $new_context ); + } + + $ok_sender = $this->resolveSchema( $context ); + + if ( ! $ok_sender ) { + $this->setLastRefSchema( $this->resolved ); + return $this->resolved->validate( $new_context ); + } + + $this->setLastRefSchema( $ok_sender ); + + return $ok_sender->validate( $new_context ); + } + + /** + * @param \Opis\JsonSchema\ValidationContext $context + */ + protected function resolveSchema( $context ) { + $ok = null; + $loader = $context->loader(); + + while ( $context ) { + $sender = $context->sender(); + + if ( ! $sender ) { + break; + } + + if ( ! $this->hasRecursiveAnchor( $sender ) ) { + if ( $sender->info()->id() ) { + // id without recursiveAnchor + break; + } + + $sender = $loader->loadSchemaById( $sender->info()->root() ); + if ( ! $sender || ! $this->hasRecursiveAnchor( $sender ) ) { + // root without recursiveAnchor + break; + } + } + + if ( $sender->info()->id() ) { + // id with recursiveAnchor + $ok = $sender; + } else { + // root with recursiveAnchor + $ok = $loader->loadSchemaById( $sender->info()->root() ); + } + + $context = $context->parent(); + } + + return $ok; + } + + /** + * @param \Opis\JsonSchema\Schema|null $schema + */ + protected function hasRecursiveAnchor( $schema ): bool { + if ( ! $schema ) { + return false; + } + + $info = $schema->info(); + + if ( ! $info->isObject() ) { + return false; + } + + $data = $info->data(); + + if ( ! property_exists( $data, $this->anchor ) ) { + return false; + } + + return $data->{$this->anchor} === $this->anchorValue; + } } diff --git a/src/opis/json-schema/src/Keywords/RequiredDataKeyword.php b/src/opis/json-schema/src/Keywords/RequiredDataKeyword.php index 672be71d..5606c813 100644 --- a/src/opis/json-schema/src/Keywords/RequiredDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/RequiredDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - $this->filter = $filter; - parent::__construct([]); - } + /** @var callable|null */ + protected $filter; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $required = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - if ($required === $this || !is_array($required) || !$this->requiredPropsAreValid($required)) { - return $this->error($schema, $context, 'required', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } + /** + * @param JsonPointer $value + * @param callable|null $filter + */ + public function __construct( JsonPointer $value, $filter = null ) { + $this->value = $value; + $this->filter = $filter; + parent::__construct( array() ); + } - $required = array_unique($required); + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $required = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + if ( $required === $this || ! is_array( $required ) || ! $this->requiredPropsAreValid( $required ) ) { + return $this->error( + $schema, + $context, + 'required', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } - if ($this->filter) { - $required = array_filter($required, $this->filter === null ? function ($value, $key) : bool { - return !empty($value); - } : $this->filter, $this->filter === null ? ARRAY_FILTER_USE_BOTH : 0); - } + $required = array_unique( $required ); - if (!$required) { - return null; - } + if ( $this->filter ) { + $required = array_filter( + $required, + $this->filter === null ? function ( $value, $key ): bool { + return ! empty( $value ); + } : $this->filter, + $this->filter === null ? ARRAY_FILTER_USE_BOTH : 0 + ); + } - $this->required = $required; - $ret = parent::validate($context, $schema); - $this->required = null; + if ( ! $required ) { + return null; + } - return $ret; - } + $this->required = $required; + $ret = parent::validate( $context, $schema ); + $this->required = null; - /** - * @param array $props - * @return bool - */ - protected function requiredPropsAreValid($props): bool - { - foreach ($props as $prop) { - if (!is_string($prop)) { - return false; - } - } + return $ret; + } - return true; - } + /** + * @param array $props + * @return bool + */ + protected function requiredPropsAreValid( $props ): bool { + foreach ( $props as $prop ) { + if ( ! is_string( $prop ) ) { + return false; + } + } + + return true; + } } diff --git a/src/opis/json-schema/src/Keywords/RequiredKeyword.php b/src/opis/json-schema/src/Keywords/RequiredKeyword.php index ed10c5a7..6d0c7ce2 100644 --- a/src/opis/json-schema/src/Keywords/RequiredKeyword.php +++ b/src/opis/json-schema/src/Keywords/RequiredKeyword.php @@ -1,5 +1,6 @@ required = $required; - } + /** @var string[] */ + protected $required; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $data = $context->currentData(); - $max = $context->maxErrors(); - $list = []; + /** + * @param string[] $required + */ + public function __construct( array $required ) { + $this->required = $required; + } - foreach ($this->required as $name) { - if (!property_exists($data, $name)) { - $list[] = $name; - if (--$max <= 0) { - break; - } - } - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $data = $context->currentData(); + $max = $context->maxErrors(); + $list = array(); - if (!$list) { - return null; - } + foreach ( $this->required as $name ) { + if ( ! property_exists( $data, $name ) ) { + $list[] = $name; + if ( --$max <= 0 ) { + break; + } + } + } - return $this->error($schema, $context, 'required', 'The required properties ({missing}) are missing', [ - 'missing' => $list, - ]); - } + if ( ! $list ) { + return null; + } + + return $this->error( + $schema, + $context, + 'required', + 'The required properties ({missing}) are missing', + array( + 'missing' => $list, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/SlotsKeyword.php b/src/opis/json-schema/src/Keywords/SlotsKeyword.php index 2b27fef8..ed0f208c 100644 --- a/src/opis/json-schema/src/Keywords/SlotsKeyword.php +++ b/src/opis/json-schema/src/Keywords/SlotsKeyword.php @@ -1,5 +1,6 @@ slots = $slots; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $newContext = $context->newInstance($context->currentData(), $schema); - - foreach ($this->slots as $name => $fallback) { - $slot = $this->resolveSlotSchema($name, $context); - - if ($slot === null) { - $save = true; - if (is_string($fallback)) { - $save = false; - $fallback = $this->resolveSlot($fallback, $context); - } - - if ($fallback === true) { - continue; - } - - if ($fallback === false) { - return $this->error($schema, $context, '$slots', "Required slot '{slot}' is missing", [ - 'slot' => $name, - ]); - } - - if (is_object($fallback) && !($fallback instanceof Schema)) { - $fallback = $context->loader()->loadObjectSchema($fallback); - if ($save) { - $this->slots[$name] = $fallback; - } - } - - $slot = $fallback; - } - - if ($error = $slot->validate($newContext)) { - return $this->error($schema, $context,'$slots', "Schema for slot '{slot}' was not matched", [ - 'slot' => $name, - ], $error); - } - } - - return null; - } - - /** - * @param string $name - * @param ValidationContext $context - * @return Schema|null - */ - protected function resolveSlotSchema($name, $context) - { - do { - $slot = $context->slot($name); - } while ($slot === null && $context = $context->parent()); - - return $slot; - } - - /** - * @param string $name - * @param ValidationContext $context - * @return bool|Schema - */ - protected function resolveSlot($name, $context) - { - $slot = $this->resolveSlotSchema($name, $context); - - if ($slot !== null) { - return $slot; - } - - if (!isset($this->slots[$name])) { - return false; - } - - $slot = $this->slots[$name]; - - if (is_bool($slot)) { - return $slot; - } - - if (is_object($slot)) { - if ($slot instanceof Schema) { - return $slot; - } - - $slot = $context->loader()->loadObjectSchema($slot); - $this->slots[$name] = $slot; - return $slot; - } - - if (!is_string($slot)) { - // Looks like the slot is missing - return false; - } - - if (in_array($slot, $this->stack)) { - // Recursive - return false; - } - - $this->stack[] = $slot; - $slot = $this->resolveSlot($slot, $context); - array_pop($this->stack); - - return $slot; - } +class SlotsKeyword implements Keyword { + + use ErrorTrait; + + /** @var bool[]|Schema[]|object[]|string[] */ + protected $slots; + + /** @var string[] */ + protected $stack = array(); + + /** + * @param string[]|bool[]|object[]|Schema[] $slots + */ + public function __construct( array $slots ) { + $this->slots = $slots; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $newContext = $context->newInstance( $context->currentData(), $schema ); + + foreach ( $this->slots as $name => $fallback ) { + $slot = $this->resolveSlotSchema( $name, $context ); + + if ( $slot === null ) { + $save = true; + if ( is_string( $fallback ) ) { + $save = false; + $fallback = $this->resolveSlot( $fallback, $context ); + } + + if ( $fallback === true ) { + continue; + } + + if ( $fallback === false ) { + return $this->error( + $schema, + $context, + '$slots', + "Required slot '{slot}' is missing", + array( + 'slot' => $name, + ) + ); + } + + if ( is_object( $fallback ) && ! ( $fallback instanceof Schema ) ) { + $fallback = $context->loader()->loadObjectSchema( $fallback ); + if ( $save ) { + $this->slots[ $name ] = $fallback; + } + } + + $slot = $fallback; + } + + if ( $error = $slot->validate( $newContext ) ) { + return $this->error( + $schema, + $context, + '$slots', + "Schema for slot '{slot}' was not matched", + array( + 'slot' => $name, + ), + $error + ); + } + } + + return null; + } + + /** + * @param string $name + * @param ValidationContext $context + * @return Schema|null + */ + protected function resolveSlotSchema( $name, $context ) { + do { + $slot = $context->slot( $name ); + } while ( $slot === null && $context = $context->parent() ); + + return $slot; + } + + /** + * @param string $name + * @param ValidationContext $context + * @return bool|Schema + */ + protected function resolveSlot( $name, $context ) { + $slot = $this->resolveSlotSchema( $name, $context ); + + if ( $slot !== null ) { + return $slot; + } + + if ( ! isset( $this->slots[ $name ] ) ) { + return false; + } + + $slot = $this->slots[ $name ]; + + if ( is_bool( $slot ) ) { + return $slot; + } + + if ( is_object( $slot ) ) { + if ( $slot instanceof Schema ) { + return $slot; + } + + $slot = $context->loader()->loadObjectSchema( $slot ); + $this->slots[ $name ] = $slot; + return $slot; + } + + if ( ! is_string( $slot ) ) { + // Looks like the slot is missing + return false; + } + + if ( in_array( $slot, $this->stack ) ) { + // Recursive + return false; + } + + $this->stack[] = $slot; + $slot = $this->resolveSlot( $slot, $context ); + array_pop( $this->stack ); + + return $slot; + } } diff --git a/src/opis/json-schema/src/Keywords/TemplateRefKeyword.php b/src/opis/json-schema/src/Keywords/TemplateRefKeyword.php index 3ba07048..5414da10 100644 --- a/src/opis/json-schema/src/Keywords/TemplateRefKeyword.php +++ b/src/opis/json-schema/src/Keywords/TemplateRefKeyword.php @@ -1,5 +1,6 @@ template = $template; - $this->vars = $vars; - $this->allowRelativeJsonPointerInRef = $allowRelativeJsonPointerInRef; - } - - /** - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - protected function doValidate($context, $schema) - { - if ($this->vars) { - $vars = $this->vars->resolve($context->rootData(), $context->currentDataPath()); - if (!is_array($vars)) { - $vars = (array)$vars; - } - $vars += $context->globals(); - } else { - $vars = $context->globals(); - } - - $ref = $this->template->resolve($vars); - - $key = isset($ref[32]) ? md5($ref) : $ref; - - if (!array_key_exists($key, $this->cached)) { - $this->cached[$key] = $this->resolveRef($ref, $context->loader(), $schema); - } - - $resolved = $this->cached[$key]; - unset($key); - - if (!$resolved) { - throw new UnresolvedReferenceException($ref, $schema, $context); - } - - return $resolved->validate($this->createContext($context, $schema)); - } - - /** - * @param string $ref - * @param SchemaLoader $repo - * @param Schema $schema - * @return null|Schema - */ - protected function resolveRef($ref, $repo, $schema) - { - if ($ref === '') { - return null; - } - - $baseUri = $schema->info()->idBaseRoot(); - - if ($ref === '#') { - return $repo->loadSchemaById($baseUri); - } - - // Check if is pointer - if ($ref[0] === '#') { - if ($pointer = JsonPointer::parse(substr($ref, 1))) { - if ($pointer->isAbsolute()) { - return $this->resolvePointer($repo, $pointer, $baseUri); - } - unset($pointer); - } - } elseif ($this->allowRelativeJsonPointerInRef && ($pointer = JsonPointer::parse($ref))) { - if ($pointer->isRelative()) { - return $this->resolvePointer($repo, $pointer, $baseUri, $schema->info()->path()); - } - unset($pointer); - } - - $ref = Uri::merge($ref, $baseUri, true); - - if ($ref === null || !$ref->isAbsolute()) { - return null; - } - - return $repo->loadSchemaById($ref); - } +class TemplateRefKeyword extends AbstractRefKeyword { + + /** + * @var \Opis\Uri\UriTemplate + */ + protected $template; + /** + * @var \Opis\JsonSchema\Variables|null + */ + protected $vars; + /** @var Schema[]|null[] */ + protected $cached = array(); + /** + * @var bool + */ + protected $allowRelativeJsonPointerInRef; + + public function __construct( + UriTemplate $template, + $vars, + $mapper = null, + $globals = null, + $slots = null, + string $keyword = '$ref', + bool $allowRelativeJsonPointerInRef = true + ) { + parent::__construct( $mapper, $globals, $slots, $keyword ); + $this->template = $template; + $this->vars = $vars; + $this->allowRelativeJsonPointerInRef = $allowRelativeJsonPointerInRef; + } + + /** + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + protected function doValidate( $context, $schema ) { + if ( $this->vars ) { + $vars = $this->vars->resolve( $context->rootData(), $context->currentDataPath() ); + if ( ! is_array( $vars ) ) { + $vars = (array) $vars; + } + $vars += $context->globals(); + } else { + $vars = $context->globals(); + } + + $ref = $this->template->resolve( $vars ); + + $key = isset( $ref[32] ) ? md5( $ref ) : $ref; + + if ( ! array_key_exists( $key, $this->cached ) ) { + $this->cached[ $key ] = $this->resolveRef( $ref, $context->loader(), $schema ); + } + + $resolved = $this->cached[ $key ]; + unset( $key ); + + if ( ! $resolved ) { + throw new UnresolvedReferenceException( $ref, $schema, $context ); + } + + return $resolved->validate( $this->createContext( $context, $schema ) ); + } + + /** + * @param string $ref + * @param SchemaLoader $repo + * @param Schema $schema + * @return null|Schema + */ + protected function resolveRef( $ref, $repo, $schema ) { + if ( $ref === '' ) { + return null; + } + + $baseUri = $schema->info()->idBaseRoot(); + + if ( $ref === '#' ) { + return $repo->loadSchemaById( $baseUri ); + } + + // Check if is pointer + if ( $ref[0] === '#' ) { + if ( $pointer = JsonPointer::parse( substr( $ref, 1 ) ) ) { + if ( $pointer->isAbsolute() ) { + return $this->resolvePointer( $repo, $pointer, $baseUri ); + } + unset( $pointer ); + } + } elseif ( $this->allowRelativeJsonPointerInRef && ( $pointer = JsonPointer::parse( $ref ) ) ) { + if ( $pointer->isRelative() ) { + return $this->resolvePointer( $repo, $pointer, $baseUri, $schema->info()->path() ); + } + unset( $pointer ); + } + + $ref = Uri::merge( $ref, $baseUri, true ); + + if ( $ref === null || ! $ref->isAbsolute() ) { + return null; + } + + return $repo->loadSchemaById( $ref ); + } } diff --git a/src/opis/json-schema/src/Keywords/TypeKeyword.php b/src/opis/json-schema/src/Keywords/TypeKeyword.php index 999f5bd8..58f8b5ac 100644 --- a/src/opis/json-schema/src/Keywords/TypeKeyword.php +++ b/src/opis/json-schema/src/Keywords/TypeKeyword.php @@ -1,5 +1,6 @@ type = $type; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $type = $context->currentDataType(); - if ($type && Helper::jsonTypeMatches($type, $this->type)) { - return null; - } - - return $this->error($schema, $context, 'type', 'The data ({type}) must match the type: {expected}', [ - 'expected' => $this->type, - 'type' => $type, - ]); - } +class TypeKeyword implements Keyword { + + use ErrorTrait; + + /** @var string|string[] */ + protected $type; + + /** + * @param string|string[] $type + */ + public function __construct( $type ) { + $this->type = $type; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $type = $context->currentDataType(); + if ( $type && Helper::jsonTypeMatches( $type, $this->type ) ) { + return null; + } + + return $this->error( + $schema, + $context, + 'type', + 'The data ({type}) must match the type: {expected}', + array( + 'expected' => $this->type, + 'type' => $type, + ) + ); + } } diff --git a/src/opis/json-schema/src/Keywords/URIRefKeyword.php b/src/opis/json-schema/src/Keywords/URIRefKeyword.php index 32126f5e..6ff41364 100644 --- a/src/opis/json-schema/src/Keywords/URIRefKeyword.php +++ b/src/opis/json-schema/src/Keywords/URIRefKeyword.php @@ -1,5 +1,6 @@ uri = $uri; - } - - /** - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - protected function doValidate($context, $schema) - { - if ($this->resolved === false) { - $this->resolved = $context->loader()->loadSchemaById($this->uri); - } - - if ($this->resolved === null) { - throw new UnresolvedReferenceException((string)$this->uri, $schema, $context); - } - - $this->setLastRefSchema($this->resolved); - - return $this->resolved->validate($this->createContext($context, $schema)); - } +class URIRefKeyword extends AbstractRefKeyword { + + /** + * @var \Opis\JsonSchema\Uri + */ + protected $uri; + /** @var bool|null|Schema */ + protected $resolved = false; + + public function __construct( + Uri $uri, + $mapper, + $globals, + $slots = null, + string $keyword = '$ref' + ) { + parent::__construct( $mapper, $globals, $slots, $keyword ); + $this->uri = $uri; + } + + /** + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + protected function doValidate( $context, $schema ) { + if ( $this->resolved === false ) { + $this->resolved = $context->loader()->loadSchemaById( $this->uri ); + } + + if ( $this->resolved === null ) { + throw new UnresolvedReferenceException( (string) $this->uri, $schema, $context ); + } + + $this->setLastRefSchema( $this->resolved ); + + return $this->resolved->validate( $this->createContext( $context, $schema ) ); + } } diff --git a/src/opis/json-schema/src/Keywords/UnevaluatedItemsKeyword.php b/src/opis/json-schema/src/Keywords/UnevaluatedItemsKeyword.php index c5db7abd..c238e688 100644 --- a/src/opis/json-schema/src/Keywords/UnevaluatedItemsKeyword.php +++ b/src/opis/json-schema/src/Keywords/UnevaluatedItemsKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $unevaluated = $context->getUnevaluatedItems(); + public function __construct( $value ) { + $this->value = $value; + } - if (!$unevaluated) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $unevaluated = $context->getUnevaluatedItems(); - if ($this->value === true) { - $context->addEvaluatedItems($unevaluated); - return null; - } + if ( ! $unevaluated ) { + return null; + } - if ($this->value === false) { - return $this->error($schema, $context, 'unevaluatedItems', - 'Unevaluated array items are not allowed: {indexes}', [ - 'indexes' => $unevaluated, - ]); - } + if ( $this->value === true ) { + $context->addEvaluatedItems( $unevaluated ); + return null; + } - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } + if ( $this->value === false ) { + return $this->error( + $schema, + $context, + 'unevaluatedItems', + 'Unevaluated array items are not allowed: {indexes}', + array( + 'indexes' => $unevaluated, + ) + ); + } - $object = $this->createArrayObject($context); + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } - $error = $this->validateIterableData($schema, $this->value, $context, $unevaluated, - 'unevaluatedItems', 'All unevaluated array items must match schema: {indexes}', [ - 'indexes' => $unevaluated, - ], $object); + $object = $this->createArrayObject( $context ); - if ($object && $object->count()) { - $context->addEvaluatedItems($object->getArrayCopy()); - } + $error = $this->validateIterableData( + $schema, + $this->value, + $context, + $unevaluated, + 'unevaluatedItems', + 'All unevaluated array items must match schema: {indexes}', + array( + 'indexes' => $unevaluated, + ), + $object + ); - return $error; - } + if ( $object && $object->count() ) { + $context->addEvaluatedItems( $object->getArrayCopy() ); + } + + return $error; + } } diff --git a/src/opis/json-schema/src/Keywords/UnevaluatedPropertiesKeyword.php b/src/opis/json-schema/src/Keywords/UnevaluatedPropertiesKeyword.php index 2cc192c0..9f2f148b 100644 --- a/src/opis/json-schema/src/Keywords/UnevaluatedPropertiesKeyword.php +++ b/src/opis/json-schema/src/Keywords/UnevaluatedPropertiesKeyword.php @@ -1,5 +1,6 @@ value = $value; - } + /** @var bool|object|Schema */ + protected $value; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $unevaluated = $context->getUnevaluatedProperties(); + public function __construct( $value ) { + $this->value = $value; + } - if (!$unevaluated) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $unevaluated = $context->getUnevaluatedProperties(); - if ($this->value === true) { - $context->addEvaluatedProperties($unevaluated); - return null; - } + if ( ! $unevaluated ) { + return null; + } - if ($this->value === false) { - return $this->error($schema, $context, 'unevaluatedProperties', - 'Unevaluated object properties not allowed: {properties}', [ - 'properties' => $unevaluated, - ]); - } + if ( $this->value === true ) { + $context->addEvaluatedProperties( $unevaluated ); + return null; + } - if (is_object($this->value) && !($this->value instanceof Schema)) { - $this->value = $context->loader()->loadObjectSchema($this->value); - } + if ( $this->value === false ) { + return $this->error( + $schema, + $context, + 'unevaluatedProperties', + 'Unevaluated object properties not allowed: {properties}', + array( + 'properties' => $unevaluated, + ) + ); + } - $object = $this->createArrayObject($context); + if ( is_object( $this->value ) && ! ( $this->value instanceof Schema ) ) { + $this->value = $context->loader()->loadObjectSchema( $this->value ); + } - $error = $this->validateIterableData($schema, $this->value, $context, $unevaluated, - 'unevaluatedProperties', 'All unevaluated object properties must match schema: {properties}', [ - 'properties' => $unevaluated, - ], $object); + $object = $this->createArrayObject( $context ); + $error = $this->validateIterableData( + $schema, + $this->value, + $context, + $unevaluated, + 'unevaluatedProperties', + 'All unevaluated object properties must match schema: {properties}', + array( + 'properties' => $unevaluated, + ), + $object + ); - if ($object && $object->count()) { - $context->addEvaluatedProperties($object->getArrayCopy()); - } + if ( $object && $object->count() ) { + $context->addEvaluatedProperties( $object->getArrayCopy() ); + } - return $error; - } + return $error; + } } diff --git a/src/opis/json-schema/src/Keywords/UniqueItemsDataKeyword.php b/src/opis/json-schema/src/Keywords/UniqueItemsDataKeyword.php index 79cb925f..c08214bb 100644 --- a/src/opis/json-schema/src/Keywords/UniqueItemsDataKeyword.php +++ b/src/opis/json-schema/src/Keywords/UniqueItemsDataKeyword.php @@ -1,5 +1,6 @@ value = $value; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - * @param \Opis\JsonSchema\Schema $schema - */ - public function validate($context, $schema) - { - $value = $this->value->data($context->rootData(), $context->currentDataPath(), $this); - - if ($value === $this || !is_bool($value)) { - return $this->error($schema, $context, 'uniqueItems', 'Invalid $data', [ - 'pointer' => (string)$this->value, - ]); - } - - return $value ? parent::validate($context, $schema) : null; - } +class UniqueItemsDataKeyword extends UniqueItemsKeyword { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + protected $value; + + /** + * @param JsonPointer $value + */ + public function __construct( JsonPointer $value ) { + $this->value = $value; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $value = $this->value->data( $context->rootData(), $context->currentDataPath(), $this ); + + if ( $value === $this || ! is_bool( $value ) ) { + return $this->error( + $schema, + $context, + 'uniqueItems', + 'Invalid $data', + array( + 'pointer' => (string) $this->value, + ) + ); + } + + return $value ? parent::validate( $context, $schema ) : null; + } } diff --git a/src/opis/json-schema/src/Keywords/UniqueItemsKeyword.php b/src/opis/json-schema/src/Keywords/UniqueItemsKeyword.php index 8582eddb..b787645c 100644 --- a/src/opis/json-schema/src/Keywords/UniqueItemsKeyword.php +++ b/src/opis/json-schema/src/Keywords/UniqueItemsKeyword.php @@ -1,5 +1,6 @@ currentData(); - if (!$data) { - return null; - } - - $count = count($data); - - for ($i = 0; $i < $count - 1; $i++) { - for ($j = $i + 1; $j < $count; $j++) { - if (Helper::equals($data[$i], $data[$j])) { - return $this->error($schema, $context, 'uniqueItems', 'Array must have unique items', [ - 'duplicate' => $data[$i], - 'indexes' => [$i, $j], - ]); - } - } - } - - return null; - } +class UniqueItemsKeyword implements Keyword { + + use ErrorTrait; + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + * @param \Opis\JsonSchema\Schema $schema + */ + public function validate( $context, $schema ) { + $data = $context->currentData(); + if ( ! $data ) { + return null; + } + + $count = count( $data ); + + for ( $i = 0; $i < $count - 1; $i++ ) { + for ( $j = $i + 1; $j < $count; $j++ ) { + if ( Helper::equals( $data[ $i ], $data[ $j ] ) ) { + return $this->error( + $schema, + $context, + 'uniqueItems', + 'Array must have unique items', + array( + 'duplicate' => $data[ $i ], + 'indexes' => array( $i, $j ), + ) + ); + } + } + } + + return null; + } } diff --git a/src/opis/json-schema/src/Parsers/DataKeywordTrait.php b/src/opis/json-schema/src/Parsers/DataKeywordTrait.php index 5e43ae1d..8f6ab4eb 100644 --- a/src/opis/json-schema/src/Parsers/DataKeywordTrait.php +++ b/src/opis/json-schema/src/Parsers/DataKeywordTrait.php @@ -1,5 +1,6 @@ {'$data'}) || count(get_object_vars($value)) !== 1) { - return null; - } - - return JsonPointer::parse($value->{'$data'}); - } - - /** - * @param SchemaParser $parser - * @param string|null $keyword - * @return bool - */ - protected function isDataKeywordAllowed($parser, $keyword = null): bool - { - if (!($enabled = $parser->option('allowDataKeyword'))) { - return false; - } - - if ($enabled === true) { - return true; - } - - if ($keyword === null) { - return false; - } - - return is_array($enabled) && in_array($keyword, $enabled); - } +trait DataKeywordTrait { + + /** + * @param $value + * @return JsonPointer|null + */ + protected function getDataKeywordPointer( $value ) { + if ( ! is_object( $value ) || ! property_exists( $value, '$data' ) || + ! is_string( $value->{'$data'} ) || count( get_object_vars( $value ) ) !== 1 ) { + return null; + } + + return JsonPointer::parse( $value->{'$data'} ); + } + + /** + * @param SchemaParser $parser + * @param string|null $keyword + * @return bool + */ + protected function isDataKeywordAllowed( $parser, $keyword = null ): bool { + if ( ! ( $enabled = $parser->option( 'allowDataKeyword' ) ) ) { + return false; + } + + if ( $enabled === true ) { + return true; + } + + if ( $keyword === null ) { + return false; + } + + return is_array( $enabled ) && in_array( $keyword, $enabled ); + } } diff --git a/src/opis/json-schema/src/Parsers/DefaultVocabulary.php b/src/opis/json-schema/src/Parsers/DefaultVocabulary.php index d05d4280..8349fe21 100644 --- a/src/opis/json-schema/src/Parsers/DefaultVocabulary.php +++ b/src/opis/json-schema/src/Parsers/DefaultVocabulary.php @@ -1,5 +1,6 @@ getKeywordParsers(); - $keywordValidators = $this->getKeywordValidatorParsers(); - $pragmas = $this->getPragmaParsers(); +abstract class Draft extends Vocabulary { - if ($extraVocabulary) { - $keywords = array_merge($keywords, $extraVocabulary->keywords()); - $keywordValidators = array_merge($keywordValidators, $extraVocabulary->keywordValidators()); - $pragmas = array_merge($pragmas, $extraVocabulary->pragmas()); - } + /** + * @param Vocabulary|null $extraVocabulary + */ + public function __construct( $extraVocabulary = null ) { + $keywords = $this->getKeywordParsers(); + $keywordValidators = $this->getKeywordValidatorParsers(); + $pragmas = $this->getPragmaParsers(); - array_unshift($keywords, $this->getRefKeywordParser()); + if ( $extraVocabulary ) { + $keywords = array_merge( $keywords, $extraVocabulary->keywords() ); + $keywordValidators = array_merge( $keywordValidators, $extraVocabulary->keywordValidators() ); + $pragmas = array_merge( $pragmas, $extraVocabulary->pragmas() ); + } - parent::__construct($keywords, $keywordValidators, $pragmas); - } + array_unshift( $keywords, $this->getRefKeywordParser() ); - /** - * @return string - */ - abstract public function version(): string; + parent::__construct( $keywords, $keywordValidators, $pragmas ); + } - /** - * @return bool - */ - abstract public function allowKeywordsAlongsideRef(): bool; + /** + * @return string + */ + abstract public function version(): string; - /** - * @return bool - */ - abstract public function supportsAnchorId(): bool; + /** + * @return bool + */ + abstract public function allowKeywordsAlongsideRef(): bool; - /** - * @return KeywordParser - */ - abstract protected function getRefKeywordParser(): KeywordParser; + /** + * @return bool + */ + abstract public function supportsAnchorId(): bool; - /** - * @return KeywordParser[] - */ - abstract protected function getKeywordParsers(): array; + /** + * @return KeywordParser + */ + abstract protected function getRefKeywordParser(): KeywordParser; - /** - * @return KeywordValidatorParser[] - */ - protected function getKeywordValidatorParsers(): array - { - return []; - } + /** + * @return KeywordParser[] + */ + abstract protected function getKeywordParsers(): array; - /** - * @return PragmaParser[] - */ - protected function getPragmaParsers(): array - { - return []; - } + /** + * @return KeywordValidatorParser[] + */ + protected function getKeywordValidatorParsers(): array { + return array(); + } + + /** + * @return PragmaParser[] + */ + protected function getPragmaParsers(): array { + return array(); + } } diff --git a/src/opis/json-schema/src/Parsers/DraftOptionTrait.php b/src/opis/json-schema/src/Parsers/DraftOptionTrait.php index 22e28153..6511fa1b 100644 --- a/src/opis/json-schema/src/Parsers/DraftOptionTrait.php +++ b/src/opis/json-schema/src/Parsers/DraftOptionTrait.php @@ -1,5 +1,6 @@ option($option); - - if (!$value) { - return false; - } - - if ($value === true) { - return true; - } - - if (is_array($value)) { - return in_array($info->draft(), $value); - } - - return $value === $info->draft(); - } -} \ No newline at end of file +trait DraftOptionTrait { + + /** + * @param string $option + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + */ + protected function optionAllowedForDraft( $option, $info, $parser ): bool { + $value = $parser->option( $option ); + + if ( ! $value ) { + return false; + } + + if ( $value === true ) { + return true; + } + + if ( is_array( $value ) ) { + return in_array( $info->draft(), $value ); + } + + return $value === $info->draft(); + } +} diff --git a/src/opis/json-schema/src/Parsers/Drafts/Draft06.php b/src/opis/json-schema/src/Parsers/Drafts/Draft06.php index f8a48343..4ee16951 100644 --- a/src/opis/json-schema/src/Parsers/Drafts/Draft06.php +++ b/src/opis/json-schema/src/Parsers/Drafts/Draft06.php @@ -1,5 +1,6 @@ '$recursiveRef', 'anchor' => '$recursiveAnchor', 'fragment' => false], - ]); - } + /** + * @inheritDoc + */ + public function supportsAnchorId(): bool { + return true; + } - /** - * @inheritDoc - */ - protected function getKeywordParsers(): array - { - return [ - // Generic - new TypeKeywordParser('type'), - new ConstKeywordParser('const'), - new EnumKeywordParser('enum'), - new FormatKeywordParser('format'), + /** + * @inheritDoc + */ + protected function getRefKeywordParser(): KeywordParser { + return new RefKeywordParser( + '$ref', + array( + array( + 'ref' => '$recursiveRef', + 'anchor' => '$recursiveAnchor', + 'fragment' => false, + ), + ) + ); + } - // String - new MinLengthKeywordParser('minLength'), - new MaxLengthKeywordParser('maxLength'), - new PatternKeywordParser("pattern"), - new ContentEncodingKeywordParser('contentEncoding'), - new ContentMediaTypeKeywordParser('contentMediaType'), - new ContentSchemaKeywordParser('contentSchema'), + /** + * @inheritDoc + */ + protected function getKeywordParsers(): array { + return array( + // Generic + new TypeKeywordParser( 'type' ), + new ConstKeywordParser( 'const' ), + new EnumKeywordParser( 'enum' ), + new FormatKeywordParser( 'format' ), - // Number - new MinimumKeywordParser('minimum', 'exclusiveMinimum'), - new MaximumKeywordParser('maximum', 'exclusiveMaximum'), - new ExclusiveMinimumKeywordParser('exclusiveMinimum'), - new ExclusiveMaximumKeywordParser('exclusiveMaximum'), - new MultipleOfKeywordParser('multipleOf'), + // String + new MinLengthKeywordParser( 'minLength' ), + new MaxLengthKeywordParser( 'maxLength' ), + new PatternKeywordParser( 'pattern' ), + new ContentEncodingKeywordParser( 'contentEncoding' ), + new ContentMediaTypeKeywordParser( 'contentMediaType' ), + new ContentSchemaKeywordParser( 'contentSchema' ), - // Array - new MinItemsKeywordParser('minItems'), - new MaxItemsKeywordParser('maxItems'), - new UniqueItemsKeywordParser('uniqueItems'), - new ContainsKeywordParser('contains', 'minContains', 'maxContains'), - new ItemsKeywordParser('items'), - new AdditionalItemsKeywordParser('additionalItems'), + // Number + new MinimumKeywordParser( 'minimum', 'exclusiveMinimum' ), + new MaximumKeywordParser( 'maximum', 'exclusiveMaximum' ), + new ExclusiveMinimumKeywordParser( 'exclusiveMinimum' ), + new ExclusiveMaximumKeywordParser( 'exclusiveMaximum' ), + new MultipleOfKeywordParser( 'multipleOf' ), - // Object - new MinPropertiesKeywordParser('minProperties'), - new MaxPropertiesKeywordParser('maxProperties'), - new RequiredKeywordParser('required'), - new DependenciesKeywordParser('dependencies'), // keep for draft-07 compatibility - new DependentRequiredKeywordParser('dependentRequired'), - new DependentSchemasKeywordParser('dependentSchemas'), - new PropertyNamesKeywordParser('propertyNames'), - new PropertiesKeywordParser('properties'), - new PatternPropertiesKeywordParser('patternProperties'), - new AdditionalPropertiesKeywordParser('additionalProperties'), + // Array + new MinItemsKeywordParser( 'minItems' ), + new MaxItemsKeywordParser( 'maxItems' ), + new UniqueItemsKeywordParser( 'uniqueItems' ), + new ContainsKeywordParser( 'contains', 'minContains', 'maxContains' ), + new ItemsKeywordParser( 'items' ), + new AdditionalItemsKeywordParser( 'additionalItems' ), - // Conditionals - new IfThenElseKeywordParser('if', 'then', 'else'), - new AnyOfKeywordParser('anyOf'), - new AllOfKeywordParser('allOf'), - new OneOfKeywordParser('oneOf'), - new NotKeywordParser('not'), + // Object + new MinPropertiesKeywordParser( 'minProperties' ), + new MaxPropertiesKeywordParser( 'maxProperties' ), + new RequiredKeywordParser( 'required' ), + new DependenciesKeywordParser( 'dependencies' ), // keep for draft-07 compatibility + new DependentRequiredKeywordParser( 'dependentRequired' ), + new DependentSchemasKeywordParser( 'dependentSchemas' ), + new PropertyNamesKeywordParser( 'propertyNames' ), + new PropertiesKeywordParser( 'properties' ), + new PatternPropertiesKeywordParser( 'patternProperties' ), + new AdditionalPropertiesKeywordParser( 'additionalProperties' ), - // Unevaluated - new UnevaluatedPropertiesKeywordParser('unevaluatedProperties'), - new UnevaluatedItemsKeywordParser('unevaluatedItems'), + // Conditionals + new IfThenElseKeywordParser( 'if', 'then', 'else' ), + new AnyOfKeywordParser( 'anyOf' ), + new AllOfKeywordParser( 'allOf' ), + new OneOfKeywordParser( 'oneOf' ), + new NotKeywordParser( 'not' ), - // Optional - new DefaultKeywordParser('default'), - ]; - } + // Unevaluated + new UnevaluatedPropertiesKeywordParser( 'unevaluatedProperties' ), + new UnevaluatedItemsKeywordParser( 'unevaluatedItems' ), -} \ No newline at end of file + // Optional + new DefaultKeywordParser( 'default' ), + ); + } +} diff --git a/src/opis/json-schema/src/Parsers/Drafts/Draft202012.php b/src/opis/json-schema/src/Parsers/Drafts/Draft202012.php index b4e960ff..d6fa72f0 100644 --- a/src/opis/json-schema/src/Parsers/Drafts/Draft202012.php +++ b/src/opis/json-schema/src/Parsers/Drafts/Draft202012.php @@ -1,5 +1,6 @@ '$dynamicRef', 'anchor' => '$dynamicAnchor', 'fragment' => true], - ['ref' => '$recursiveRef', 'anchor' => '$recursiveAnchor', 'fragment' => false], - ]); - } + /** + * @inheritDoc + */ + public function supportsAnchorId(): bool { + return true; + } - /** - * @inheritDoc - */ - protected function getKeywordParsers(): array - { - return [ - // Generic - new TypeKeywordParser('type'), - new ConstKeywordParser('const'), - new EnumKeywordParser('enum'), - new FormatKeywordParser('format'), + /** + * @inheritDoc + */ + protected function getRefKeywordParser(): KeywordParser { + return new RefKeywordParser( + '$ref', + array( + array( + 'ref' => '$dynamicRef', + 'anchor' => '$dynamicAnchor', + 'fragment' => true, + ), + array( + 'ref' => '$recursiveRef', + 'anchor' => '$recursiveAnchor', + 'fragment' => false, + ), + ) + ); + } - // String - new MinLengthKeywordParser('minLength'), - new MaxLengthKeywordParser('maxLength'), - new PatternKeywordParser("pattern"), - new ContentEncodingKeywordParser('contentEncoding'), - new ContentMediaTypeKeywordParser('contentMediaType'), - new ContentSchemaKeywordParser('contentSchema'), + /** + * @inheritDoc + */ + protected function getKeywordParsers(): array { + return array( + // Generic + new TypeKeywordParser( 'type' ), + new ConstKeywordParser( 'const' ), + new EnumKeywordParser( 'enum' ), + new FormatKeywordParser( 'format' ), - // Number - new MinimumKeywordParser('minimum', 'exclusiveMinimum'), - new MaximumKeywordParser('maximum', 'exclusiveMaximum'), - new ExclusiveMinimumKeywordParser('exclusiveMinimum'), - new ExclusiveMaximumKeywordParser('exclusiveMaximum'), - new MultipleOfKeywordParser('multipleOf'), + // String + new MinLengthKeywordParser( 'minLength' ), + new MaxLengthKeywordParser( 'maxLength' ), + new PatternKeywordParser( 'pattern' ), + new ContentEncodingKeywordParser( 'contentEncoding' ), + new ContentMediaTypeKeywordParser( 'contentMediaType' ), + new ContentSchemaKeywordParser( 'contentSchema' ), - // Array - new MinItemsKeywordParser('minItems'), - new MaxItemsKeywordParser('maxItems'), - new UniqueItemsKeywordParser('uniqueItems'), - new ContainsKeywordParser('contains', 'minContains', 'maxContains'), - new ItemsKeywordParser('prefixItems', ItemsKeywordParser::ONLY_ARRAY), - new ItemsKeywordParser('items', ItemsKeywordParser::ONLY_SCHEMA, 'prefixItems'), - // keep for draft-2019-09 compatibility - new AdditionalItemsKeywordParser('additionalItems'), + // Number + new MinimumKeywordParser( 'minimum', 'exclusiveMinimum' ), + new MaximumKeywordParser( 'maximum', 'exclusiveMaximum' ), + new ExclusiveMinimumKeywordParser( 'exclusiveMinimum' ), + new ExclusiveMaximumKeywordParser( 'exclusiveMaximum' ), + new MultipleOfKeywordParser( 'multipleOf' ), - // Object - new MinPropertiesKeywordParser('minProperties'), - new MaxPropertiesKeywordParser('maxProperties'), - new RequiredKeywordParser('required'), - new DependenciesKeywordParser('dependencies'), // keep for draft-07 compatibility - new DependentRequiredKeywordParser('dependentRequired'), - new DependentSchemasKeywordParser('dependentSchemas'), - new PropertyNamesKeywordParser('propertyNames'), - new PropertiesKeywordParser('properties'), - new PatternPropertiesKeywordParser('patternProperties'), - new AdditionalPropertiesKeywordParser('additionalProperties'), + // Array + new MinItemsKeywordParser( 'minItems' ), + new MaxItemsKeywordParser( 'maxItems' ), + new UniqueItemsKeywordParser( 'uniqueItems' ), + new ContainsKeywordParser( 'contains', 'minContains', 'maxContains' ), + new ItemsKeywordParser( 'prefixItems', ItemsKeywordParser::ONLY_ARRAY ), + new ItemsKeywordParser( 'items', ItemsKeywordParser::ONLY_SCHEMA, 'prefixItems' ), + // keep for draft-2019-09 compatibility + new AdditionalItemsKeywordParser( 'additionalItems' ), - // Conditionals - new IfThenElseKeywordParser('if', 'then', 'else'), - new AnyOfKeywordParser('anyOf'), - new AllOfKeywordParser('allOf'), - new OneOfKeywordParser('oneOf'), - new NotKeywordParser('not'), + // Object + new MinPropertiesKeywordParser( 'minProperties' ), + new MaxPropertiesKeywordParser( 'maxProperties' ), + new RequiredKeywordParser( 'required' ), + new DependenciesKeywordParser( 'dependencies' ), // keep for draft-07 compatibility + new DependentRequiredKeywordParser( 'dependentRequired' ), + new DependentSchemasKeywordParser( 'dependentSchemas' ), + new PropertyNamesKeywordParser( 'propertyNames' ), + new PropertiesKeywordParser( 'properties' ), + new PatternPropertiesKeywordParser( 'patternProperties' ), + new AdditionalPropertiesKeywordParser( 'additionalProperties' ), - // Unevaluated - new UnevaluatedPropertiesKeywordParser('unevaluatedProperties'), - new UnevaluatedItemsKeywordParser('unevaluatedItems'), + // Conditionals + new IfThenElseKeywordParser( 'if', 'then', 'else' ), + new AnyOfKeywordParser( 'anyOf' ), + new AllOfKeywordParser( 'allOf' ), + new OneOfKeywordParser( 'oneOf' ), + new NotKeywordParser( 'not' ), - // Optional - new DefaultKeywordParser('default'), - ]; - } + // Unevaluated + new UnevaluatedPropertiesKeywordParser( 'unevaluatedProperties' ), + new UnevaluatedItemsKeywordParser( 'unevaluatedItems' ), -} \ No newline at end of file + // Optional + new DefaultKeywordParser( 'default' ), + ); + } +} diff --git a/src/opis/json-schema/src/Parsers/KeywordParser.php b/src/opis/json-schema/src/Parsers/KeywordParser.php index 0a8fad91..a111d1fb 100644 --- a/src/opis/json-schema/src/Parsers/KeywordParser.php +++ b/src/opis/json-schema/src/Parsers/KeywordParser.php @@ -1,5 +1,6 @@ draft(); - return $draft !== '06' && $draft !== '07'; - } +abstract class KeywordParser { + + const TYPE_PREPEND = '_prepend'; + const TYPE_BEFORE = '_before'; + const TYPE_AFTER = '_after'; + const TYPE_APPEND = '_append'; + + const TYPE_AFTER_REF = '_after_ref'; + + const TYPE_STRING = 'string'; + const TYPE_NUMBER = 'number'; + const TYPE_ARRAY = 'array'; + const TYPE_OBJECT = 'object'; + + use KeywordParserTrait; + + /** + * The keyword type, can be one of the TYPE_* const + * + * @return string + */ + abstract public function type(): string; + + /** + * @param SchemaInfo $info + * @param SchemaParser $parser + * @param object $shared + * @return Keyword|null + */ + abstract public function parse( $info, $parser, $shared ); + + /** + * @param SchemaInfo $info + * @return bool + */ + protected function trackEvaluated( $info ): bool { + $draft = $info->draft(); + return $draft !== '06' && $draft !== '07'; + } } diff --git a/src/opis/json-schema/src/Parsers/KeywordParserTrait.php b/src/opis/json-schema/src/Parsers/KeywordParserTrait.php index 939b7886..2fa9b200 100644 --- a/src/opis/json-schema/src/Parsers/KeywordParserTrait.php +++ b/src/opis/json-schema/src/Parsers/KeywordParserTrait.php @@ -1,5 +1,6 @@ keyword = $keyword; - } + /** @var string */ + protected $keyword; - /** - * @param object|SchemaInfo $schema - * @param string|null $keyword - * @return bool - */ - protected function keywordExists($schema, $keyword = null): bool - { - if ($schema instanceof SchemaInfo) { - $schema = $schema->data(); - } + /** + * @param string $keyword + */ + public function __construct( string $keyword ) { + $this->keyword = $keyword; + } - return property_exists($schema, $keyword ?? $this->keyword); - } + /** + * @param object|SchemaInfo $schema + * @param string|null $keyword + * @return bool + */ + protected function keywordExists( $schema, $keyword = null ): bool { + if ( $schema instanceof SchemaInfo ) { + $schema = $schema->data(); + } - /** - * @param object|SchemaInfo $schema - * @param string|null $keyword - * @return mixed - */ - protected function keywordValue($schema, $keyword = null) - { - if ($schema instanceof SchemaInfo) { - $schema = $schema->data(); - } + return property_exists( $schema, $keyword ?? $this->keyword ); + } - return $schema->{$keyword ?? $this->keyword}; - } + /** + * @param object|SchemaInfo $schema + * @param string|null $keyword + * @return mixed + */ + protected function keywordValue( $schema, $keyword = null ) { + if ( $schema instanceof SchemaInfo ) { + $schema = $schema->data(); + } - /** - * @param string $message - * @param SchemaInfo $info - * @param string|null $keyword - * @return InvalidKeywordException - */ - protected function keywordException($message, $info, $keyword = null): InvalidKeywordException - { - $keyword = $keyword ?? $this->keyword; + return $schema->{$keyword ?? $this->keyword}; + } - return new InvalidKeywordException(str_replace('{keyword}', $keyword, $message), $keyword, $info); - } -} \ No newline at end of file + /** + * @param string $message + * @param SchemaInfo $info + * @param string|null $keyword + * @return InvalidKeywordException + */ + protected function keywordException( $message, $info, $keyword = null ): InvalidKeywordException { + $keyword = $keyword ?? $this->keyword; + + return new InvalidKeywordException( str_replace( '{keyword}', $keyword, $message ), $keyword, $info ); + } +} diff --git a/src/opis/json-schema/src/Parsers/KeywordValidatorParser.php b/src/opis/json-schema/src/Parsers/KeywordValidatorParser.php index a1c049fe..c60a9e22 100644 --- a/src/opis/json-schema/src/Parsers/KeywordValidatorParser.php +++ b/src/opis/json-schema/src/Parsers/KeywordValidatorParser.php @@ -1,5 +1,6 @@ option('allowPragmas') || !$this->keywordExists($info)) { - return null; - } - - $value = $this->keywordValue($info); - - if (!is_object($value)) { - throw $this->keywordException('{keyword} must be an object', $info); - } - - $list = []; - - $draft = $info->draft() ?? $parser->defaultDraftVersion(); - - $pragmaInfo = new SchemaInfo($value, null, $info->id() ?? $info->base(), $info->root(), - array_merge($info->path(), [$this->keyword]), $draft); - - foreach ($parser->draft($draft)->pragmas() as $pragma) { - if ($handler = $pragma->parse($pragmaInfo, $parser, $shared)) { - $list[] = $handler; - } - } - - return $list ? new PragmaKeywordValidator($list) : null; - } +class PragmaKeywordValidatorParser extends KeywordValidatorParser { + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $parser->option( 'allowPragmas' ) || ! $this->keywordExists( $info ) ) { + return null; + } + + $value = $this->keywordValue( $info ); + + if ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be an object', $info ); + } + + $list = array(); + + $draft = $info->draft() ?? $parser->defaultDraftVersion(); + + $pragmaInfo = new SchemaInfo( + $value, + null, + $info->id() ?? $info->base(), + $info->root(), + array_merge( $info->path(), array( $this->keyword ) ), + $draft + ); + + foreach ( $parser->draft( $draft )->pragmas() as $pragma ) { + if ( $handler = $pragma->parse( $pragmaInfo, $parser, $shared ) ) { + $list[] = $handler; + } + } + + return $list ? new PragmaKeywordValidator( $list ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/AdditionalItemsKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/AdditionalItemsKeywordParser.php index 3f527daf..e6bf59ba 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/AdditionalItemsKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/AdditionalItemsKeywordParser.php @@ -1,5 +1,6 @@ option('keepAdditionalItemsKeyword') && $info->draft() === '2020-12') { - return null; - } + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_ARRAY; + } - $schema = $info->data(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $parser->option( 'keepAdditionalItemsKeyword' ) && $info->draft() === '2020-12' ) { + return null; + } - if (!$this->keywordExists($schema)) { - return null; - } + $schema = $info->data(); - if (!property_exists($schema, 'items') || !is_array($schema->items)) { - // Ignore additionalItems - return null; - } + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - $value = $this->keywordValue($schema); + if ( ! property_exists( $schema, 'items' ) || ! is_array( $schema->items ) ) { + // Ignore additionalItems + return null; + } - if (!is_bool($value) && !is_object($value)) { - throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); - } + $value = $this->keywordValue( $schema ); - return new AdditionalItemsKeyword($value, count($schema->items)); - } + if ( ! is_bool( $value ) && ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be a json schema (object or boolean)', $info ); + } + + return new AdditionalItemsKeyword( $value, count( $schema->items ) ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/AdditionalPropertiesKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/AdditionalPropertiesKeywordParser.php index bc889504..203bd665 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/AdditionalPropertiesKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/AdditionalPropertiesKeywordParser.php @@ -1,5 +1,6 @@ data(); - - if (!$this->keywordExists($schema)) { - return null; - } - - $value = $this->keywordValue($schema); - - if (!is_bool($value) && !is_object($value)) { - throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); - } - - return new AdditionalPropertiesKeyword($value); - } +class AdditionalPropertiesKeywordParser extends KeywordParser { + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); + + if ( ! $this->keywordExists( $schema ) ) { + return null; + } + + $value = $this->keywordValue( $schema ); + + if ( ! is_bool( $value ) && ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be a json schema (object or boolean)', $info ); + } + + return new AdditionalPropertiesKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/AllOfKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/AllOfKeywordParser.php index 808b9f6a..3ae8f3be 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/AllOfKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/AllOfKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_AFTER; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if (!is_array($value)) { - throw $this->keywordException("{keyword} should be an array of json schemas", $info); - } + $value = $this->keywordValue( $schema ); - if (!$value) { - throw $this->keywordException("{keyword} must have at least one element", $info); - } + if ( ! is_array( $value ) ) { + throw $this->keywordException( '{keyword} should be an array of json schemas', $info ); + } - $valid = 0; + if ( ! $value ) { + throw $this->keywordException( '{keyword} must have at least one element', $info ); + } - foreach ($value as $index => $item) { - if ($item === false) { - throw $this->keywordException("{keyword} contains false schema", $info); - } - if ($item === true) { - $valid++; - continue; - } - if (!is_object($item)) { - throw $this->keywordException("{keyword}[{$index}] must be a json schema", $info); - } elseif (!count(get_object_vars($item))) { - $valid++; - } - } + $valid = 0; - return $valid !== count($value) ? new AllOfKeyword($value) : null; - } + foreach ( $value as $index => $item ) { + if ( $item === false ) { + throw $this->keywordException( '{keyword} contains false schema', $info ); + } + if ( $item === true ) { + ++$valid; + continue; + } + if ( ! is_object( $item ) ) { + throw $this->keywordException( "{keyword}[{$index}] must be a json schema", $info ); + } elseif ( ! count( get_object_vars( $item ) ) ) { + ++$valid; + } + } + + return $valid !== count( $value ) ? new AllOfKeyword( $value ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/AnyOfKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/AnyOfKeywordParser.php index 21458985..79db5019 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/AnyOfKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/AnyOfKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_AFTER; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if (!is_array($value)) { - throw $this->keywordException("{keyword} should be an array of json schemas", $info); - } + $value = $this->keywordValue( $schema ); - if (!$value) { - throw $this->keywordException("{keyword} must have at least one element", $info); - } + if ( ! is_array( $value ) ) { + throw $this->keywordException( '{keyword} should be an array of json schemas', $info ); + } - $alwaysValid = false; + if ( ! $value ) { + throw $this->keywordException( '{keyword} must have at least one element', $info ); + } - foreach ($value as $index => $item) { - if ($item === true) { - $alwaysValid = true; - continue; - } - if ($item === false) { - continue; - } - if (!is_object($item)) { - throw $this->keywordException("{keyword}[{$index}] must be a json schema", $info); - } elseif (!count(get_object_vars($item))) { - $alwaysValid = true; - } - } + $alwaysValid = false; - return new AnyOfKeyword($value, $alwaysValid); - } + foreach ( $value as $index => $item ) { + if ( $item === true ) { + $alwaysValid = true; + continue; + } + if ( $item === false ) { + continue; + } + if ( ! is_object( $item ) ) { + throw $this->keywordException( "{keyword}[{$index}] must be a json schema", $info ); + } elseif ( ! count( get_object_vars( $item ) ) ) { + $alwaysValid = true; + } + } + + return new AnyOfKeyword( $value, $alwaysValid ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/ConstKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/ConstKeywordParser.php index 8ac34a90..5a198d1e 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/ConstKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/ConstKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_BEFORE; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new ConstDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - $type = Helper::getJsonType($value); - if ($type === null) { - throw $this->keywordException("{keyword} contains unknown json data type", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new ConstDataKeyword( $pointer ); + } + } - if (isset($shared->types)) { - if (!Helper::jsonTypeMatches($type, $shared->types)) { - throw $this->keywordException("{keyword} contains a value that doesn't match the type keyword", $info); - } - } else { - $shared->types = [$type]; - } + $type = Helper::getJsonType( $value ); + if ( $type === null ) { + throw $this->keywordException( '{keyword} contains unknown json data type', $info ); + } - return new ConstKeyword($value); - } + if ( isset( $shared->types ) ) { + if ( ! Helper::jsonTypeMatches( $type, $shared->types ) ) { + throw $this->keywordException( "{keyword} contains a value that doesn't match the type keyword", $info ); + } + } else { + $shared->types = array( $type ); + } + return new ConstKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/ContainsKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/ContainsKeywordParser.php index 37e1de77..e5131eff 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/ContainsKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/ContainsKeywordParser.php @@ -1,5 +1,6 @@ minContains = $minContains; - $this->maxContains = $maxContains; - } + /** + * @var string|null + */ + protected $minContains; + /** + * @var string|null + */ + protected $maxContains; - /** - * @inheritDoc - */ - public function type(): string - { - return self::TYPE_ARRAY; - } + public function __construct( string $keyword, $minContains = null, $maxContains = null ) { + parent::__construct( $keyword ); + $this->minContains = $minContains; + $this->maxContains = $maxContains; + } - /** - * @inheritDoc - * @param \Opis\JsonSchema\Info\SchemaInfo $info - * @param \Opis\JsonSchema\Parsers\SchemaParser $parser - * @param object $shared - */ - public function parse($info, $parser, $shared) - { - $schema = $info->data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_ARRAY; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if (!is_bool($value) && !is_object($value)) { - throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); - } + $value = $this->keywordValue( $schema ); - $min = $max = null; + if ( ! is_bool( $value ) && ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be a json schema (object or boolean)', $info ); + } - if ($this->minContains && $this->keywordExists($schema, $this->minContains)) { - $min = $this->keywordValue($schema, $this->minContains); - if (!is_int($min) || $min < 0) { - throw $this->keywordException("{keyword} must be a non-negative integer", $info, $this->minContains); - } - } + $min = $max = null; - if ($this->maxContains && $this->keywordExists($schema, $this->maxContains)) { - $max = $this->keywordValue($schema, $this->maxContains); - if (!is_int($max) || $max < 0) { - throw $this->keywordException("{keyword} must be a non-negative integer", $info, $this->maxContains); - } - if ($min !== null && $max < $min) { - throw $this->keywordException("{keyword} must be greater than {$this->minContains}", $info, $this->maxContains); - } - } elseif ($min === 0) { - return null; - } + if ( $this->minContains && $this->keywordExists( $schema, $this->minContains ) ) { + $min = $this->keywordValue( $schema, $this->minContains ); + if ( ! is_int( $min ) || $min < 0 ) { + throw $this->keywordException( '{keyword} must be a non-negative integer', $info, $this->minContains ); + } + } - return new ContainsKeyword($value, $min, $max); - } + if ( $this->maxContains && $this->keywordExists( $schema, $this->maxContains ) ) { + $max = $this->keywordValue( $schema, $this->maxContains ); + if ( ! is_int( $max ) || $max < 0 ) { + throw $this->keywordException( '{keyword} must be a non-negative integer', $info, $this->maxContains ); + } + if ( $min !== null && $max < $min ) { + throw $this->keywordException( "{keyword} must be greater than {$this->minContains}", $info, $this->maxContains ); + } + } elseif ( $min === 0 ) { + return null; + } + + return new ContainsKeyword( $value, $min, $max ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/ContentEncodingKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/ContentEncodingKeywordParser.php index 4f55e062..4bf58c23 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/ContentEncodingKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/ContentEncodingKeywordParser.php @@ -1,5 +1,6 @@ optionAllowedForDraft('decodeContent', $info, $parser)) { - return null; - } + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_STRING; + } - $schema = $info->data(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $this->optionAllowedForDraft( 'decodeContent', $info, $parser ) ) { + return null; + } - $resolver = $parser->getContentEncodingResolver(); + $schema = $info->data(); - if (!$resolver || !$this->keywordExists($schema)) { - return null; - } + $resolver = $parser->getContentEncodingResolver(); - $value = $this->keywordValue($schema); + if ( ! $resolver || ! $this->keywordExists( $schema ) ) { + return null; + } - if (!is_string($value)) { - throw $this->keywordException("{keyword} must be a string", $info); - } + $value = $this->keywordValue( $schema ); - return new ContentEncodingKeyword(strtolower($value), $resolver); - } + if ( ! is_string( $value ) ) { + throw $this->keywordException( '{keyword} must be a string', $info ); + } + + return new ContentEncodingKeyword( strtolower( $value ), $resolver ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/ContentMediaTypeKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/ContentMediaTypeKeywordParser.php index 82a7d8f9..c835b140 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/ContentMediaTypeKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/ContentMediaTypeKeywordParser.php @@ -1,5 +1,6 @@ optionAllowedForDraft('decodeContent', $info, $parser)) { - return null; - } + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_STRING; + } - $schema = $info->data(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $this->optionAllowedForDraft( 'decodeContent', $info, $parser ) ) { + return null; + } - $resolver = $parser->getMediaTypeResolver(); + $schema = $info->data(); - if (!$resolver || !$this->keywordExists($schema)) { - return null; - } + $resolver = $parser->getMediaTypeResolver(); - $value = $this->keywordValue($schema); + if ( ! $resolver || ! $this->keywordExists( $schema ) ) { + return null; + } - if (!is_string($value)) { - throw $this->keywordException("{keyword} must be a string", $info); - } + $value = $this->keywordValue( $schema ); - return new ContentMediaTypeKeyword($value, $resolver); - } + if ( ! is_string( $value ) ) { + throw $this->keywordException( '{keyword} must be a string', $info ); + } + + return new ContentMediaTypeKeyword( $value, $resolver ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/ContentSchemaKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/ContentSchemaKeywordParser.php index 08c1c1e9..eecaba2c 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/ContentSchemaKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/ContentSchemaKeywordParser.php @@ -1,5 +1,6 @@ optionAllowedForDraft('decodeContent', $info, $parser)) { - return null; - } - - $schema = $info->data(); - - if (!$this->keywordExists($schema)) { - return null; - } - - $value = $this->keywordValue($schema); - - if (!is_object($value)) { - throw $this->keywordException("{keyword} must be a valid json schema object", $info); - } - - return new ContentSchemaKeyword($value); - } +class ContentSchemaKeywordParser extends KeywordParser { + + use DraftOptionTrait; + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_STRING; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $this->optionAllowedForDraft( 'decodeContent', $info, $parser ) ) { + return null; + } + + $schema = $info->data(); + + if ( ! $this->keywordExists( $schema ) ) { + return null; + } + + $value = $this->keywordValue( $schema ); + + if ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be a valid json schema object', $info ); + } + + return new ContentSchemaKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/DefaultKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/DefaultKeywordParser.php index 99f1dcd3..35b3c57f 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/DefaultKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/DefaultKeywordParser.php @@ -1,5 +1,6 @@ properties = $properties; - } + /** + * @var string|null + */ + protected $properties; - /** - * @inheritDoc - */ - public function type(): string - { - return self::TYPE_APPEND; - } + /** + * @inheritDoc + */ + public function __construct( string $keyword, $properties = 'properties' ) { + parent::__construct( $keyword ); + $this->properties = $properties; + } - /** - * @inheritDoc - * @param \Opis\JsonSchema\Info\SchemaInfo $info - * @param \Opis\JsonSchema\Parsers\SchemaParser $parser - * @param object $shared - */ - public function parse($info, $parser, $shared) - { - $schema = $info->data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_APPEND; + } - if (!$parser->option('allowDefaults')) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $defaults = null; + if ( ! $parser->option( 'allowDefaults' ) ) { + return null; + } - if ($this->keywordExists($schema)) { - $defaults = $this->keywordValue($schema); + $defaults = null; - if (is_object($defaults)) { - $defaults = (array)Helper::cloneValue($defaults); - } else { - $defaults = null; - } - } + if ( $this->keywordExists( $schema ) ) { + $defaults = $this->keywordValue( $schema ); - if ($this->properties !== null && property_exists($schema, $this->properties) - && is_object($schema->{$this->properties})) { - foreach ($schema->{$this->properties} as $name => $value) { - if (is_object($value) && property_exists($value, $this->keyword)) { - $defaults[$name] = $value->{$this->keyword}; - } - } - } + if ( is_object( $defaults ) ) { + $defaults = (array) Helper::cloneValue( $defaults ); + } else { + $defaults = null; + } + } - if (!$defaults) { - return null; - } + if ( $this->properties !== null && property_exists( $schema, $this->properties ) + && is_object( $schema->{$this->properties} ) ) { + foreach ( $schema->{$this->properties} as $name => $value ) { + if ( is_object( $value ) && property_exists( $value, $this->keyword ) ) { + $defaults[ $name ] = $value->{$this->keyword}; + } + } + } - return new DefaultKeyword($defaults); - } + if ( ! $defaults ) { + return null; + } + + return new DefaultKeyword( $defaults ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/DependenciesKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/DependenciesKeywordParser.php index ca3cc507..bd19dee0 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/DependenciesKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/DependenciesKeywordParser.php @@ -1,5 +1,6 @@ option('keepDependenciesKeyword') && !in_array($info->draft(), ['06', '07'])) { - return null; - } + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } - $schema = $info->data(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $parser->option( 'keepDependenciesKeyword' ) && ! in_array( $info->draft(), array( '06', '07' ) ) ) { + return null; + } - if (!$this->keywordExists($schema)) { - return null; - } + $schema = $info->data(); - $value = $this->keywordValue($schema); - if (!is_object($value)) { - throw $this->keywordException("{keyword} must be an object", $info); - } + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - $list = get_object_vars($value); + $value = $this->keywordValue( $schema ); + if ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be an object', $info ); + } - foreach ($list as $name => $s) { - if (is_array($s)) { - if (!$s) { - unset($list[$name]); - continue; - } - foreach ($s as $p) { - if (!is_string($p)) { - throw $this->keywordException("{keyword} must be an object containing json schemas or arrays of property names", $info); - } - } - $list[$name] = array_unique($s); - } elseif (is_bool($s)) { - if ($s) { - unset($list[$name]); - } - } elseif (!is_object($s)) { - throw $this->keywordException("{keyword} must be an object containing json schemas or arrays of property names", $info); - } - } + $list = get_object_vars( $value ); - return $list ? new DependenciesKeyword($list) : null; - } + foreach ( $list as $name => $s ) { + if ( is_array( $s ) ) { + if ( ! $s ) { + unset( $list[ $name ] ); + continue; + } + foreach ( $s as $p ) { + if ( ! is_string( $p ) ) { + throw $this->keywordException( '{keyword} must be an object containing json schemas or arrays of property names', $info ); + } + } + $list[ $name ] = array_unique( $s ); + } elseif ( is_bool( $s ) ) { + if ( $s ) { + unset( $list[ $name ] ); + } + } elseif ( ! is_object( $s ) ) { + throw $this->keywordException( '{keyword} must be an object containing json schemas or arrays of property names', $info ); + } + } + + return $list ? new DependenciesKeyword( $list ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/DependentRequiredKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/DependentRequiredKeywordParser.php index da237989..9b9cf6d0 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/DependentRequiredKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/DependentRequiredKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); - if (!is_object($value)) { - throw $this->keywordException("{keyword} must be an object", $info); - } + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - $list = []; - foreach ($value as $name => $s) { - if (!is_array($s)) { - throw $this->keywordException("{keyword} must be an object containing json schemas or arrays of property names", $info); - } - if (!$s) { - // Empty array - continue; - } - foreach ($s as $p) { - if (!is_string($p)) { - throw $this->keywordException("{keyword} must be an object containing arrays of property names", $info); - } - } - $list[$name] = array_unique($s); - } + $value = $this->keywordValue( $schema ); + if ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be an object', $info ); + } - return $list ? new DependentRequiredKeyword($list) : null; - } + $list = array(); + foreach ( $value as $name => $s ) { + if ( ! is_array( $s ) ) { + throw $this->keywordException( '{keyword} must be an object containing json schemas or arrays of property names', $info ); + } + if ( ! $s ) { + // Empty array + continue; + } + foreach ( $s as $p ) { + if ( ! is_string( $p ) ) { + throw $this->keywordException( '{keyword} must be an object containing arrays of property names', $info ); + } + } + $list[ $name ] = array_unique( $s ); + } + + return $list ? new DependentRequiredKeyword( $list ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/DependentSchemasKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/DependentSchemasKeywordParser.php index f77f980a..69c91a25 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/DependentSchemasKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/DependentSchemasKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); - if (!is_object($value)) { - throw $this->keywordException("{keyword} must be an object", $info); - } + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - $valid = 0; - $total = 0; + $value = $this->keywordValue( $schema ); + if ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be an object', $info ); + } - foreach ($value as $name => $s) { - $total++; - if (is_bool($s)) { - if ($s) { - $valid++; - } - } elseif (!is_object($s)) { - throw $this->keywordException("{keyword} must be an object containing json schemas", $info); - } elseif (!count(get_object_vars($s))) { - $valid++; - } - } + $valid = 0; + $total = 0; - if (!$total) { - return null; - } + foreach ( $value as $name => $s ) { + ++$total; + if ( is_bool( $s ) ) { + if ( $s ) { + ++$valid; + } + } elseif ( ! is_object( $s ) ) { + throw $this->keywordException( '{keyword} must be an object containing json schemas', $info ); + } elseif ( ! count( get_object_vars( $s ) ) ) { + ++$valid; + } + } - return $valid !== $total ? new DependentSchemasKeyword($value) : null; - } + if ( ! $total ) { + return null; + } + + return $valid !== $total ? new DependentSchemasKeyword( $value ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/EnumKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/EnumKeywordParser.php index 2aa6465f..c1c85034 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/EnumKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/EnumKeywordParser.php @@ -1,5 +1,6 @@ data(); - - if (!$this->keywordExists($schema)) { - return null; - } - - $value = $this->keywordValue($schema); - - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new EnumDataKeyword($pointer); - } - } - - if (!is_array($value) || !$value) { - throw $this->keywordException("{keyword} must be a non-empty array", $info); - } - - $hasConst = property_exists($schema, 'const'); - $constMatched = false; - - $allowedTypes = isset($shared->types) ? $shared->types : null; - $foundTypes = []; - $list = []; - foreach ($value as $item) { - $type = Helper::getJsonType($item); - if ($type === null) { - throw $this->keywordException("{keyword} contains invalid json data type", $info); - } - - if ($allowedTypes && !Helper::jsonTypeMatches($type, $allowedTypes)) { - continue; - } - - if ($hasConst && Helper::equals($item, $schema->const)) { - $constMatched = true; - break; - } - - if (!in_array($type, $foundTypes)) { - $foundTypes[] = $type; - } - - $list[] = $item; - } - - if ($hasConst) { - if ($constMatched) { - return null; - } - throw $this->keywordException("{keyword} does not contain the value of const keyword", $info); - } - - if ($foundTypes) { - if ($allowedTypes === null) { - $shared->types = $foundTypes; - } else { - $shared->types = array_unique(array_merge($shared->types, $foundTypes)); - } - } - - return new EnumKeyword($list); - } +class EnumKeywordParser extends KeywordParser { + + use DataKeywordTrait; + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_BEFORE; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); + + if ( ! $this->keywordExists( $schema ) ) { + return null; + } + + $value = $this->keywordValue( $schema ); + + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new EnumDataKeyword( $pointer ); + } + } + + if ( ! is_array( $value ) || ! $value ) { + throw $this->keywordException( '{keyword} must be a non-empty array', $info ); + } + + $hasConst = property_exists( $schema, 'const' ); + $constMatched = false; + + $allowedTypes = isset( $shared->types ) ? $shared->types : null; + $foundTypes = array(); + $list = array(); + foreach ( $value as $item ) { + $type = Helper::getJsonType( $item ); + if ( $type === null ) { + throw $this->keywordException( '{keyword} contains invalid json data type', $info ); + } + + if ( $allowedTypes && ! Helper::jsonTypeMatches( $type, $allowedTypes ) ) { + continue; + } + + if ( $hasConst && Helper::equals( $item, $schema->const ) ) { + $constMatched = true; + break; + } + + if ( ! in_array( $type, $foundTypes ) ) { + $foundTypes[] = $type; + } + + $list[] = $item; + } + + if ( $hasConst ) { + if ( $constMatched ) { + return null; + } + throw $this->keywordException( '{keyword} does not contain the value of const keyword', $info ); + } + + if ( $foundTypes ) { + if ( $allowedTypes === null ) { + $shared->types = $foundTypes; + } else { + $shared->types = array_unique( array_merge( $shared->types, $foundTypes ) ); + } + } + + return new EnumKeyword( $list ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/ExclusiveMaximumKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/ExclusiveMaximumKeywordParser.php index 8f3cc97e..c51ce4d4 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/ExclusiveMaximumKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/ExclusiveMaximumKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_NUMBER; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if (is_bool($value) && $parser->option('allowExclusiveMinMaxAsBool')) { - return null; - } + $value = $this->keywordValue( $schema ); - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new ExclusiveMaximumDataKeyword($pointer); - } - } + if ( is_bool( $value ) && $parser->option( 'allowExclusiveMinMaxAsBool' ) ) { + return null; + } - if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { - throw $this->keywordException('{keyword} must contain a valid number', $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new ExclusiveMaximumDataKeyword( $pointer ); + } + } - return new ExclusiveMaximumKeyword($value); - } + if ( ! is_int( $value ) && ! is_float( $value ) || is_nan( $value ) || ! is_finite( $value ) ) { + throw $this->keywordException( '{keyword} must contain a valid number', $info ); + } + + return new ExclusiveMaximumKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/ExclusiveMinimumKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/ExclusiveMinimumKeywordParser.php index 0f3ab97b..ce861634 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/ExclusiveMinimumKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/ExclusiveMinimumKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_NUMBER; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if (is_bool($value) && $parser->option('allowExclusiveMinMaxAsBool')) { - return null; - } + $value = $this->keywordValue( $schema ); - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new ExclusiveMinimumDataKeyword($pointer); - } - } + if ( is_bool( $value ) && $parser->option( 'allowExclusiveMinMaxAsBool' ) ) { + return null; + } - if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { - throw $this->keywordException('{keyword} must contain a valid number', $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new ExclusiveMinimumDataKeyword( $pointer ); + } + } - return new ExclusiveMinimumKeyword($value); - } + if ( ! is_int( $value ) && ! is_float( $value ) || is_nan( $value ) || ! is_finite( $value ) ) { + throw $this->keywordException( '{keyword} must contain a valid number', $info ); + } + + return new ExclusiveMinimumKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/FiltersKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/FiltersKeywordParser.php index 477c6591..ad905e94 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/FiltersKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/FiltersKeywordParser.php @@ -1,5 +1,6 @@ data(); - - if (!$parser->option('allowFilters')) { - return null; - } - - $resolver = $parser->getFilterResolver(); - - if (!$resolver || !$this->keywordExists($schema)) { - return null; - } - - $filters = $this->parseFilters($parser, $resolver, $this->keywordValue($schema), $info); - if (!$filters) { - return null; - } - - return new FiltersKeyword($filters); - } - - /** - * @param SchemaParser $parser - * @param FilterResolver $filterResolver - * @param mixed $filters - * @param SchemaInfo $info - * @return array|null - */ - protected function parseFilters( - $parser, - $filterResolver, - $filters, - $info - ) - { - if (is_string($filters)) { - if ($filters = $this->parseFilter($parser, $filterResolver, $filters, $info)) { - return [$filters]; - } - - return null; - } - - if (is_object($filters)) { - if ($filter = $this->parseFilter($parser, $filterResolver, $filters, $info)) { - return [$filter]; - } - - return null; - } - - if (is_array($filters)) { - if (!$filters) { - return null; - } - $list = []; - foreach ($filters as $filter) { - if ($filter = $this->parseFilter($parser, $filterResolver, $filter, $info)) { - $list[] = $filter; - } - } - - return $list ?: null; - } - - throw $this->keywordException('{keyword} can be a non-empty string, an object or an array of string and objects', $info); - } - - /** - * @param SchemaParser $parser - * @param FilterResolver $resolver - * @param $filter - * @param SchemaInfo $info - * @return object|null - */ - protected function parseFilter( - $parser, - $resolver, - $filter, - $info - ) - { - $vars = null; - if (is_object($filter)) { - if (!property_exists($filter, '$func') || !is_string($filter->{'$func'}) || $filter->{'$func'} === '') { - throw $this->keywordException('$func (for {keyword}) must be a non-empty string', $info); - } - - $vars = get_object_vars($filter); - unset($vars['$func']); - - if (property_exists($filter, '$vars')) { - if (!is_object($filter->{'$vars'})) { - throw $this->keywordException('$vars (for {keyword}) must be a string', $info); - } - unset($vars['$vars']); - $vars = get_object_vars($filter->{'$vars'}) + $vars; - } - - $filter = $filter->{'$func'}; - } elseif (!is_string($filter) || $filter === '') { - throw $this->keywordException('{keyword} can be a non-empty string, an object or an array of string and objects', $info); - } - - $list = $resolver->resolveAll($filter); - if (!$list) { - throw $this->keywordException("{keyword}: {$filter} doesn't exists", $info); - } - - $list = $this->resolveSubTypes($list); - - return (object)[ - 'name' => $filter, - 'args' => $vars ? $this->createVariables($parser, $vars) : null, - 'types' => $list, - ]; - } + ResolverTrait, VariablesTrait}; + +class FiltersKeywordParser extends KeywordParser { + + use ResolverTrait; + use VariablesTrait; + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_APPEND; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); + + if ( ! $parser->option( 'allowFilters' ) ) { + return null; + } + + $resolver = $parser->getFilterResolver(); + + if ( ! $resolver || ! $this->keywordExists( $schema ) ) { + return null; + } + + $filters = $this->parseFilters( $parser, $resolver, $this->keywordValue( $schema ), $info ); + if ( ! $filters ) { + return null; + } + + return new FiltersKeyword( $filters ); + } + + /** + * @param SchemaParser $parser + * @param FilterResolver $filterResolver + * @param mixed $filters + * @param SchemaInfo $info + * @return array|null + */ + protected function parseFilters( + $parser, + $filterResolver, + $filters, + $info + ) { + if ( is_string( $filters ) ) { + if ( $filters = $this->parseFilter( $parser, $filterResolver, $filters, $info ) ) { + return array( $filters ); + } + + return null; + } + + if ( is_object( $filters ) ) { + if ( $filter = $this->parseFilter( $parser, $filterResolver, $filters, $info ) ) { + return array( $filter ); + } + + return null; + } + + if ( is_array( $filters ) ) { + if ( ! $filters ) { + return null; + } + $list = array(); + foreach ( $filters as $filter ) { + if ( $filter = $this->parseFilter( $parser, $filterResolver, $filter, $info ) ) { + $list[] = $filter; + } + } + + return $list ?: null; + } + + throw $this->keywordException( '{keyword} can be a non-empty string, an object or an array of string and objects', $info ); + } + + /** + * @param SchemaParser $parser + * @param FilterResolver $resolver + * @param $filter + * @param SchemaInfo $info + * @return object|null + */ + protected function parseFilter( + $parser, + $resolver, + $filter, + $info + ) { + $vars = null; + if ( is_object( $filter ) ) { + if ( ! property_exists( $filter, '$func' ) || ! is_string( $filter->{'$func'} ) || $filter->{'$func'} === '' ) { + throw $this->keywordException( '$func (for {keyword}) must be a non-empty string', $info ); + } + + $vars = get_object_vars( $filter ); + unset( $vars['$func'] ); + + if ( property_exists( $filter, '$vars' ) ) { + if ( ! is_object( $filter->{'$vars'} ) ) { + throw $this->keywordException( '$vars (for {keyword}) must be a string', $info ); + } + unset( $vars['$vars'] ); + $vars = get_object_vars( $filter->{'$vars'} ) + $vars; + } + + $filter = $filter->{'$func'}; + } elseif ( ! is_string( $filter ) || $filter === '' ) { + throw $this->keywordException( '{keyword} can be a non-empty string, an object or an array of string and objects', $info ); + } + + $list = $resolver->resolveAll( $filter ); + if ( ! $list ) { + throw $this->keywordException( "{keyword}: {$filter} doesn't exists", $info ); + } + + $list = $this->resolveSubTypes( $list ); + + return (object) array( + 'name' => $filter, + 'args' => $vars ? $this->createVariables( $parser, $vars ) : null, + 'types' => $list, + ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/FormatKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/FormatKeywordParser.php index 4791e05f..b2ce30ad 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/FormatKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/FormatKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_BEFORE; + } - $resolver = $parser->getFormatResolver(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - if (!$resolver || !$parser->option('allowFormats') || !$this->keywordExists($schema)) { - return null; - } + $resolver = $parser->getFormatResolver(); - $value = $this->keywordValue($schema); + if ( ! $resolver || ! $parser->option( 'allowFormats' ) || ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new FormatDataKeyword($pointer, $resolver); - } - } + $value = $this->keywordValue( $schema ); - if (!is_string($value)) { - throw $this->keywordException("{keyword} must be a string", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new FormatDataKeyword( $pointer, $resolver ); + } + } - $list = $resolver->resolveAll($value); + if ( ! is_string( $value ) ) { + throw $this->keywordException( '{keyword} must be a string', $info ); + } - if (!$list) { - return null; - } + $list = $resolver->resolveAll( $value ); - return new FormatKeyword($value, $this->resolveSubTypes($list)); - } + if ( ! $list ) { + return null; + } + + return new FormatKeyword( $value, $this->resolveSubTypes( $list ) ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/IfThenElseKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/IfThenElseKeywordParser.php index df8862ba..38ac7f80 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/IfThenElseKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/IfThenElseKeywordParser.php @@ -1,5 +1,6 @@ then = $then; - $this->else = $else; - } - - /** - * @inheritDoc - */ - public function type(): string - { - return self::TYPE_AFTER; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\Info\SchemaInfo $info - * @param \Opis\JsonSchema\Parsers\SchemaParser $parser - * @param object $shared - */ - public function parse($info, $parser, $shared) - { - $schema = $info->data(); - - if (!$this->keywordExists($schema)) { - return null; - } - - $if = $this->keywordValue($schema); - if (!$this->isJsonSchema($if)) { - throw $this->keywordException("{keyword} keyword must be a json schema", $info); - } - - $then = true; - if (property_exists($schema, $this->then)) { - $then = $schema->{$this->then}; - } - if (!$this->isJsonSchema($then)) { - throw $this->keywordException("{keyword} keyword must be a json schema", $info, $this->then); - } - - $else = true; - if (property_exists($schema, $this->else)) { - $else = $schema->{$this->else}; - } - if (!$this->isJsonSchema($else)) { - throw $this->keywordException("{keyword} keyword must be a json schema", $info, $this->else); - } - - if ($if === true) { - if ($then === true) { - return null; - } - $else = true; - } elseif ($if === false) { - if ($else === true) { - return null; - } - $then = true; - } elseif ($then === true && $else === true) { - return null; - } - - return new IfThenElseKeyword($if, $then, $else); - } - - /** - * @param $value - * @return bool - */ - protected function isJsonSchema($value): bool - { - return is_bool($value) || is_object($value); - } +class IfThenElseKeywordParser extends KeywordParser { + + + /** + * @var string + */ + protected $then; + + /** + * @var string + */ + protected $else; + + /** + * @param string $if + * @param string $then + * @param string $else + */ + public function __construct( string $if, string $then, string $else ) { + parent::__construct( $if ); + $this->then = $then; + $this->else = $else; + } + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_AFTER; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); + + if ( ! $this->keywordExists( $schema ) ) { + return null; + } + + $if = $this->keywordValue( $schema ); + if ( ! $this->isJsonSchema( $if ) ) { + throw $this->keywordException( '{keyword} keyword must be a json schema', $info ); + } + + $then = true; + if ( property_exists( $schema, $this->then ) ) { + $then = $schema->{$this->then}; + } + if ( ! $this->isJsonSchema( $then ) ) { + throw $this->keywordException( '{keyword} keyword must be a json schema', $info, $this->then ); + } + + $else = true; + if ( property_exists( $schema, $this->else ) ) { + $else = $schema->{$this->else}; + } + if ( ! $this->isJsonSchema( $else ) ) { + throw $this->keywordException( '{keyword} keyword must be a json schema', $info, $this->else ); + } + + if ( $if === true ) { + if ( $then === true ) { + return null; + } + $else = true; + } elseif ( $if === false ) { + if ( $else === true ) { + return null; + } + $then = true; + } elseif ( $then === true && $else === true ) { + return null; + } + + return new IfThenElseKeyword( $if, $then, $else ); + } + + /** + * @param $value + * @return bool + */ + protected function isJsonSchema( $value ): bool { + return is_bool( $value ) || is_object( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/ItemsKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/ItemsKeywordParser.php index d944ee04..666e6c4f 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/ItemsKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/ItemsKeywordParser.php @@ -1,5 +1,6 @@ mode = $mode; - $this->startIndexKeyword = $startIndexKeyword; - } + /** + * @var int + */ + protected $mode; + /** + * @var string|null + */ + protected $startIndexKeyword; - /** - * @inheritDoc - */ - public function type(): string - { - return self::TYPE_ARRAY; - } + public function __construct( string $keyword, int $mode = self::BOTH, $startIndexKeyword = null ) { + parent::__construct( $keyword ); + $this->mode = $mode; + $this->startIndexKeyword = $startIndexKeyword; + } - /** - * @inheritDoc - * @param \Opis\JsonSchema\Info\SchemaInfo $info - * @param \Opis\JsonSchema\Parsers\SchemaParser $parser - * @param object $shared - */ - public function parse($info, $parser, $shared) - { - $schema = $info->data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_ARRAY; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - $alwaysValid = false; + $value = $this->keywordValue( $schema ); - if (is_bool($value)) { - if ($this->mode === self::ONLY_ARRAY) { - throw $this->keywordException("{keyword} must contain an array of json schemas", $info); - } - if ($value) { - $alwaysValid = true; - } - } elseif (is_array($value)) { - if ($this->mode === self::ONLY_SCHEMA) { - throw $this->keywordException("{keyword} must contain a valid json schema", $info); - } - $valid = 0; - foreach ($value as $index => $v) { - if (is_bool($v)) { - if ($v) { - $valid++; - } - } elseif (!is_object($v)) { - throw $this->keywordException("{keyword}[$index] must contain a valid json schema", $info); - } elseif (!count(get_object_vars($v))) { - $valid++; - } - } - if ($valid === count($value)) { - $alwaysValid = true; - } - } elseif (!is_object($value)) { - if ($this->mode === self::BOTH) { - throw $this->keywordException("{keyword} must be a json schema or an array of json schemas", $info); - } elseif ($this->mode === self::ONLY_ARRAY) { - throw $this->keywordException("{keyword} must contain an array of json schemas", $info); - } else { - throw $this->keywordException("{keyword} must contain a valid json schema", $info); - } - } else { - if ($this->mode === self::ONLY_ARRAY) { - throw $this->keywordException("{keyword} must contain an array of json schemas", $info); - } - if (!count(get_object_vars($value))) { - $alwaysValid = true; - } - } + $alwaysValid = false; - $startIndex = 0; - if ($this->startIndexKeyword !== null && $this->keywordExists($schema, $this->startIndexKeyword)) { - $start = $this->keywordValue($schema, $this->startIndexKeyword); - if (is_array($start)) { - $startIndex = count($start); - } - } + if ( is_bool( $value ) ) { + if ( $this->mode === self::ONLY_ARRAY ) { + throw $this->keywordException( '{keyword} must contain an array of json schemas', $info ); + } + if ( $value ) { + $alwaysValid = true; + } + } elseif ( is_array( $value ) ) { + if ( $this->mode === self::ONLY_SCHEMA ) { + throw $this->keywordException( '{keyword} must contain a valid json schema', $info ); + } + $valid = 0; + foreach ( $value as $index => $v ) { + if ( is_bool( $v ) ) { + if ( $v ) { + ++$valid; + } + } elseif ( ! is_object( $v ) ) { + throw $this->keywordException( "{keyword}[$index] must contain a valid json schema", $info ); + } elseif ( ! count( get_object_vars( $v ) ) ) { + ++$valid; + } + } + if ( $valid === count( $value ) ) { + $alwaysValid = true; + } + } elseif ( ! is_object( $value ) ) { + if ( $this->mode === self::BOTH ) { + throw $this->keywordException( '{keyword} must be a json schema or an array of json schemas', $info ); + } elseif ( $this->mode === self::ONLY_ARRAY ) { + throw $this->keywordException( '{keyword} must contain an array of json schemas', $info ); + } else { + throw $this->keywordException( '{keyword} must contain a valid json schema', $info ); + } + } else { + if ( $this->mode === self::ONLY_ARRAY ) { + throw $this->keywordException( '{keyword} must contain an array of json schemas', $info ); + } + if ( ! count( get_object_vars( $value ) ) ) { + $alwaysValid = true; + } + } - return new ItemsKeyword($value, $alwaysValid, $this->keyword, $startIndex); - } + $startIndex = 0; + if ( $this->startIndexKeyword !== null && $this->keywordExists( $schema, $this->startIndexKeyword ) ) { + $start = $this->keywordValue( $schema, $this->startIndexKeyword ); + if ( is_array( $start ) ) { + $startIndex = count( $start ); + } + } + + return new ItemsKeyword( $value, $alwaysValid, $this->keyword, $startIndex ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MaxItemsKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MaxItemsKeywordParser.php index f33cd92d..231761c4 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MaxItemsKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MaxItemsKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_ARRAY; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new MaxItemsDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_int($value) || $value < 0) { - throw $this->keywordException("{keyword} most be a positive integer", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new MaxItemsDataKeyword( $pointer ); + } + } - return new MaxItemsKeyword($value); - } + if ( ! is_int( $value ) || $value < 0 ) { + throw $this->keywordException( '{keyword} most be a positive integer', $info ); + } + + return new MaxItemsKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MaxLengthKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MaxLengthKeywordParser.php index 5f691d53..6d7fef6e 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MaxLengthKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MaxLengthKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_STRING; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new MaxLengthDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_int($value) || $value < 0) { - throw $this->keywordException("{keyword} must be a non-negative integer", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new MaxLengthDataKeyword( $pointer ); + } + } - return new MaxLengthKeyword($value); - } + if ( ! is_int( $value ) || $value < 0 ) { + throw $this->keywordException( '{keyword} must be a non-negative integer', $info ); + } + + return new MaxLengthKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MaxPropertiesKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MaxPropertiesKeywordParser.php index dc42ce46..63aaddaf 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MaxPropertiesKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MaxPropertiesKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new MaxPropertiesDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_int($value) || $value < 0) { - throw $this->keywordException("{keyword} must be a non-negative integer", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new MaxPropertiesDataKeyword( $pointer ); + } + } - return new MaxPropertiesKeywords($value); - } + if ( ! is_int( $value ) || $value < 0 ) { + throw $this->keywordException( '{keyword} must be a non-negative integer', $info ); + } + + return new MaxPropertiesKeywords( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MaximumKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MaximumKeywordParser.php index e016403d..45d6eae4 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MaximumKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MaximumKeywordParser.php @@ -1,5 +1,6 @@ exclusiveKeyword = $exclusiveKeyword; - } + /** + * @var string|null + */ + protected $exclusiveKeyword; - /** - * @inheritDoc - */ - public function type(): string - { - return self::TYPE_NUMBER; - } + /** + * @param string $keyword + * @param string|null $exclusiveKeyword + */ + public function __construct( string $keyword, $exclusiveKeyword = null ) { + parent::__construct( $keyword ); + $this->exclusiveKeyword = $exclusiveKeyword; + } - /** - * @inheritDoc - * @param \Opis\JsonSchema\Info\SchemaInfo $info - * @param \Opis\JsonSchema\Parsers\SchemaParser $parser - * @param object $shared - */ - public function parse($info, $parser, $shared) - { - $schema = $info->data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_NUMBER; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - $exclusive = false; - if ($parser->option('allowExclusiveMinMaxAsBool') && - $this->exclusiveKeyword !== null && - property_exists($schema, $this->exclusiveKeyword)) { - $exclusive = $schema->{$this->exclusiveKeyword} === true; - } + $value = $this->keywordValue( $schema ); - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return $exclusive - ? new ExclusiveMaximumDataKeyword($pointer) - : new MaximumDataKeyword($pointer); - } - } + $exclusive = false; + if ( $parser->option( 'allowExclusiveMinMaxAsBool' ) && + $this->exclusiveKeyword !== null && + property_exists( $schema, $this->exclusiveKeyword ) ) { + $exclusive = $schema->{$this->exclusiveKeyword} === true; + } - if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { - throw $this->keywordException('{keyword} must contain a valid number', $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return $exclusive + ? new ExclusiveMaximumDataKeyword( $pointer ) + : new MaximumDataKeyword( $pointer ); + } + } - return $exclusive - ? new ExclusiveMaximumKeyword($value) - : new MaximumKeyword($value); - } + if ( ! is_int( $value ) && ! is_float( $value ) || is_nan( $value ) || ! is_finite( $value ) ) { + throw $this->keywordException( '{keyword} must contain a valid number', $info ); + } + + return $exclusive + ? new ExclusiveMaximumKeyword( $value ) + : new MaximumKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MinItemsKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MinItemsKeywordParser.php index 2e36c18b..eb0ddbb6 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MinItemsKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MinItemsKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_ARRAY; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new MinItemsDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_int($value) || $value < 0) { - throw $this->keywordException("{keyword} most be a positive integer", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new MinItemsDataKeyword( $pointer ); + } + } - if ($value === 0) { - return null; - } + if ( ! is_int( $value ) || $value < 0 ) { + throw $this->keywordException( '{keyword} most be a positive integer', $info ); + } - return new MinItemsKeyword($value); - } + if ( $value === 0 ) { + return null; + } + + return new MinItemsKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MinLengthKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MinLengthKeywordParser.php index 261330e7..54364e3a 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MinLengthKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MinLengthKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_STRING; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new MinLengthDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_int($value) || $value < 0) { - throw $this->keywordException("{keyword} must be a non-negative integer", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new MinLengthDataKeyword( $pointer ); + } + } - if ($value === 0) { - return null; - } + if ( ! is_int( $value ) || $value < 0 ) { + throw $this->keywordException( '{keyword} must be a non-negative integer', $info ); + } - return new MinLengthKeyword($value); - } + if ( $value === 0 ) { + return null; + } + + return new MinLengthKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MinPropertiesKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MinPropertiesKeywordParser.php index 62592705..4fd66fb6 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MinPropertiesKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MinPropertiesKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new MinPropertiesDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_int($value) || $value < 0) { - throw $this->keywordException("{keyword} must be a non-negative integer", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new MinPropertiesDataKeyword( $pointer ); + } + } - if ($value === 0) { - return null; - } + if ( ! is_int( $value ) || $value < 0 ) { + throw $this->keywordException( '{keyword} must be a non-negative integer', $info ); + } - return new MinPropertiesKeyword($value); - } + if ( $value === 0 ) { + return null; + } + + return new MinPropertiesKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MinimumKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MinimumKeywordParser.php index 09fd9119..022ac1be 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MinimumKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MinimumKeywordParser.php @@ -1,5 +1,6 @@ exclusiveKeyword = $exclusiveKeyword; - } + /** + * @var string|null + */ + protected $exclusiveKeyword; - /** - * @inheritDoc - */ - public function type(): string - { - return self::TYPE_NUMBER; - } + /** + * @param string $keyword + * @param string|null $exclusiveKeyword + */ + public function __construct( string $keyword, $exclusiveKeyword = null ) { + parent::__construct( $keyword ); + $this->exclusiveKeyword = $exclusiveKeyword; + } - /** - * @inheritDoc - * @param \Opis\JsonSchema\Info\SchemaInfo $info - * @param \Opis\JsonSchema\Parsers\SchemaParser $parser - * @param object $shared - */ - public function parse($info, $parser, $shared) - { - $schema = $info->data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_NUMBER; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - $exclusive = false; - if ($parser->option('allowExclusiveMinMaxAsBool') && - $this->exclusiveKeyword !== null && - property_exists($schema, $this->exclusiveKeyword)) { - $exclusive = $schema->{$this->exclusiveKeyword} === true; - } + $value = $this->keywordValue( $schema ); - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return $exclusive - ? new ExclusiveMinimumDataKeyword($pointer) - : new MinimumDataKeyword($pointer); - } - } + $exclusive = false; + if ( $parser->option( 'allowExclusiveMinMaxAsBool' ) && + $this->exclusiveKeyword !== null && + property_exists( $schema, $this->exclusiveKeyword ) ) { + $exclusive = $schema->{$this->exclusiveKeyword} === true; + } - if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { - throw $this->keywordException('{keyword} must contain a valid number', $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return $exclusive + ? new ExclusiveMinimumDataKeyword( $pointer ) + : new MinimumDataKeyword( $pointer ); + } + } - return $exclusive - ? new ExclusiveMinimumKeyword($value) - : new MinimumKeyword($value); - } + if ( ! is_int( $value ) && ! is_float( $value ) || is_nan( $value ) || ! is_finite( $value ) ) { + throw $this->keywordException( '{keyword} must contain a valid number', $info ); + } + + return $exclusive + ? new ExclusiveMinimumKeyword( $value ) + : new MinimumKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/MultipleOfKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/MultipleOfKeywordParser.php index 516ca674..ab0cfa69 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/MultipleOfKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/MultipleOfKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_NUMBER; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new MultipleOfDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_int($value) && !is_float($value) || is_nan($value) || !is_finite($value)) { - throw $this->keywordException("{keyword} must be a valid number (integer or float)", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new MultipleOfDataKeyword( $pointer ); + } + } - if ($value <= 0) { - throw $this->keywordException("{keyword} must be greater than zero", $info); - } + if ( ! is_int( $value ) && ! is_float( $value ) || is_nan( $value ) || ! is_finite( $value ) ) { + throw $this->keywordException( '{keyword} must be a valid number (integer or float)', $info ); + } - return new MultipleOfKeyword($value); - } + if ( $value <= 0 ) { + throw $this->keywordException( '{keyword} must be greater than zero', $info ); + } + + return new MultipleOfKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/NotKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/NotKeywordParser.php index 7832bbce..d41ab803 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/NotKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/NotKeywordParser.php @@ -1,5 +1,6 @@ data(); - - if (!$this->keywordExists($schema)) { - return null; - } - - $value = $this->keywordValue($schema); - - if (is_bool($value)) { - if (!$value) { - return null; - } - } elseif (!is_object($value)) { - throw $this->keywordException("{keyword} must contain a json schema (object or boolean)", $info); - } - - return new NotKeyword($value); - } +class NotKeywordParser extends KeywordParser { + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_AFTER; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); + + if ( ! $this->keywordExists( $schema ) ) { + return null; + } + + $value = $this->keywordValue( $schema ); + + if ( is_bool( $value ) ) { + if ( ! $value ) { + return null; + } + } elseif ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must contain a json schema (object or boolean)', $info ); + } + + return new NotKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/OneOfKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/OneOfKeywordParser.php index a0570f2c..4ba57db7 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/OneOfKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/OneOfKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_AFTER; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if (!is_array($value)) { - throw $this->keywordException("{keyword} should be an array of json schemas", $info); - } + $value = $this->keywordValue( $schema ); - if (!$value) { - throw $this->keywordException("{keyword} must have at least one element", $info); - } + if ( ! is_array( $value ) ) { + throw $this->keywordException( '{keyword} should be an array of json schemas', $info ); + } - $valid = 0; + if ( ! $value ) { + throw $this->keywordException( '{keyword} must have at least one element', $info ); + } - foreach ($value as $index => $item) { - if ($item === false) { - continue; - } - if ($item === true) { - if (++$valid > 1) { - throw $this->keywordException("{keyword} contains multiple true values", $info); - } - continue; - } - if (!is_object($item)) { - throw $this->keywordException("{keyword}[{$index}] must be a json schema", $info); - } elseif (!count(get_object_vars($item))) { - if (++$valid > 1) { - throw $this->keywordException("{keyword} contains multiple true values", $info); - } - } - } + $valid = 0; - return new OneOfKeyword($value); - } + foreach ( $value as $index => $item ) { + if ( $item === false ) { + continue; + } + if ( $item === true ) { + if ( ++$valid > 1 ) { + throw $this->keywordException( '{keyword} contains multiple true values', $info ); + } + continue; + } + if ( ! is_object( $item ) ) { + throw $this->keywordException( "{keyword}[{$index}] must be a json schema", $info ); + } elseif ( ! count( get_object_vars( $item ) ) ) { + if ( ++$valid > 1 ) { + throw $this->keywordException( '{keyword} contains multiple true values', $info ); + } + } + } + + return new OneOfKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/PatternKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/PatternKeywordParser.php index 2ffd51e4..b54f79b0 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/PatternKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/PatternKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_STRING; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new PatternDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_string($value)) { - throw $this->keywordException("{keyword} value must be a string", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new PatternDataKeyword( $pointer ); + } + } - if (!Helper::isValidPattern($value)) { - throw $this->keywordException("{keyword} value must be a valid regex", $info); - } + if ( ! is_string( $value ) ) { + throw $this->keywordException( '{keyword} value must be a string', $info ); + } - return new PatternKeyword($value); - } + if ( ! Helper::isValidPattern( $value ) ) { + throw $this->keywordException( '{keyword} value must be a valid regex', $info ); + } + + return new PatternKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/PatternPropertiesKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/PatternPropertiesKeywordParser.php index c6aff641..5b03f714 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/PatternPropertiesKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/PatternPropertiesKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); - if (!is_object($value)) { - throw $this->keywordException("{keyword} must be an object", $info); - } + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - $list = []; + $value = $this->keywordValue( $schema ); + if ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be an object', $info ); + } - foreach ($value as $pattern => $item) { - if (!Helper::isValidPattern($pattern)) { - throw $this->keywordException("Each property name from {keyword} must be valid regex", $info); - } + $list = array(); - if (!is_bool($item) && !is_object($item)) { - throw $this->keywordException("{keyword}[{$pattern}] must be a json schema (object or boolean)", $info); - } + foreach ( $value as $pattern => $item ) { + if ( ! Helper::isValidPattern( $pattern ) ) { + throw $this->keywordException( 'Each property name from {keyword} must be valid regex', $info ); + } - $list[$pattern] = $item; - } + if ( ! is_bool( $item ) && ! is_object( $item ) ) { + throw $this->keywordException( "{keyword}[{$pattern}] must be a json schema (object or boolean)", $info ); + } - return $list ? new PatternPropertiesKeyword($list) : null; - } + $list[ $pattern ] = $item; + } + + return $list ? new PatternPropertiesKeyword( $list ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/PropertiesKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/PropertiesKeywordParser.php index faa3312b..72597166 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/PropertiesKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/PropertiesKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if (!is_object($value)) { - throw $this->keywordException("{keyword} must be an object", $info); - } + $value = $this->keywordValue( $schema ); - $list = []; + if ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be an object', $info ); + } - foreach ($value as $name => $s) { - if (!is_bool($s) && !is_object($s)) { - throw $this->keywordException("{keyword}[{$name}] must be a json schema (object or boolean)", $info); - } + $list = array(); - $list[$name] = $s; - } + foreach ( $value as $name => $s ) { + if ( ! is_bool( $s ) && ! is_object( $s ) ) { + throw $this->keywordException( "{keyword}[{$name}] must be a json schema (object or boolean)", $info ); + } - return $list ? new PropertiesKeyword($list) : null; - } + $list[ $name ] = $s; + } + + return $list ? new PropertiesKeyword( $list ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/PropertyNamesKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/PropertyNamesKeywordParser.php index 8e03e387..ce0dd870 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/PropertyNamesKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/PropertyNamesKeywordParser.php @@ -1,5 +1,6 @@ data(); - - if (!$this->keywordExists($schema)) { - return null; - } - - $value = $this->keywordValue($schema); - - if (is_bool($value)) { - if ($value) { - return null; - } - } elseif (!is_object($value)) { - throw $this->keywordException("{keyword} must be a valid json schema (object or boolean)", $info); - } - - return new PropertyNamesKeyword($value); - } +class PropertyNamesKeywordParser extends KeywordParser { + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); + + if ( ! $this->keywordExists( $schema ) ) { + return null; + } + + $value = $this->keywordValue( $schema ); + + if ( is_bool( $value ) ) { + if ( $value ) { + return null; + } + } elseif ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be a valid json schema (object or boolean)', $info ); + } + + return new PropertyNamesKeyword( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/RefKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/RefKeywordParser.php index 554ddcfa..ec06cd93 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/RefKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/RefKeywordParser.php @@ -1,5 +1,6 @@ variations = $variations; - } - - /** - * @inheritDoc - */ - public function type(): string - { - return self::TYPE_AFTER_REF; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\Info\SchemaInfo $info - * @param \Opis\JsonSchema\Parsers\SchemaParser $parser - * @param object $shared - */ - public function parse($info, $parser, $shared) - { - $ref = null; - $recursive = false; - $schema = $info->data(); - $variation = null; - - if ($this->keywordExists($schema)) { - $ref = $this->keywordValue($schema); - if (!is_string($ref) || $ref === '') { - throw $this->keywordException('{keyword} must be a non-empty string', $info); - } - } elseif ($this->variations) { - foreach ($this->variations as $v) { - if (!$this->keywordExists($schema, $v['ref'])) { - continue; - } - $ref = $this->keywordValue($schema, $v['ref']); - if ($v['fragment']) { - if (!preg_match('/^#[a-z][a-z0-9\\-.:_]*/i', $ref)) { - $this->keywordException("{keyword} value is malformed", $info, $v['ref']); - } - } elseif ($ref !== '#') { - $this->keywordException("{keyword} supports only '#' as value", $info, $v['ref']); - } - $variation = $v; - $recursive = true; - break; - } - if (!$recursive) { - return null; - } - } else { - return null; - } - - // Mappers - $mapper = null; - if ($parser->option('allowMappers') && property_exists($schema, '$map')) { - if (!is_object($schema->{'$map'}) && !is_array($schema->{'$map'})) { - throw $this->keywordException('$map keyword must be an object or an array', $info, '$map'); - } - - if (!empty($schema->{'$map'})) { - $mapper = $this->createVariables($parser, $schema->{'$map'}); - } - } - - // Globals - $globals = null; - if ($parser->option('allowGlobals') && property_exists($schema, '$globals')) { - if (!is_object($schema->{'$globals'})) { - throw $this->keywordException('$globals keyword must be an object', $info, '$globals'); - } - - if (!empty($schema->{'$globals'})) { - $globals = $this->createVariables($parser, $schema->{'$globals'}); - } - } - - // Pass slots - $slots = null; - if ($parser->option('allowSlots') && property_exists($schema, '$inject')) { - $slots = $this->parseInjectedSlots($info, $parser, '$inject'); - } - - if ($recursive) { - $ref = $info->idBaseRoot()->resolveRef($ref); - if ($variation['fragment']) { - return new RecursiveRefKeyword($ref->resolveRef('#'), $mapper, $globals, $slots, - $variation['ref'], $variation['anchor'], $ref->fragment()); - } - return new RecursiveRefKeyword($ref, $mapper, $globals, $slots, - $variation['ref'], $variation['anchor'], true); - } - - if ($ref === '#') { - return new URIRefKeyword(Uri::merge('#', $info->idBaseRoot()), $mapper, $globals, $slots, $this->keyword); - } - - if ($parser->option('allowTemplates') && UriTemplate::isTemplate($ref)) { - $tpl = new UriTemplate($ref); - - if ($tpl->hasPlaceholders()) { - $vars = null; - - if (property_exists($schema, '$vars')) { - if (!is_object($schema->{'$vars'})) { - throw $this->keywordException('$vars keyword must be an object', $info, '$vars'); - } - - if (!empty($schema->{'$vars'})) { - $vars = $this->createVariables($parser, $schema->{'$vars'}); - } - } - - return new TemplateRefKeyword( - $tpl, $vars, $mapper, - $globals, $slots, $this->keyword, - $parser->option('allowRelativeJsonPointerInRef') - ); - } - - unset($tpl); - } - - if ($ref[0] === '#') { - if (($pointer = JsonPointer::parse(substr($ref, 1))) && $pointer->isAbsolute()) { - return new PointerRefKeyword($pointer, $mapper, $globals, $slots, $this->keyword); - } - } elseif ($parser->option('allowRelativeJsonPointerInRef') && - ($pointer = JsonPointer::parse($ref)) && $pointer->isRelative()) { - return new PointerRefKeyword($pointer, $mapper, $globals, $slots, $this->keyword); - } - - $ref = Uri::merge($ref, $info->idBaseRoot(), true); - - if ($ref === null || !$ref->isAbsolute()) { - throw $this->keywordException('{keyword} must be a valid uri, uri-reference, uri-template or json-pointer', - $info); - } - - return new URIRefKeyword($ref, $mapper, $globals, $slots, $this->keyword); - } - - /** - * @param SchemaInfo $info - * @param SchemaParser $parser - * @param string $keyword - * @return string[]|object[]|Schema[] - */ - protected function parseInjectedSlots($info, $parser, $keyword) - { - $schema = $info->data(); - - if (!is_object($schema->{$keyword})) { - throw $this->keywordException('{keyword} keyword value must be an object', $info, $keyword); - } - - return $this->getSlotSchemas($info, $parser, $schema->{$keyword}, [$keyword]); - } - - /** - * @param SchemaInfo $info - * @param SchemaParser $parser - * @param object $slots - * @param array $path - * @return null - */ - protected function getSlotSchemas($info, $parser, $slots, $path) - { - $keyword = null; - if ($path) { - $keyword = end($path); - $path = array_merge($info->path(), $path); - } else { - $path = $info->path(); - } - - $list = []; - - foreach ($slots as $name => $value) { - if ($value === null) { - continue; - } - if (is_string($value) || is_object($value)) { - $list[$name] = $value; - } elseif (is_bool($value)) { - $list[$name] = $parser->parseSchema(new SchemaInfo( - $value, null, $info->id() ?? $info->base(), $info->root(), - array_merge($path, [$name]), - $info->draft() ?? $parser->defaultDraftVersion() - )); - } else { - throw $this->keywordException('Slots must contain valid json schemas or slot names', $info, $keyword); - } - } - - return $list ?: null; - } +class RefKeywordParser extends KeywordParser { + + use VariablesTrait; + + /** + * @var mixed[]|null + */ + protected $variations; + + public function __construct( string $keyword, $variations = null ) { + parent::__construct( $keyword ); + $this->variations = $variations; + } + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_AFTER_REF; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $ref = null; + $recursive = false; + $schema = $info->data(); + $variation = null; + + if ( $this->keywordExists( $schema ) ) { + $ref = $this->keywordValue( $schema ); + if ( ! is_string( $ref ) || $ref === '' ) { + throw $this->keywordException( '{keyword} must be a non-empty string', $info ); + } + } elseif ( $this->variations ) { + foreach ( $this->variations as $v ) { + if ( ! $this->keywordExists( $schema, $v['ref'] ) ) { + continue; + } + $ref = $this->keywordValue( $schema, $v['ref'] ); + if ( $v['fragment'] ) { + if ( ! preg_match( '/^#[a-z][a-z0-9\\-.:_]*/i', $ref ) ) { + $this->keywordException( '{keyword} value is malformed', $info, $v['ref'] ); + } + } elseif ( $ref !== '#' ) { + $this->keywordException( "{keyword} supports only '#' as value", $info, $v['ref'] ); + } + $variation = $v; + $recursive = true; + break; + } + if ( ! $recursive ) { + return null; + } + } else { + return null; + } + + // Mappers + $mapper = null; + if ( $parser->option( 'allowMappers' ) && property_exists( $schema, '$map' ) ) { + if ( ! is_object( $schema->{'$map'} ) && ! is_array( $schema->{'$map'} ) ) { + throw $this->keywordException( '$map keyword must be an object or an array', $info, '$map' ); + } + + if ( ! empty( $schema->{'$map'} ) ) { + $mapper = $this->createVariables( $parser, $schema->{'$map'} ); + } + } + + // Globals + $globals = null; + if ( $parser->option( 'allowGlobals' ) && property_exists( $schema, '$globals' ) ) { + if ( ! is_object( $schema->{'$globals'} ) ) { + throw $this->keywordException( '$globals keyword must be an object', $info, '$globals' ); + } + + if ( ! empty( $schema->{'$globals'} ) ) { + $globals = $this->createVariables( $parser, $schema->{'$globals'} ); + } + } + + // Pass slots + $slots = null; + if ( $parser->option( 'allowSlots' ) && property_exists( $schema, '$inject' ) ) { + $slots = $this->parseInjectedSlots( $info, $parser, '$inject' ); + } + + if ( $recursive ) { + $ref = $info->idBaseRoot()->resolveRef( $ref ); + if ( $variation['fragment'] ) { + return new RecursiveRefKeyword( + $ref->resolveRef( '#' ), + $mapper, + $globals, + $slots, + $variation['ref'], + $variation['anchor'], + $ref->fragment() + ); + } + return new RecursiveRefKeyword( + $ref, + $mapper, + $globals, + $slots, + $variation['ref'], + $variation['anchor'], + true + ); + } + + if ( $ref === '#' ) { + return new URIRefKeyword( Uri::merge( '#', $info->idBaseRoot() ), $mapper, $globals, $slots, $this->keyword ); + } + + if ( $parser->option( 'allowTemplates' ) && UriTemplate::isTemplate( $ref ) ) { + $tpl = new UriTemplate( $ref ); + + if ( $tpl->hasPlaceholders() ) { + $vars = null; + + if ( property_exists( $schema, '$vars' ) ) { + if ( ! is_object( $schema->{'$vars'} ) ) { + throw $this->keywordException( '$vars keyword must be an object', $info, '$vars' ); + } + + if ( ! empty( $schema->{'$vars'} ) ) { + $vars = $this->createVariables( $parser, $schema->{'$vars'} ); + } + } + + return new TemplateRefKeyword( + $tpl, + $vars, + $mapper, + $globals, + $slots, + $this->keyword, + $parser->option( 'allowRelativeJsonPointerInRef' ) + ); + } + + unset( $tpl ); + } + + if ( $ref[0] === '#' ) { + if ( ( $pointer = JsonPointer::parse( substr( $ref, 1 ) ) ) && $pointer->isAbsolute() ) { + return new PointerRefKeyword( $pointer, $mapper, $globals, $slots, $this->keyword ); + } + } elseif ( $parser->option( 'allowRelativeJsonPointerInRef' ) && + ( $pointer = JsonPointer::parse( $ref ) ) && $pointer->isRelative() ) { + return new PointerRefKeyword( $pointer, $mapper, $globals, $slots, $this->keyword ); + } + + $ref = Uri::merge( $ref, $info->idBaseRoot(), true ); + + if ( $ref === null || ! $ref->isAbsolute() ) { + throw $this->keywordException( + '{keyword} must be a valid uri, uri-reference, uri-template or json-pointer', + $info + ); + } + + return new URIRefKeyword( $ref, $mapper, $globals, $slots, $this->keyword ); + } + + /** + * @param SchemaInfo $info + * @param SchemaParser $parser + * @param string $keyword + * @return string[]|object[]|Schema[] + */ + protected function parseInjectedSlots( $info, $parser, $keyword ) { + $schema = $info->data(); + + if ( ! is_object( $schema->{$keyword} ) ) { + throw $this->keywordException( '{keyword} keyword value must be an object', $info, $keyword ); + } + + return $this->getSlotSchemas( $info, $parser, $schema->{$keyword}, array( $keyword ) ); + } + + /** + * @param SchemaInfo $info + * @param SchemaParser $parser + * @param object $slots + * @param array $path + * @return null + */ + protected function getSlotSchemas( $info, $parser, $slots, $path ) { + $keyword = null; + if ( $path ) { + $keyword = end( $path ); + $path = array_merge( $info->path(), $path ); + } else { + $path = $info->path(); + } + + $list = array(); + + foreach ( $slots as $name => $value ) { + if ( $value === null ) { + continue; + } + if ( is_string( $value ) || is_object( $value ) ) { + $list[ $name ] = $value; + } elseif ( is_bool( $value ) ) { + $list[ $name ] = $parser->parseSchema( + new SchemaInfo( + $value, + null, + $info->id() ?? $info->base(), + $info->root(), + array_merge( $path, array( $name ) ), + $info->draft() ?? $parser->defaultDraftVersion() + ) + ); + } else { + throw $this->keywordException( 'Slots must contain valid json schemas or slot names', $info, $keyword ); + } + } + + return $list ?: null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/RequiredKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/RequiredKeywordParser.php index b723877f..1cf40588 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/RequiredKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/RequiredKeywordParser.php @@ -1,5 +1,6 @@ data(); - - if (!$this->keywordExists($schema)) { - return null; - } - - $value = $this->keywordValue($schema); - - $filter = $this->propertiesFilter($parser, $schema); - - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new RequiredDataKeyword($pointer, $filter); - } - } - - if (!is_array($value)) { - throw $this->keywordException("{keyword} must be an array of strings", $info); - } - - foreach ($value as $name) { - if (!is_string($name)) { - throw $this->keywordException("{keyword} must be an array of strings", $info); - } - } - - if ($filter) { - $value = array_filter($value, $filter === null ? function ($value, $key) : bool { - return !empty($value); - } : $filter, $filter === null ? ARRAY_FILTER_USE_BOTH : 0); - } - - return $value ? new RequiredKeyword(array_unique($value)) : null; - } - - /** - * @param SchemaParser $parser - * @param object $schema - * @return callable|null - */ - protected function propertiesFilter($parser, $schema) - { - if (!$parser->option('allowDefaults')) { - return null; - } - - if (!property_exists($schema, 'properties') || !is_object($schema->properties)) { - return null; - } - - $props = $schema->properties; - - return static function (string $name) use ($props) { - if (!property_exists($props, $name)) { - return true; - } - - if (is_object($props->{$name}) && property_exists($props->{$name}, 'default')) { - return false; - } - - return true; - }; - } +class RequiredKeywordParser extends KeywordParser { + + use DataKeywordTrait; + + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_OBJECT; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); + + if ( ! $this->keywordExists( $schema ) ) { + return null; + } + + $value = $this->keywordValue( $schema ); + + $filter = $this->propertiesFilter( $parser, $schema ); + + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new RequiredDataKeyword( $pointer, $filter ); + } + } + + if ( ! is_array( $value ) ) { + throw $this->keywordException( '{keyword} must be an array of strings', $info ); + } + + foreach ( $value as $name ) { + if ( ! is_string( $name ) ) { + throw $this->keywordException( '{keyword} must be an array of strings', $info ); + } + } + + if ( $filter ) { + $value = array_filter( + $value, + $filter === null ? function ( $value, $key ): bool { + return ! empty( $value ); + } : $filter, + $filter === null ? ARRAY_FILTER_USE_BOTH : 0 + ); + } + + return $value ? new RequiredKeyword( array_unique( $value ) ) : null; + } + + /** + * @param SchemaParser $parser + * @param object $schema + * @return callable|null + */ + protected function propertiesFilter( $parser, $schema ) { + if ( ! $parser->option( 'allowDefaults' ) ) { + return null; + } + + if ( ! property_exists( $schema, 'properties' ) || ! is_object( $schema->properties ) ) { + return null; + } + + $props = $schema->properties; + + return static function ( string $name ) use ( $props ) { + if ( ! property_exists( $props, $name ) ) { + return true; + } + + if ( is_object( $props->{$name} ) && property_exists( $props->{$name}, 'default' ) ) { + return false; + } + + return true; + }; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/SlotsKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/SlotsKeywordParser.php index 609e750d..7ee46334 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/SlotsKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/SlotsKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_APPEND; + } - if (!$parser->option('allowSlots') || !$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $parser->option( 'allowSlots' ) || ! $this->keywordExists( $schema ) ) { + return null; + } - if (!is_object($value)) { - throw $this->keywordException('{keyword} keyword value must be an object', $info); - } + $value = $this->keywordValue( $schema ); - $slots = []; - foreach ($value as $name => $fallback) { - if (!is_string($name) || $name === '') { - continue; - } - if (is_bool($fallback) || is_string($fallback) || is_object($fallback)) { - $slots[$name] = $fallback; - } - } + if ( ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} keyword value must be an object', $info ); + } - return $slots ? new SlotsKeyword($slots) : null; - } + $slots = array(); + foreach ( $value as $name => $fallback ) { + if ( ! is_string( $name ) || $name === '' ) { + continue; + } + if ( is_bool( $fallback ) || is_string( $fallback ) || is_object( $fallback ) ) { + $slots[ $name ] = $fallback; + } + } + + return $slots ? new SlotsKeyword( $slots ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/TypeKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/TypeKeywordParser.php index 8aa9d79b..ffb9b563 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/TypeKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/TypeKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_BEFORE; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $type = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if (is_string($type)) { - $type = [$type]; - } elseif (!is_array($type)) { - throw $this->keywordException('{keyword} can only be a string or an array of string', $info); - } + $type = $this->keywordValue( $schema ); - foreach ($type as $t) { - if (!Helper::isValidJsonType($t)) { - throw $this->keywordException("{keyword} contains invalid json type: {$t}", $info); - } - } + if ( is_string( $type ) ) { + $type = array( $type ); + } elseif ( ! is_array( $type ) ) { + throw $this->keywordException( '{keyword} can only be a string or an array of string', $info ); + } - $type = array_unique($type); + foreach ( $type as $t ) { + if ( ! Helper::isValidJsonType( $t ) ) { + throw $this->keywordException( "{keyword} contains invalid json type: {$t}", $info ); + } + } - if (!isset($shared->types)) { - $shared->types = $type; - } else { - $shared->types = array_unique(array_merge($shared->types, $type)); - } + $type = array_unique( $type ); - $count = count($type); + if ( ! isset( $shared->types ) ) { + $shared->types = $type; + } else { + $shared->types = array_unique( array_merge( $shared->types, $type ) ); + } - if ($count === 0) { - throw $this->keywordException("{keyword} cannot be an empty array", $info); - } elseif ($count === 1) { - $type = reset($type); - } + $count = count( $type ); - return new TypeKeyword($type); - } + if ( $count === 0 ) { + throw $this->keywordException( '{keyword} cannot be an empty array', $info ); + } elseif ( $count === 1 ) { + $type = reset( $type ); + } + + return new TypeKeyword( $type ); + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/UnevaluatedItemsKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/UnevaluatedItemsKeywordParser.php index ff88103e..a71f140e 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/UnevaluatedItemsKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/UnevaluatedItemsKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_AFTER_REF; + } - if (!$this->keywordExists($schema) || !$parser->option('allowUnevaluated')) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); -// if (!$this->makesSense($schema)) { -// return null; -// } + if ( ! $this->keywordExists( $schema ) || ! $parser->option( 'allowUnevaluated' ) ) { + return null; + } - $value = $this->keywordValue($schema); + // if (!$this->makesSense($schema)) { + // return null; + // } - if (!is_bool($value) && !is_object($value)) { - throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); - } + $value = $this->keywordValue( $schema ); - return new UnevaluatedItemsKeyword($value); - } + if ( ! is_bool( $value ) && ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be a json schema (object or boolean)', $info ); + } - /** - * @param object $schema - */ - protected function makesSense($schema): bool - { - if (property_exists($schema, 'additionalItems')) { - return false; - } -// if (property_exists($schema, 'contains')) { -// return false; -// } - if (property_exists($schema, 'items') && !is_array($schema->items)) { - return false; - } + return new UnevaluatedItemsKeyword( $value ); + } - return true; - } + /** + * @param object $schema + */ + protected function makesSense( $schema ): bool { + if ( property_exists( $schema, 'additionalItems' ) ) { + return false; + } + // if (property_exists($schema, 'contains')) { + // return false; + // } + if ( property_exists( $schema, 'items' ) && ! is_array( $schema->items ) ) { + return false; + } + + return true; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/UnevaluatedPropertiesKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/UnevaluatedPropertiesKeywordParser.php index 953088bb..7fb0d7f2 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/UnevaluatedPropertiesKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/UnevaluatedPropertiesKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - if (!$this->keywordExists($schema) || !$parser->option('allowUnevaluated')) { - return null; - } + if ( ! $this->keywordExists( $schema ) || ! $parser->option( 'allowUnevaluated' ) ) { + return null; + } -// if (!$this->makesSense($schema)) { -// return null; -// } + // if (!$this->makesSense($schema)) { + // return null; + // } - $value = $this->keywordValue($schema); + $value = $this->keywordValue( $schema ); - if (!is_bool($value) && !is_object($value)) { - throw $this->keywordException("{keyword} must be a json schema (object or boolean)", $info); - } + if ( ! is_bool( $value ) && ! is_object( $value ) ) { + throw $this->keywordException( '{keyword} must be a json schema (object or boolean)', $info ); + } - return new UnevaluatedPropertiesKeyword($value); - } + return new UnevaluatedPropertiesKeyword( $value ); + } - /** - * @param object $schema - */ - protected function makesSense($schema): bool - { - if (property_exists($schema, 'additionalProperties')) { - return false; - } + /** + * @param object $schema + */ + protected function makesSense( $schema ): bool { + if ( property_exists( $schema, 'additionalProperties' ) ) { + return false; + } - return true; - } + return true; + } } diff --git a/src/opis/json-schema/src/Parsers/Keywords/UniqueItemsKeywordParser.php b/src/opis/json-schema/src/Parsers/Keywords/UniqueItemsKeywordParser.php index 12bbcfbe..e2dbf527 100644 --- a/src/opis/json-schema/src/Parsers/Keywords/UniqueItemsKeywordParser.php +++ b/src/opis/json-schema/src/Parsers/Keywords/UniqueItemsKeywordParser.php @@ -1,5 +1,6 @@ data(); + /** + * @inheritDoc + */ + public function type(): string { + return self::TYPE_ARRAY; + } - if (!$this->keywordExists($schema)) { - return null; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + $schema = $info->data(); - $value = $this->keywordValue($schema); + if ( ! $this->keywordExists( $schema ) ) { + return null; + } - if ($this->isDataKeywordAllowed($parser, $this->keyword)) { - if ($pointer = $this->getDataKeywordPointer($value)) { - return new UniqueItemsDataKeyword($pointer); - } - } + $value = $this->keywordValue( $schema ); - if (!is_bool($value)) { - throw $this->keywordException("{keyword} must be a boolean", $info); - } + if ( $this->isDataKeywordAllowed( $parser, $this->keyword ) ) { + if ( $pointer = $this->getDataKeywordPointer( $value ) ) { + return new UniqueItemsDataKeyword( $pointer ); + } + } - return $value ? new UniqueItemsKeyword() : null; - } + if ( ! is_bool( $value ) ) { + throw $this->keywordException( '{keyword} must be a boolean', $info ); + } + + return $value ? new UniqueItemsKeyword() : null; + } } diff --git a/src/opis/json-schema/src/Parsers/PragmaParser.php b/src/opis/json-schema/src/Parsers/PragmaParser.php index 1f6a7c72..183b52e0 100644 --- a/src/opis/json-schema/src/Parsers/PragmaParser.php +++ b/src/opis/json-schema/src/Parsers/PragmaParser.php @@ -1,5 +1,6 @@ pragma = $pragma; - } + /** + * @var string + */ + protected $pragma; - /** - * @param SchemaInfo $info - * @param SchemaParser $parser - * @param object $shared - * @return Pragma|null - */ - abstract public function parse($info, $parser, $shared); + /** + * @param string $pragma + */ + public function __construct( string $pragma ) { + $this->pragma = $pragma; + } - /** - * @param object|SchemaInfo $schema - * @param string|null $pragma - * @return bool - */ - protected function pragmaExists($schema, $pragma = null): bool - { - if ($schema instanceof SchemaInfo) { - $schema = $schema->isObject() ? $schema->data() : null; - } + /** + * @param SchemaInfo $info + * @param SchemaParser $parser + * @param object $shared + * @return Pragma|null + */ + abstract public function parse( $info, $parser, $shared ); - return is_object($schema) && property_exists($schema, $pragma ?? $this->pragma); - } + /** + * @param object|SchemaInfo $schema + * @param string|null $pragma + * @return bool + */ + protected function pragmaExists( $schema, $pragma = null ): bool { + if ( $schema instanceof SchemaInfo ) { + $schema = $schema->isObject() ? $schema->data() : null; + } - /** - * @param object|SchemaInfo $schema - * @param string|null $pragma - * @return mixed - */ - protected function pragmaValue($schema, $pragma = null) - { - if ($schema instanceof SchemaInfo) { - $schema = $schema->isObject() ? $schema->data() : null; - } + return is_object( $schema ) && property_exists( $schema, $pragma ?? $this->pragma ); + } - return is_object($schema) ? $schema->{$pragma ?? $this->pragma} : null; - } + /** + * @param object|SchemaInfo $schema + * @param string|null $pragma + * @return mixed + */ + protected function pragmaValue( $schema, $pragma = null ) { + if ( $schema instanceof SchemaInfo ) { + $schema = $schema->isObject() ? $schema->data() : null; + } - /** - * @param string $message - * @param SchemaInfo $info - * @param string|null $pragma - * @return InvalidPragmaException - */ - protected function pragmaException($message, $info, $pragma = null): InvalidPragmaException - { - $pragma = $pragma ?? $this->pragma; + return is_object( $schema ) ? $schema->{$pragma ?? $this->pragma} : null; + } - return new InvalidPragmaException(str_replace('{pragma}', $pragma, $message), $pragma, $info); - } + /** + * @param string $message + * @param SchemaInfo $info + * @param string|null $pragma + * @return InvalidPragmaException + */ + protected function pragmaException( $message, $info, $pragma = null ): InvalidPragmaException { + $pragma = $pragma ?? $this->pragma; + + return new InvalidPragmaException( str_replace( '{pragma}', $pragma, $message ), $pragma, $info ); + } } diff --git a/src/opis/json-schema/src/Parsers/Pragmas/CastPragmaParser.php b/src/opis/json-schema/src/Parsers/Pragmas/CastPragmaParser.php index b382520f..e9e2620e 100644 --- a/src/opis/json-schema/src/Parsers/Pragmas/CastPragmaParser.php +++ b/src/opis/json-schema/src/Parsers/Pragmas/CastPragmaParser.php @@ -1,5 +1,6 @@ pragmaExists($info)) { - return null; - } +class CastPragmaParser extends PragmaParser { - $value = $this->pragmaValue($info); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $this->pragmaExists( $info ) ) { + return null; + } - if (!is_string($value) || !Helper::isValidJsonType($value)) { - throw $this->pragmaException('Pragma {pragma} must contain a valid json type name', $info); - } + $value = $this->pragmaValue( $info ); - return new CastPragma($value); - } + if ( ! is_string( $value ) || ! Helper::isValidJsonType( $value ) ) { + throw $this->pragmaException( 'Pragma {pragma} must contain a valid json type name', $info ); + } + + return new CastPragma( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Pragmas/GlobalsPragmaParser.php b/src/opis/json-schema/src/Parsers/Pragmas/GlobalsPragmaParser.php index 4bb99d65..b4aab506 100644 --- a/src/opis/json-schema/src/Parsers/Pragmas/GlobalsPragmaParser.php +++ b/src/opis/json-schema/src/Parsers/Pragmas/GlobalsPragmaParser.php @@ -1,5 +1,6 @@ option('allowGlobals') || !$this->pragmaExists($info)) { - return null; - } + use VariablesTrait; - $value = $this->pragmaValue($info); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $parser->option( 'allowGlobals' ) || ! $this->pragmaExists( $info ) ) { + return null; + } - if (!is_object($value)) { - throw $this->pragmaException('Pragma {pragma} must be an object', $info); - } + $value = $this->pragmaValue( $info ); - $value = get_object_vars($value); + if ( ! is_object( $value ) ) { + throw $this->pragmaException( 'Pragma {pragma} must be an object', $info ); + } - return $value ? new GlobalsPragma($this->createVariables($parser, $value)) : null; - } + $value = get_object_vars( $value ); + + return $value ? new GlobalsPragma( $this->createVariables( $parser, $value ) ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/Pragmas/MaxErrorsPragmaParser.php b/src/opis/json-schema/src/Parsers/Pragmas/MaxErrorsPragmaParser.php index dcff09e5..a58113f3 100644 --- a/src/opis/json-schema/src/Parsers/Pragmas/MaxErrorsPragmaParser.php +++ b/src/opis/json-schema/src/Parsers/Pragmas/MaxErrorsPragmaParser.php @@ -1,5 +1,6 @@ pragmaExists($info)) { - return null; - } +class MaxErrorsPragmaParser extends PragmaParser { - $value = $this->pragmaValue($info); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $this->pragmaExists( $info ) ) { + return null; + } - if (!is_int($value)) { - throw $this->pragmaException('Pragma {pragma} must be an integer', $info); - } + $value = $this->pragmaValue( $info ); - return new MaxErrorsPragma($value); - } + if ( ! is_int( $value ) ) { + throw $this->pragmaException( 'Pragma {pragma} must be an integer', $info ); + } + + return new MaxErrorsPragma( $value ); + } } diff --git a/src/opis/json-schema/src/Parsers/Pragmas/SlotsPragmaParser.php b/src/opis/json-schema/src/Parsers/Pragmas/SlotsPragmaParser.php index 4bd68cfe..0ddd4806 100644 --- a/src/opis/json-schema/src/Parsers/Pragmas/SlotsPragmaParser.php +++ b/src/opis/json-schema/src/Parsers/Pragmas/SlotsPragmaParser.php @@ -1,5 +1,6 @@ option('allowSlots') || !$this->pragmaExists($info)) { - return null; - } +class SlotsPragmaParser extends PragmaParser { - $value = $this->pragmaValue($info); + /** + * @inheritDoc + * @param \Opis\JsonSchema\Info\SchemaInfo $info + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + * @param object $shared + */ + public function parse( $info, $parser, $shared ) { + if ( ! $parser->option( 'allowSlots' ) || ! $this->pragmaExists( $info ) ) { + return null; + } - if (!is_object($value)) { - throw $this->pragmaException('Pragma {pragma} must be an object', $info); - } + $value = $this->pragmaValue( $info ); - $list = []; + if ( ! is_object( $value ) ) { + throw $this->pragmaException( 'Pragma {pragma} must be an object', $info ); + } - foreach ($value as $name => $slot) { - if ($slot === null) { - continue; - } + $list = array(); - if (is_bool($slot)) { + foreach ( $value as $name => $slot ) { + if ( $slot === null ) { + continue; + } - $list[$name] = $parser->parseSchema(new SchemaInfo( - $slot, null, $info->base(), $info->root(), - array_merge($info->path(), [$this->pragma, $name]), - $info->draft() ?? $parser->defaultDraftVersion() - )); - } elseif (is_string($slot) || is_object($slot)) { - $list[$name] = $slot; - } else { - throw $this->pragmaException('Pragma {pragma} contains invalid value for slot ' . $name, $info); - } - } + if ( is_bool( $slot ) ) { - return $list ? new SlotsPragma($list) : null; - } + $list[ $name ] = $parser->parseSchema( + new SchemaInfo( + $slot, + null, + $info->base(), + $info->root(), + array_merge( $info->path(), array( $this->pragma, $name ) ), + $info->draft() ?? $parser->defaultDraftVersion() + ) + ); + } elseif ( is_string( $slot ) || is_object( $slot ) ) { + $list[ $name ] = $slot; + } else { + throw $this->pragmaException( 'Pragma {pragma} contains invalid value for slot ' . $name, $info ); + } + } + + return $list ? new SlotsPragma( $list ) : null; + } } diff --git a/src/opis/json-schema/src/Parsers/ResolverTrait.php b/src/opis/json-schema/src/Parsers/ResolverTrait.php index a4916fda..705715c7 100644 --- a/src/opis/json-schema/src/Parsers/ResolverTrait.php +++ b/src/opis/json-schema/src/Parsers/ResolverTrait.php @@ -1,5 +1,6 @@ $super) { - if (!isset($list[$sub]) && isset($list[$super])) { - $list[$sub] = $list[$super]; - } - } +trait ResolverTrait { - return $list; - } -} \ No newline at end of file + /** + * @param array $list + * @return array + */ + protected function resolveSubTypes( $list ): array { + foreach ( Helper::JSON_SUBTYPES as $sub => $super ) { + if ( ! isset( $list[ $sub ] ) && isset( $list[ $super ] ) ) { + $list[ $sub ] = $list[ $super ]; + } + } + + return $list; + } +} diff --git a/src/opis/json-schema/src/Parsers/SchemaParser.php b/src/opis/json-schema/src/Parsers/SchemaParser.php index 2678c622..1744c098 100644 --- a/src/opis/json-schema/src/Parsers/SchemaParser.php +++ b/src/opis/json-schema/src/Parsers/SchemaParser.php @@ -1,5 +1,6 @@ true, - 'allowFormats' => true, - 'allowMappers' => true, - 'allowTemplates' => true, - 'allowGlobals' => true, - 'allowDefaults' => true, - 'allowSlots' => true, - 'allowKeywordValidators' => true, - 'allowPragmas' => true, - - 'allowDataKeyword' => true, - 'allowKeywordsAlongsideRef' => false, - 'allowUnevaluated' => true, - 'allowRelativeJsonPointerInRef' => true, - 'allowExclusiveMinMaxAsBool' => true, - - 'keepDependenciesKeyword' => true, - 'keepAdditionalItemsKeyword' => true, - - 'decodeContent' => ['06', '07'], - 'defaultDraft' => self::DEFAULT_DRAFT, - - 'varRefKey' => '$ref', - 'varEachKey' => '$each', - 'varDefaultKey' => 'default', - ]; - - /** @var array */ - protected $options; - - /** @var Draft[] */ - protected $drafts; - - /** @var array */ - protected $resolvers; - - /** - * @param array $resolvers - * @param array $options - * @param Vocabulary|null $extraVocabulary - */ - public function __construct( - array $resolvers = [], - array $options = [], - $extraVocabulary = null - ) - { - if ($options) { - $this->options = $options + self::DEFAULT_OPTIONS; - } else { - $this->options = self::DEFAULT_OPTIONS; - } - - $this->resolvers = $this->getResolvers($resolvers); - - $this->drafts = $this->getDrafts($extraVocabulary ?? new DefaultVocabulary()); - } - - /** - * @param Vocabulary|null $extraVocabulary - * @return array - */ - protected function getDrafts($extraVocabulary): array - { - return [ - '06' => new Draft06($extraVocabulary), - '07' => new Draft07($extraVocabulary), - '2019-09' => new Draft201909($extraVocabulary), - '2020-12' => new Draft202012($extraVocabulary), - ]; - } - - /** - * @param array $resolvers - * @return array - */ - protected function getResolvers($resolvers): array - { - if (!array_key_exists('format', $resolvers)) { - $resolvers['format'] = new FormatResolver(); - } - - if (!array_key_exists('contentEncoding', $resolvers)) { - $resolvers['contentEncoding'] = new ContentEncodingResolver(); - } - - if (!array_key_exists('contentMediaType', $resolvers)) { - $resolvers['contentMediaType'] = new ContentMediaTypeResolver(); - } - - if (!array_key_exists('$filters', $resolvers)) { - $resolvers['$filters'] = new FilterResolver(); - } - - return $resolvers; - } - - /** - * @param string $name - * @param null $default - * @return mixed|null - */ - public function option($name, $default = null) - { - return $this->options[$name] ?? $default; - } - - /** - * @param string $name - * @param $value - * @return $this - */ - public function setOption($name, $value): self - { - $this->options[$name] = $value; - - return $this; - } - - /** - * @return array - */ - public function getOptions(): array - { - return $this->options; - } - - /** - * @param string $name - * @param $resolver - * @return $this - */ - public function setResolver($name, $resolver): self - { - $this->resolvers[$name] = $resolver; - - return $this; - } - - /** - * @return null|FilterResolver - */ - public function getFilterResolver() - { - return $this->getResolver('$filters'); - } - - /** - * @param null|FilterResolver $resolver - * @return $this - */ - public function setFilterResolver($resolver): self - { - return $this->setResolver('$filters', $resolver); - } - - /** - * @return null|FormatResolver - */ - public function getFormatResolver() - { - return $this->getResolver('format'); - } - - /** - * @param FormatResolver|null $resolver - * @return $this - */ - public function setFormatResolver($resolver): self - { - return $this->setResolver('format', $resolver); - } - - /** - * @return null|ContentEncodingResolver - */ - public function getContentEncodingResolver() - { - return $this->getResolver('contentEncoding'); - } - - /** - * @param ContentEncodingResolver|null $resolver - * @return $this - */ - public function setContentEncodingResolver($resolver): self - { - return $this->setResolver('contentEncoding', $resolver); - } - - /** - * @return null|ContentMediaTypeResolver - */ - public function getMediaTypeResolver() - { - return $this->getResolver('contentMediaType'); - } - - /** - * @param ContentMediaTypeResolver|null $resolver - * @return $this - */ - public function setMediaTypeResolver($resolver): self - { - return $this->setResolver('contentMediaType', $resolver); - } - - /** - * @return string - */ - public function defaultDraftVersion(): string - { - return $this->option('defaultDraft', self::DEFAULT_DRAFT); - } - - /** - * @param string $draft - * @return $this - */ - public function setDefaultDraftVersion($draft): self - { - return $this->setOption('defaultDraft', $draft); - } - - /** - * @param string $schema - * @return string|null - */ - public function parseDraftVersion($schema) - { - if (!preg_match(self::DRAFT_REGEX, $schema, $m)) { - return null; - } - - return $m[1] ?? null; - } - - /** - * @param object $schema - * @return string|null - */ - public function parseId($schema) - { - if (property_exists($schema, '$id') && is_string($schema->{'$id'})) { - return $schema->{'$id'}; - } - - return null; - } - - /** - * @param object $schema - * @param string $draft - * @return string|null - */ - public function parseAnchor($schema, $draft) - { - if (!property_exists($schema, '$anchor') || - !isset($this->drafts[$draft]) || - !$this->drafts[$draft]->supportsAnchorId()) { - return null; - } - - $anchor = $schema->{'$anchor'}; - - if (!is_string($anchor) || !preg_match(self::ANCHOR_REGEX, $anchor)) { - return null; - } - - return $anchor; - } - - /** - * @param object $schema - * @return string|null - */ - public function parseSchemaDraft($schema) - { - if (!property_exists($schema, '$schema') || !is_string($schema->{'$schema'})) { - return null; - } - - return $this->parseDraftVersion($schema->{'$schema'}); - } - - /** - * @param object $schema - * @param Uri $id - * @param callable $handle_id - * @param callable $handle_object - * @param string|null $draft - * @return Schema|null - */ - public function parseRootSchema( - $schema, - $id, - $handle_id, - $handle_object, - $draft = null - ) - { - $existent = false; - if (property_exists($schema, '$id')) { - $existent = true; - $id = Uri::parse($schema->{'$id'}, true); - } - - if ($id instanceof Uri) { - if ($id->fragment() === null) { - $id = Uri::merge($id, null, true); - } - } else { - throw new ParseException('Root schema id must be an URI', new SchemaInfo($schema, $id)); - } - - if (!$id->isAbsolute()) { - throw new ParseException('Root schema id must be an absolute URI', new SchemaInfo($schema, $id)); - } - - if ($id->fragment() !== '') { - throw new ParseException('Root schema id must have an empty fragment or none', new SchemaInfo($schema, $id)); - } - - // Check if id exists - if ($resolved = $handle_id($id)) { - return $resolved; - } - - if (property_exists($schema, '$schema')) { - if (!is_string($schema->{'$schema'})) { - throw new ParseException('Schema draft must be a string', new SchemaInfo($schema, $id)); - } - $draft = $this->parseDraftVersion($schema->{'$schema'}); - } - - if ($draft === null) { - $draft = $this->defaultDraftVersion(); - } - - if (!$existent) { - $schema->{'$id'} = (string)$id; - } - - $resolved = $handle_object($schema, $id, $draft); - - if (!$existent) { - unset($schema->{'$id'}); - } - - return $resolved; - } - - /** - * @param SchemaInfo $info - * @return Schema - */ - public function parseSchema($info): Schema - { - if ($info->isBoolean()) { - return new BooleanSchema($info); - } - - try { - return $this->parseSchemaObject($info); - } catch (SchemaException $exception) { - return new ExceptionSchema($info, $exception); - } - } - - /** - * @param string $version - * @return Draft|null - */ - public function draft($version) - { - return $this->drafts[$version] ?? null; - } - - /** - * @param Draft $draft - * @return $this - */ - public function addDraft($draft): self - { - $this->drafts[$draft->version()] = $draft; - - return $this; - } - - /** - * @return string[] - */ - public function supportedDrafts(): array - { - return array_keys($this->drafts); - } - - /** - * @param array $options - * @return $this - */ - protected function setOptions($options): self - { - $this->options = $options + $this->options; - - return $this; - } - - /** - * @param string $name - * @return mixed|null - */ - protected function getResolver($name) - { - $resolver = $this->resolvers[$name] ?? null; - - if (!is_object($resolver)) { - return null; - } - - return $resolver; - } - - /** - * @param SchemaInfo $info - * @return Schema - */ - protected function parseSchemaObject($info): Schema - { - $draftObject = $this->draft($info->draft()); - - if ($draftObject === null) { - throw new ParseException("Unsupported draft-{$info->draft()}", $info); - } - - /** @var object $schema */ - $schema = $info->data(); - - // Check id - if (property_exists($schema, '$id')) { - $id = $info->id(); - if ($id === null || !$id->isAbsolute()) { - throw new ParseException('Schema id must be a valid URI', $info); - } - } - - if ($hasRef = property_exists($schema, '$ref')) { - if ($this->option('allowKeywordsAlongsideRef') || $draftObject->allowKeywordsAlongsideRef()) { - $hasRef = false; - } - } - - $shared = (object) []; - - if ($this->option('allowKeywordValidators')) { - $keywordValidator = $this->parseKeywordValidators($info, $draftObject->keywordValidators(), $shared); - } else { - $keywordValidator = null; - } - - return $this->parseSchemaKeywords($info, $keywordValidator, $draftObject->keywords(), $shared, $hasRef); - } - - /** - * @param SchemaInfo $info - * @param KeywordValidatorParser[] $keywordValidators - * @param object $shared - * @return KeywordValidator|null - */ - protected function parseKeywordValidators($info, $keywordValidators, $shared) - { - $last = null; - - while ($keywordValidators) { - /** @var KeywordValidatorParser $keywordValidator */ - $keywordValidator = array_pop($keywordValidators); - if ($keywordValidator && ($keyword = $keywordValidator->parse($info, $this, $shared))) { - $keyword->setNext($last); - $last = $keyword; - unset($keyword); - } - unset($keywordValidator); - } - - return $last; - } - - /** - * @param SchemaInfo $info - * @param KeywordValidator|null $keywordValidator - * @param KeywordParser[] $parsers - * @param object $shared - * @param bool $hasRef - * @return Schema - */ - protected function parseSchemaKeywords($info, $keywordValidator, - $parsers, $shared, $hasRef = false): Schema - { - /** @var Keyword[] $prepend */ - $prepend = []; - /** @var Keyword[] $append */ - $append = []; - /** @var Keyword[] $before */ - $before = []; - /** @var Keyword[] $after */ - $after = []; - /** @var Keyword[][] $types */ - $types = []; - /** @var Keyword[] $ref */ - $ref = []; - - if ($hasRef) { - foreach ($parsers as $parser) { - $kType = $parser->type(); - - if ($kType === KeywordParser::TYPE_APPEND) { - $container = &$append; - } elseif ($kType === KeywordParser::TYPE_AFTER_REF) { - $container = &$ref; - } elseif ($kType === KeywordParser::TYPE_PREPEND) { - $container = &$prepend; - } else { - continue; - } - - if ($keyword = $parser->parse($info, $this, $shared)) { - $container[] = $keyword; - } - - unset($container, $keyword, $kType); - } - } else { - foreach ($parsers as $parser) { - $keyword = $parser->parse($info, $this, $shared); - if ($keyword === null) { - continue; - } - - $kType = $parser->type(); - - switch ($kType) { - case KeywordParser::TYPE_PREPEND: - $prepend[] = $keyword; - break; - case KeywordParser::TYPE_APPEND: - $append[] = $keyword; - break; - case KeywordParser::TYPE_BEFORE: - $before[] = $keyword; - break; - case KeywordParser::TYPE_AFTER: - $after[] = $keyword; - break; - case KeywordParser::TYPE_AFTER_REF: - $ref[] = $keyword; - break; - default: - if (!isset($types[$kType])) { - $types[$kType] = []; - } - $types[$kType][] = $keyword; - break; - - } - } - } - - unset($shared); - - if ($prepend) { - $before = array_merge($prepend, $before); - } - unset($prepend); - - if ($ref) { - $after = array_merge($after, $ref); - } - unset($ref); - - if ($append) { - $after = array_merge($after, $append); - } - unset($append); - - if (empty($before)) { - $before = null; - } - if (empty($after)) { - $after = null; - } - if (empty($types)) { - $types = null; - } - - if (empty($types) && empty($before) && empty($after)) { - return new EmptySchema($info, $keywordValidator); - } - - return new ObjectSchema($info, $keywordValidator, $types, $before, $after); - } +class SchemaParser { + + const DRAFT_REGEX = '~^https?://json-schema\.org/draft(?:/|-)(\d[0-9-]*\d)/schema#?$~i'; + const ANCHOR_REGEX = '/^[a-z][a-z0-9\\-.:_]*/i'; + const DEFAULT_DRAFT = '2020-12'; + + /** @var array */ + const DEFAULT_OPTIONS = array( + 'allowFilters' => true, + 'allowFormats' => true, + 'allowMappers' => true, + 'allowTemplates' => true, + 'allowGlobals' => true, + 'allowDefaults' => true, + 'allowSlots' => true, + 'allowKeywordValidators' => true, + 'allowPragmas' => true, + + 'allowDataKeyword' => true, + 'allowKeywordsAlongsideRef' => false, + 'allowUnevaluated' => true, + 'allowRelativeJsonPointerInRef' => true, + 'allowExclusiveMinMaxAsBool' => true, + + 'keepDependenciesKeyword' => true, + 'keepAdditionalItemsKeyword' => true, + + 'decodeContent' => array( '06', '07' ), + 'defaultDraft' => self::DEFAULT_DRAFT, + + 'varRefKey' => '$ref', + 'varEachKey' => '$each', + 'varDefaultKey' => 'default', + ); + + /** @var array */ + protected $options; + + /** @var Draft[] */ + protected $drafts; + + /** @var array */ + protected $resolvers; + + /** + * @param array $resolvers + * @param array $options + * @param Vocabulary|null $extraVocabulary + */ + public function __construct( + array $resolvers = array(), + array $options = array(), + $extraVocabulary = null + ) { + if ( $options ) { + $this->options = $options + self::DEFAULT_OPTIONS; + } else { + $this->options = self::DEFAULT_OPTIONS; + } + + $this->resolvers = $this->getResolvers( $resolvers ); + + $this->drafts = $this->getDrafts( $extraVocabulary ?? new DefaultVocabulary() ); + } + + /** + * @param Vocabulary|null $extraVocabulary + * @return array + */ + protected function getDrafts( $extraVocabulary ): array { + return array( + '06' => new Draft06( $extraVocabulary ), + '07' => new Draft07( $extraVocabulary ), + '2019-09' => new Draft201909( $extraVocabulary ), + '2020-12' => new Draft202012( $extraVocabulary ), + ); + } + + /** + * @param array $resolvers + * @return array + */ + protected function getResolvers( $resolvers ): array { + if ( ! array_key_exists( 'format', $resolvers ) ) { + $resolvers['format'] = new FormatResolver(); + } + + if ( ! array_key_exists( 'contentEncoding', $resolvers ) ) { + $resolvers['contentEncoding'] = new ContentEncodingResolver(); + } + + if ( ! array_key_exists( 'contentMediaType', $resolvers ) ) { + $resolvers['contentMediaType'] = new ContentMediaTypeResolver(); + } + + if ( ! array_key_exists( '$filters', $resolvers ) ) { + $resolvers['$filters'] = new FilterResolver(); + } + + return $resolvers; + } + + /** + * @param string $name + * @param null $default + * @return mixed|null + */ + public function option( $name, $default = null ) { + return $this->options[ $name ] ?? $default; + } + + /** + * @param string $name + * @param $value + * @return $this + */ + public function setOption( $name, $value ): self { + $this->options[ $name ] = $value; + + return $this; + } + + /** + * @return array + */ + public function getOptions(): array { + return $this->options; + } + + /** + * @param string $name + * @param $resolver + * @return $this + */ + public function setResolver( $name, $resolver ): self { + $this->resolvers[ $name ] = $resolver; + + return $this; + } + + /** + * @return null|FilterResolver + */ + public function getFilterResolver() { + return $this->getResolver( '$filters' ); + } + + /** + * @param null|FilterResolver $resolver + * @return $this + */ + public function setFilterResolver( $resolver ): self { + return $this->setResolver( '$filters', $resolver ); + } + + /** + * @return null|FormatResolver + */ + public function getFormatResolver() { + return $this->getResolver( 'format' ); + } + + /** + * @param FormatResolver|null $resolver + * @return $this + */ + public function setFormatResolver( $resolver ): self { + return $this->setResolver( 'format', $resolver ); + } + + /** + * @return null|ContentEncodingResolver + */ + public function getContentEncodingResolver() { + return $this->getResolver( 'contentEncoding' ); + } + + /** + * @param ContentEncodingResolver|null $resolver + * @return $this + */ + public function setContentEncodingResolver( $resolver ): self { + return $this->setResolver( 'contentEncoding', $resolver ); + } + + /** + * @return null|ContentMediaTypeResolver + */ + public function getMediaTypeResolver() { + return $this->getResolver( 'contentMediaType' ); + } + + /** + * @param ContentMediaTypeResolver|null $resolver + * @return $this + */ + public function setMediaTypeResolver( $resolver ): self { + return $this->setResolver( 'contentMediaType', $resolver ); + } + + /** + * @return string + */ + public function defaultDraftVersion(): string { + return $this->option( 'defaultDraft', self::DEFAULT_DRAFT ); + } + + /** + * @param string $draft + * @return $this + */ + public function setDefaultDraftVersion( $draft ): self { + return $this->setOption( 'defaultDraft', $draft ); + } + + /** + * @param string $schema + * @return string|null + */ + public function parseDraftVersion( $schema ) { + if ( ! preg_match( self::DRAFT_REGEX, $schema, $m ) ) { + return null; + } + + return $m[1] ?? null; + } + + /** + * @param object $schema + * @return string|null + */ + public function parseId( $schema ) { + if ( property_exists( $schema, '$id' ) && is_string( $schema->{'$id'} ) ) { + return $schema->{'$id'}; + } + + return null; + } + + /** + * @param object $schema + * @param string $draft + * @return string|null + */ + public function parseAnchor( $schema, $draft ) { + if ( ! property_exists( $schema, '$anchor' ) || + ! isset( $this->drafts[ $draft ] ) || + ! $this->drafts[ $draft ]->supportsAnchorId() ) { + return null; + } + + $anchor = $schema->{'$anchor'}; + + if ( ! is_string( $anchor ) || ! preg_match( self::ANCHOR_REGEX, $anchor ) ) { + return null; + } + + return $anchor; + } + + /** + * @param object $schema + * @return string|null + */ + public function parseSchemaDraft( $schema ) { + if ( ! property_exists( $schema, '$schema' ) || ! is_string( $schema->{'$schema'} ) ) { + return null; + } + + return $this->parseDraftVersion( $schema->{'$schema'} ); + } + + /** + * @param object $schema + * @param Uri $id + * @param callable $handle_id + * @param callable $handle_object + * @param string|null $draft + * @return Schema|null + */ + public function parseRootSchema( + $schema, + $id, + $handle_id, + $handle_object, + $draft = null + ) { + $existent = false; + if ( property_exists( $schema, '$id' ) ) { + $existent = true; + $id = Uri::parse( $schema->{'$id'}, true ); + } + + if ( $id instanceof Uri ) { + if ( $id->fragment() === null ) { + $id = Uri::merge( $id, null, true ); + } + } else { + throw new ParseException( 'Root schema id must be an URI', new SchemaInfo( $schema, $id ) ); + } + + if ( ! $id->isAbsolute() ) { + throw new ParseException( 'Root schema id must be an absolute URI', new SchemaInfo( $schema, $id ) ); + } + + if ( $id->fragment() !== '' ) { + throw new ParseException( 'Root schema id must have an empty fragment or none', new SchemaInfo( $schema, $id ) ); + } + + // Check if id exists + if ( $resolved = $handle_id( $id ) ) { + return $resolved; + } + + if ( property_exists( $schema, '$schema' ) ) { + if ( ! is_string( $schema->{'$schema'} ) ) { + throw new ParseException( 'Schema draft must be a string', new SchemaInfo( $schema, $id ) ); + } + $draft = $this->parseDraftVersion( $schema->{'$schema'} ); + } + + if ( $draft === null ) { + $draft = $this->defaultDraftVersion(); + } + + if ( ! $existent ) { + $schema->{'$id'} = (string) $id; + } + + $resolved = $handle_object( $schema, $id, $draft ); + + if ( ! $existent ) { + unset( $schema->{'$id'} ); + } + + return $resolved; + } + + /** + * @param SchemaInfo $info + * @return Schema + */ + public function parseSchema( $info ): Schema { + if ( $info->isBoolean() ) { + return new BooleanSchema( $info ); + } + + try { + return $this->parseSchemaObject( $info ); + } catch ( SchemaException $exception ) { + return new ExceptionSchema( $info, $exception ); + } + } + + /** + * @param string $version + * @return Draft|null + */ + public function draft( $version ) { + return $this->drafts[ $version ] ?? null; + } + + /** + * @param Draft $draft + * @return $this + */ + public function addDraft( $draft ): self { + $this->drafts[ $draft->version() ] = $draft; + + return $this; + } + + /** + * @return string[] + */ + public function supportedDrafts(): array { + return array_keys( $this->drafts ); + } + + /** + * @param array $options + * @return $this + */ + protected function setOptions( $options ): self { + $this->options = $options + $this->options; + + return $this; + } + + /** + * @param string $name + * @return mixed|null + */ + protected function getResolver( $name ) { + $resolver = $this->resolvers[ $name ] ?? null; + + if ( ! is_object( $resolver ) ) { + return null; + } + + return $resolver; + } + + /** + * @param SchemaInfo $info + * @return Schema + */ + protected function parseSchemaObject( $info ): Schema { + $draftObject = $this->draft( $info->draft() ); + + if ( $draftObject === null ) { + throw new ParseException( "Unsupported draft-{$info->draft()}", $info ); + } + + /** @var object $schema */ + $schema = $info->data(); + + // Check id + if ( property_exists( $schema, '$id' ) ) { + $id = $info->id(); + if ( $id === null || ! $id->isAbsolute() ) { + throw new ParseException( 'Schema id must be a valid URI', $info ); + } + } + + if ( $hasRef = property_exists( $schema, '$ref' ) ) { + if ( $this->option( 'allowKeywordsAlongsideRef' ) || $draftObject->allowKeywordsAlongsideRef() ) { + $hasRef = false; + } + } + + $shared = (object) array(); + + if ( $this->option( 'allowKeywordValidators' ) ) { + $keywordValidator = $this->parseKeywordValidators( $info, $draftObject->keywordValidators(), $shared ); + } else { + $keywordValidator = null; + } + + return $this->parseSchemaKeywords( $info, $keywordValidator, $draftObject->keywords(), $shared, $hasRef ); + } + + /** + * @param SchemaInfo $info + * @param KeywordValidatorParser[] $keywordValidators + * @param object $shared + * @return KeywordValidator|null + */ + protected function parseKeywordValidators( $info, $keywordValidators, $shared ) { + $last = null; + + while ( $keywordValidators ) { + /** @var KeywordValidatorParser $keywordValidator */ + $keywordValidator = array_pop( $keywordValidators ); + if ( $keywordValidator && ( $keyword = $keywordValidator->parse( $info, $this, $shared ) ) ) { + $keyword->setNext( $last ); + $last = $keyword; + unset( $keyword ); + } + unset( $keywordValidator ); + } + + return $last; + } + + /** + * @param SchemaInfo $info + * @param KeywordValidator|null $keywordValidator + * @param KeywordParser[] $parsers + * @param object $shared + * @param bool $hasRef + * @return Schema + */ + protected function parseSchemaKeywords( + $info, + $keywordValidator, + $parsers, + $shared, + $hasRef = false + ): Schema { + /** @var Keyword[] $prepend */ + $prepend = array(); + /** @var Keyword[] $append */ + $append = array(); + /** @var Keyword[] $before */ + $before = array(); + /** @var Keyword[] $after */ + $after = array(); + /** @var Keyword[][] $types */ + $types = array(); + /** @var Keyword[] $ref */ + $ref = array(); + + if ( $hasRef ) { + foreach ( $parsers as $parser ) { + $kType = $parser->type(); + + if ( $kType === KeywordParser::TYPE_APPEND ) { + $container = &$append; + } elseif ( $kType === KeywordParser::TYPE_AFTER_REF ) { + $container = &$ref; + } elseif ( $kType === KeywordParser::TYPE_PREPEND ) { + $container = &$prepend; + } else { + continue; + } + + if ( $keyword = $parser->parse( $info, $this, $shared ) ) { + $container[] = $keyword; + } + + unset( $container, $keyword, $kType ); + } + } else { + foreach ( $parsers as $parser ) { + $keyword = $parser->parse( $info, $this, $shared ); + if ( $keyword === null ) { + continue; + } + + $kType = $parser->type(); + + switch ( $kType ) { + case KeywordParser::TYPE_PREPEND: + $prepend[] = $keyword; + break; + case KeywordParser::TYPE_APPEND: + $append[] = $keyword; + break; + case KeywordParser::TYPE_BEFORE: + $before[] = $keyword; + break; + case KeywordParser::TYPE_AFTER: + $after[] = $keyword; + break; + case KeywordParser::TYPE_AFTER_REF: + $ref[] = $keyword; + break; + default: + if ( ! isset( $types[ $kType ] ) ) { + $types[ $kType ] = array(); + } + $types[ $kType ][] = $keyword; + break; + + } + } + } + + unset( $shared ); + + if ( $prepend ) { + $before = array_merge( $prepend, $before ); + } + unset( $prepend ); + + if ( $ref ) { + $after = array_merge( $after, $ref ); + } + unset( $ref ); + + if ( $append ) { + $after = array_merge( $after, $append ); + } + unset( $append ); + + if ( empty( $before ) ) { + $before = null; + } + if ( empty( $after ) ) { + $after = null; + } + if ( empty( $types ) ) { + $types = null; + } + + if ( empty( $types ) && empty( $before ) && empty( $after ) ) { + return new EmptySchema( $info, $keywordValidator ); + } + + return new ObjectSchema( $info, $keywordValidator, $types, $before, $after ); + } } diff --git a/src/opis/json-schema/src/Parsers/VariablesTrait.php b/src/opis/json-schema/src/Parsers/VariablesTrait.php index 611127ce..edbddb81 100644 --- a/src/opis/json-schema/src/Parsers/VariablesTrait.php +++ b/src/opis/json-schema/src/Parsers/VariablesTrait.php @@ -1,5 +1,6 @@ option('varRefKey', '$ref'), - $parser->option('varEachKey', '$each'), - $parser->option('varDefaultKey', 'default') - ); - } -} \ No newline at end of file +trait VariablesTrait { + + /** + * @param SchemaParser $parser + * @param array|object $vars + * @param bool $lazy + * @return Variables + */ + protected function createVariables( $parser, $vars, $lazy = true ): Variables { + return new VariablesContainer( + $vars, + $lazy, + $parser->option( 'varRefKey', '$ref' ), + $parser->option( 'varEachKey', '$each' ), + $parser->option( 'varDefaultKey', 'default' ) + ); + } +} diff --git a/src/opis/json-schema/src/Parsers/Vocabulary.php b/src/opis/json-schema/src/Parsers/Vocabulary.php index 9d402270..bb504579 100644 --- a/src/opis/json-schema/src/Parsers/Vocabulary.php +++ b/src/opis/json-schema/src/Parsers/Vocabulary.php @@ -1,5 +1,6 @@ keywords = $keywords; - $this->keywordValidators = $keywordValidators; - $this->pragmas = $pragmas; - } + /** @var PragmaParser[] */ + protected $pragmas; - /** - * @return KeywordParser[] - */ - public function keywords(): array - { - return $this->keywords; - } + /** + * @param KeywordParser[] $keywords + * @param KeywordValidatorParser[] $keywordValidators + * @param PragmaParser[] $pragmas + */ + public function __construct( array $keywords = array(), array $keywordValidators = array(), array $pragmas = array() ) { + $this->keywords = $keywords; + $this->keywordValidators = $keywordValidators; + $this->pragmas = $pragmas; + } - /** - * @return KeywordValidatorParser[] - */ - public function keywordValidators(): array - { - return $this->keywordValidators; - } + /** + * @return KeywordParser[] + */ + public function keywords(): array { + return $this->keywords; + } - /** - * @return PragmaParser[] - */ - public function pragmas(): array - { - return $this->pragmas; - } + /** + * @return KeywordValidatorParser[] + */ + public function keywordValidators(): array { + return $this->keywordValidators; + } - /** - * @param KeywordParser $keyword - * @return Vocabulary - */ - public function appendKeyword($keyword): self - { - $this->keywords[] = $keyword; - return $this; - } + /** + * @return PragmaParser[] + */ + public function pragmas(): array { + return $this->pragmas; + } - /** - * @param KeywordParser $keyword - * @return Vocabulary - */ - public function prependKeyword($keyword): self - { - array_unshift($this->keywords, $keyword); - return $this; - } + /** + * @param KeywordParser $keyword + * @return Vocabulary + */ + public function appendKeyword( $keyword ): self { + $this->keywords[] = $keyword; + return $this; + } - /** - * @param KeywordValidatorParser $keywordValidatorParser - * @return Vocabulary - */ - public function appendKeywordValidator($keywordValidatorParser): self - { - $this->keywordValidators[] = $keywordValidatorParser; - return $this; - } + /** + * @param KeywordParser $keyword + * @return Vocabulary + */ + public function prependKeyword( $keyword ): self { + array_unshift( $this->keywords, $keyword ); + return $this; + } - /** - * @param KeywordValidatorParser $keywordValidator - * @return Vocabulary - */ - public function prependKeywordValidator($keywordValidator): self - { - array_unshift($this->keywordValidators, $keywordValidator); - return $this; - } + /** + * @param KeywordValidatorParser $keywordValidatorParser + * @return Vocabulary + */ + public function appendKeywordValidator( $keywordValidatorParser ): self { + $this->keywordValidators[] = $keywordValidatorParser; + return $this; + } - /** - * @param PragmaParser $pragma - * @return Vocabulary - */ - public function appendPragma($pragma): self - { - $this->pragmas[] = $pragma; - return $this; - } + /** + * @param KeywordValidatorParser $keywordValidator + * @return Vocabulary + */ + public function prependKeywordValidator( $keywordValidator ): self { + array_unshift( $this->keywordValidators, $keywordValidator ); + return $this; + } - /** - * @param PragmaParser $pragma - * @return Vocabulary - */ - public function prependPragma($pragma): self - { - array_unshift($this->pragmas, $pragma); - return $this; - } -} \ No newline at end of file + /** + * @param PragmaParser $pragma + * @return Vocabulary + */ + public function appendPragma( $pragma ): self { + $this->pragmas[] = $pragma; + return $this; + } + + /** + * @param PragmaParser $pragma + * @return Vocabulary + */ + public function prependPragma( $pragma ): self { + array_unshift( $this->pragmas, $pragma ); + return $this; + } +} diff --git a/src/opis/json-schema/src/Pragma.php b/src/opis/json-schema/src/Pragma.php index 22ebbb72..f2ff2897 100644 --- a/src/opis/json-schema/src/Pragma.php +++ b/src/opis/json-schema/src/Pragma.php @@ -1,5 +1,6 @@ cast = $cast; - $this->func = $this->getCastFunction($cast); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function enter($context) - { - $currentType = $context->currentDataType(); - if ($currentType !== null && Helper::jsonTypeMatches($currentType, $this->cast)) { - // Cast not needed - return $this; - } - unset($currentType); - - $currentData = $context->currentData(); - - $context->setCurrentData(($this->func)($currentData)); - - return $currentData; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function leave($context, $data) - { - if ($data !== $this) { - $context->setCurrentData($data); - } - } - - /** - * @param string $type - * @return callable - */ - protected function getCastFunction($type): callable - { - $f = 'toNull'; - - switch ($type) { - case 'integer': - $f = 'toInteger'; - break; - case 'number': - $f = 'toNumber'; - break; - case 'string': - $f = 'toString'; - break; - case 'array': - $f = 'toArray'; - break; - case 'object': - $f = 'toObject'; - break; - case 'boolean': - $f = 'toBoolean'; - break; - } - - return [$this, $f]; - } - - /** - * @param $value - * @return int|null - */ - public function toInteger($value) - { - if ($value === null) { - return 0; - } - - return is_scalar($value) ? intval($value) : null; - } - - /** - * @param $value - * @return float|null - */ - public function toNumber($value) - { - if ($value === null) { - return 0.0; - } - - return is_scalar($value) ? floatval($value) : null; - } - - /** - * @param $value - * @return string|null - */ - public function toString($value) - { - if ($value === null) { - return ''; - } - - if (is_scalar($value)) { - return (string) $value; - } - - return null; - } - - /** - * @param $value - * @return array|null - */ - public function toArray($value) - { - if ($value === null) { - return []; - } - - if (is_scalar($value)) { - return [$value]; - } - - if (is_array($value)) { - return array_values($value); - } - - if (is_object($value)) { - return array_values(get_object_vars($value)); - } - - return null; - } - - /** - * @param $value - * @return object|null - */ - public function toObject($value) - { - if (is_object($value) || is_array($value)) { - return (object) $value; - } - - return null; - } - - /** - * @param $value - * @return bool - */ - public function toBoolean($value): bool - { - if ($value === null) { - return false; - } - if (is_string($value)) { - return !($value === ''); - } - if (is_object($value)) { - return count(get_object_vars($value)) > 0; - } - return boolval($value); - } +class CastPragma implements Pragma { + + + /** + * @var string + */ + protected $cast; + + /** @var callable */ + protected $func; + + /** + * @param string $cast + */ + public function __construct( string $cast ) { + $this->cast = $cast; + $this->func = $this->getCastFunction( $cast ); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function enter( $context ) { + $currentType = $context->currentDataType(); + if ( $currentType !== null && Helper::jsonTypeMatches( $currentType, $this->cast ) ) { + // Cast not needed + return $this; + } + unset( $currentType ); + + $currentData = $context->currentData(); + + $context->setCurrentData( ( $this->func )( $currentData ) ); + + return $currentData; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function leave( $context, $data ) { + if ( $data !== $this ) { + $context->setCurrentData( $data ); + } + } + + /** + * @param string $type + * @return callable + */ + protected function getCastFunction( $type ): callable { + $f = 'toNull'; + + switch ( $type ) { + case 'integer': + $f = 'toInteger'; + break; + case 'number': + $f = 'toNumber'; + break; + case 'string': + $f = 'toString'; + break; + case 'array': + $f = 'toArray'; + break; + case 'object': + $f = 'toObject'; + break; + case 'boolean': + $f = 'toBoolean'; + break; + } + + return array( $this, $f ); + } + + /** + * @param $value + * @return int|null + */ + public function toInteger( $value ) { + if ( $value === null ) { + return 0; + } + + return is_scalar( $value ) ? intval( $value ) : null; + } + + /** + * @param $value + * @return float|null + */ + public function toNumber( $value ) { + if ( $value === null ) { + return 0.0; + } + + return is_scalar( $value ) ? floatval( $value ) : null; + } + + /** + * @param $value + * @return string|null + */ + public function toString( $value ) { + if ( $value === null ) { + return ''; + } + + if ( is_scalar( $value ) ) { + return (string) $value; + } + + return null; + } + + /** + * @param $value + * @return array|null + */ + public function toArray( $value ) { + if ( $value === null ) { + return array(); + } + + if ( is_scalar( $value ) ) { + return array( $value ); + } + + if ( is_array( $value ) ) { + return array_values( $value ); + } + + if ( is_object( $value ) ) { + return array_values( get_object_vars( $value ) ); + } + + return null; + } + + /** + * @param $value + * @return object|null + */ + public function toObject( $value ) { + if ( is_object( $value ) || is_array( $value ) ) { + return (object) $value; + } + + return null; + } + + /** + * @param $value + * @return bool + */ + public function toBoolean( $value ): bool { + if ( $value === null ) { + return false; + } + if ( is_string( $value ) ) { + return ! ( $value === '' ); + } + if ( is_object( $value ) ) { + return count( get_object_vars( $value ) ) > 0; + } + return boolval( $value ); + } } diff --git a/src/opis/json-schema/src/Pragmas/GlobalsPragma.php b/src/opis/json-schema/src/Pragmas/GlobalsPragma.php index b9b7473c..9b10ab34 100644 --- a/src/opis/json-schema/src/Pragmas/GlobalsPragma.php +++ b/src/opis/json-schema/src/Pragmas/GlobalsPragma.php @@ -1,5 +1,6 @@ globals = $globals; - } + /** + * @var \Opis\JsonSchema\Variables + */ + protected $globals; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function enter($context) - { - $resolved = (array) $this->globals->resolve($context->rootData(), $context->currentDataPath()); - if (!$resolved) { - return null; - } + /** + * @param Variables $globals + */ + public function __construct( Variables $globals ) { + $this->globals = $globals; + } - $data = $context->globals(); - $context->setGlobals($resolved, false); - return $data; - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function enter( $context ) { + $resolved = (array) $this->globals->resolve( $context->rootData(), $context->currentDataPath() ); + if ( ! $resolved ) { + return null; + } - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function leave($context, $data) - { - if ($data === null) { - return; - } - $context->setGlobals($data, true); - } + $data = $context->globals(); + $context->setGlobals( $resolved, false ); + return $data; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function leave( $context, $data ) { + if ( $data === null ) { + return; + } + $context->setGlobals( $data, true ); + } } diff --git a/src/opis/json-schema/src/Pragmas/MaxErrorsPragma.php b/src/opis/json-schema/src/Pragmas/MaxErrorsPragma.php index d669929e..65ef1ecb 100644 --- a/src/opis/json-schema/src/Pragmas/MaxErrorsPragma.php +++ b/src/opis/json-schema/src/Pragmas/MaxErrorsPragma.php @@ -1,5 +1,6 @@ maxErrors = $maxErrors; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function enter($context) - { - $data = $context->maxErrors(); - $context->setMaxErrors($this->maxErrors); - return $data; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function leave($context, $data) - { - if ($data === null) { - return; - } - $context->setMaxErrors($data); - } +class MaxErrorsPragma implements Pragma { + + + /** + * @var int + */ + protected $maxErrors; + + /** + * @param int $maxErrors + */ + public function __construct( int $maxErrors ) { + $this->maxErrors = $maxErrors; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function enter( $context ) { + $data = $context->maxErrors(); + $context->setMaxErrors( $this->maxErrors ); + return $data; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function leave( $context, $data ) { + if ( $data === null ) { + return; + } + $context->setMaxErrors( $data ); + } } diff --git a/src/opis/json-schema/src/Pragmas/SlotsPragma.php b/src/opis/json-schema/src/Pragmas/SlotsPragma.php index 60d380fd..3da6578f 100644 --- a/src/opis/json-schema/src/Pragmas/SlotsPragma.php +++ b/src/opis/json-schema/src/Pragmas/SlotsPragma.php @@ -1,5 +1,6 @@ slots = $slots; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function enter($context) - { - $data = $context->slots(); - $context->setSlots($data ? $this->slots + $data : $this->slots); - return $data; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function leave($context, $data) - { - $context->setSlots($data); - } +class SlotsPragma implements Pragma { + + + /** + * @var mixed[] + */ + protected $slots; + + /** + * @param array $slots + */ + public function __construct( array $slots ) { + $this->slots = $slots; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function enter( $context ) { + $data = $context->slots(); + $context->setSlots( $data ? $this->slots + $data : $this->slots ); + return $data; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function leave( $context, $data ) { + $context->setSlots( $data ); + } } diff --git a/src/opis/json-schema/src/Resolvers/ContentEncodingResolver.php b/src/opis/json-schema/src/Resolvers/ContentEncodingResolver.php index 40d0f387..69e9363c 100644 --- a/src/opis/json-schema/src/Resolvers/ContentEncodingResolver.php +++ b/src/opis/json-schema/src/Resolvers/ContentEncodingResolver.php @@ -1,5 +1,6 @@ self::class . '::DecodeBinary', - 'base64' => self::class . '::DecodeBase64', - 'quoted-printable' => self::class . '::DecodeQuotedPrintable', - ]; - - $this->list = $list; - $this->defaultEncoding = $defaultEncoding; - } - - /** - * @param string $name - * @return callable|ContentEncoding|string|null - */ - public function resolve($name) - { - return $this->list[$name] ?? $this->defaultEncoding; - } - - /** - * @param string $name - * @param ContentEncoding $encoding - * @return ContentEncodingResolver - */ - public function register($name, $encoding): self - { - $this->list[$name] = $encoding; - - return $this; - } - - /** - * @param string $name - * @param callable $encoding - * @return ContentEncodingResolver - */ - public function registerCallable($name, $encoding): self - { - $this->list[$name] = $encoding; - - return $this; - } - - /** - * @param string $name - * @return bool - */ - public function unregister($name): bool - { - if (isset($this->list[$name])) { - unset($this->list[$name]); - - return true; - } - - return false; - } - - /** - * @param callable|ContentEncoding|null $handler - * @return $this - */ - public function setDefaultHandler($handler): self - { - $this->defaultEncoding = $handler; - return $this; - } - - public function __serialize(): array - { - return [ - 'list' => $this->list, - 'defaultEncoding' => $this->defaultEncoding, - ]; - } - - public function __unserialize(array $data) - { - $this->list = $data['list']; - $this->defaultEncoding = $data['defaultEncoding'] ?? null; - } - - /** - * @param string $value - */ - public static function DecodeBinary($value) - { - return $value; - } - - /** - * @param string $value - */ - public static function DecodeBase64($value) - { - $value = base64_decode($value, true); - - return is_string($value) ? $value : null; - } - - /** - * @param string $value - */ - public static function DecodeQuotedPrintable($value) - { - return quoted_printable_decode($value); - } +class ContentEncodingResolver { + + /** @var callable[]|ContentEncoding[] */ + protected $list; + + /** @var callable|ContentEncoding|null */ + protected $defaultEncoding = null; + + /** + * @param callable[]|ContentEncoding[] $list + * @param callable|ContentEncoding|null $defaultEncoding + */ + public function __construct( array $list = array(), $defaultEncoding = null ) { + $list += array( + 'binary' => self::class . '::DecodeBinary', + 'base64' => self::class . '::DecodeBase64', + 'quoted-printable' => self::class . '::DecodeQuotedPrintable', + ); + + $this->list = $list; + $this->defaultEncoding = $defaultEncoding; + } + + /** + * @param string $name + * @return callable|ContentEncoding|string|null + */ + public function resolve( $name ) { + return $this->list[ $name ] ?? $this->defaultEncoding; + } + + /** + * @param string $name + * @param ContentEncoding $encoding + * @return ContentEncodingResolver + */ + public function register( $name, $encoding ): self { + $this->list[ $name ] = $encoding; + + return $this; + } + + /** + * @param string $name + * @param callable $encoding + * @return ContentEncodingResolver + */ + public function registerCallable( $name, $encoding ): self { + $this->list[ $name ] = $encoding; + + return $this; + } + + /** + * @param string $name + * @return bool + */ + public function unregister( $name ): bool { + if ( isset( $this->list[ $name ] ) ) { + unset( $this->list[ $name ] ); + + return true; + } + + return false; + } + + /** + * @param callable|ContentEncoding|null $handler + * @return $this + */ + public function setDefaultHandler( $handler ): self { + $this->defaultEncoding = $handler; + return $this; + } + + public function __serialize(): array { + return array( + 'list' => $this->list, + 'defaultEncoding' => $this->defaultEncoding, + ); + } + + public function __unserialize( array $data ) { + $this->list = $data['list']; + $this->defaultEncoding = $data['defaultEncoding'] ?? null; + } + + /** + * @param string $value + */ + public static function DecodeBinary( $value ) { + return $value; + } + + /** + * @param string $value + */ + public static function DecodeBase64( $value ) { + $value = base64_decode( $value, true ); + + return is_string( $value ) ? $value : null; + } + + /** + * @param string $value + */ + public static function DecodeQuotedPrintable( $value ) { + return quoted_printable_decode( $value ); + } } diff --git a/src/opis/json-schema/src/Resolvers/ContentMediaTypeResolver.php b/src/opis/json-schema/src/Resolvers/ContentMediaTypeResolver.php index 7b86c116..f12daf59 100644 --- a/src/opis/json-schema/src/Resolvers/ContentMediaTypeResolver.php +++ b/src/opis/json-schema/src/Resolvers/ContentMediaTypeResolver.php @@ -1,5 +1,6 @@ self::class . '::IsJsonEncoded', - ]; - - $this->media = $media; - $this->defaultMedia = $defaultMedia ?? self::class . '::IsEncodedAsType'; - } - - /** - * @param string $name - * @return callable|ContentMediaType|string|null - */ - public function resolve($name) - { - return $this->media[$name] ?? $this->defaultMedia; - } - - /** - * @param string $name - * @param ContentMediaType $media - * @return ContentMediaTypeResolver - */ - public function register($name, $media): self - { - $this->media[$name] = $media; - - return $this; - } - - /** - * @param string $name - * @param callable $media - * @return ContentMediaTypeResolver - */ - public function registerCallable($name, $media): self - { - $this->media[$name] = $media; - - return $this; - } - - /** - * @param string $name - * @return bool - */ - public function unregister($name): bool - { - if (isset($this->media[$name])) { - unset($this->media[$name]); - - return true; - } - - return false; - } - - /** - * @param callable|ContentMediaType|null $handler - * @return ContentMediaTypeResolver - */ - public function setDefaultHandler($handler): self - { - $this->defaultMedia = $handler; - - return $this; - } - - public function __serialize(): array - { - return [ - 'media' => $this->media, - 'defaultMedia' => $this->defaultMedia, - ]; - } - - public function __unserialize(array $data) - { - $this->media = $data['media']; - $this->defaultMedia = $data['defaultMedia']; - } - - /** - * @param string $value - * @param string $type - */ - public static function IsJsonEncoded($value, - /** @noinspection PhpUnusedParameterInspection */ $type): bool - { - json_decode($value); - - return json_last_error() === JSON_ERROR_NONE; - } - - /** - * @param string $value - * @param string $type - */ - public static function IsEncodedAsType($value, $type): bool - { - /** @var finfo|null|bool $finfo */ - static $finfo = false; - - if ($finfo === false) { - if (!class_exists(finfo::class)) { - $finfo = null; - return false; - } - $finfo = new finfo(FILEINFO_MIME_TYPE); - } elseif (!$finfo) { - return false; - } - - $r = $finfo->buffer($value); - - return $r == $type || $r == 'application/x-empty'; - } +class ContentMediaTypeResolver { + + /** @var callable[]|ContentMediaType[] */ + protected $media; + + /** @var callable|null|ContentMediaType */ + protected $defaultMedia = null; + + /** + * @param callable[]|ContentMediaType[] $media + * @param callable|ContentMediaType|null $defaultMedia + */ + public function __construct( array $media = array(), $defaultMedia = null ) { + $media += array( + 'application/json' => self::class . '::IsJsonEncoded', + ); + + $this->media = $media; + $this->defaultMedia = $defaultMedia ?? self::class . '::IsEncodedAsType'; + } + + /** + * @param string $name + * @return callable|ContentMediaType|string|null + */ + public function resolve( $name ) { + return $this->media[ $name ] ?? $this->defaultMedia; + } + + /** + * @param string $name + * @param ContentMediaType $media + * @return ContentMediaTypeResolver + */ + public function register( $name, $media ): self { + $this->media[ $name ] = $media; + + return $this; + } + + /** + * @param string $name + * @param callable $media + * @return ContentMediaTypeResolver + */ + public function registerCallable( $name, $media ): self { + $this->media[ $name ] = $media; + + return $this; + } + + /** + * @param string $name + * @return bool + */ + public function unregister( $name ): bool { + if ( isset( $this->media[ $name ] ) ) { + unset( $this->media[ $name ] ); + + return true; + } + + return false; + } + + /** + * @param callable|ContentMediaType|null $handler + * @return ContentMediaTypeResolver + */ + public function setDefaultHandler( $handler ): self { + $this->defaultMedia = $handler; + + return $this; + } + + public function __serialize(): array { + return array( + 'media' => $this->media, + 'defaultMedia' => $this->defaultMedia, + ); + } + + public function __unserialize( array $data ) { + $this->media = $data['media']; + $this->defaultMedia = $data['defaultMedia']; + } + + /** + * @param string $value + * @param string $type + */ + public static function IsJsonEncoded( + $value, + /** @noinspection PhpUnusedParameterInspection */ $type + ): bool { + json_decode( $value ); + + return json_last_error() === JSON_ERROR_NONE; + } + + /** + * @param string $value + * @param string $type + */ + public static function IsEncodedAsType( $value, $type ): bool { + /** @var finfo|null|bool $finfo */ + static $finfo = false; + + if ( $finfo === false ) { + if ( ! class_exists( finfo::class ) ) { + $finfo = null; + return false; + } + $finfo = new finfo( FILEINFO_MIME_TYPE ); + } elseif ( ! $finfo ) { + return false; + } + + $r = $finfo->buffer( $value ); + + return $r == $type || $r == 'application/x-empty'; + } } diff --git a/src/opis/json-schema/src/Resolvers/FilterResolver.php b/src/opis/json-schema/src/Resolvers/FilterResolver.php index 4a670dc7..accdfb87 100644 --- a/src/opis/json-schema/src/Resolvers/FilterResolver.php +++ b/src/opis/json-schema/src/Resolvers/FilterResolver.php @@ -1,5 +1,6 @@ separator = $ns_separator; - $this->defaultNS = $default_ns; - - $this->registerDefaultFilters(); - } - - /** - * You can override this to add/remove default filters - */ - protected function registerDefaultFilters() - { - $this->registerMultipleTypes("schema-exists", new SchemaExistsFilter()); - $this->registerMultipleTypes("data-exists", new DataExistsFilter()); - $this->registerMultipleTypes("global-exists", new GlobalVarExistsFilter()); - $this->registerMultipleTypes("slot-exists", new SlotExistsFilter()); - $this->registerMultipleTypes("filter-exists", new FilterExistsFilter()); - $this->registerMultipleTypes("format-exists", new FormatExistsFilter()); - - $cls = DateTimeFilters::class . "::"; - $this->registerCallable("string", "min-date", $cls . "MinDate"); - $this->registerCallable("string", "max-date", $cls . "MaxDate"); - $this->registerCallable("string", "not-date", $cls . "NotDate"); - $this->registerCallable("string", "min-time", $cls . "MinTime"); - $this->registerCallable("string", "max-time", $cls . "MaxTime"); - $this->registerCallable("string", "min-datetime", $cls . "MinDateTime"); - $this->registerCallable("string", "max-datetime", $cls . "MaxDateTime"); - - $cls = CommonFilters::class . "::"; - $this->registerCallable("string", "regex", $cls . "Regex"); - $this->registerMultipleTypes("equals", $cls . "Equals"); - } - - - /** - * @param string $name - * @param string $type - * @return Filter|callable|null - */ - public function resolve($name, $type) - { - list($ns, $name) = $this->parseName($name); - - if (isset($this->filters[$ns][$name])) { - return $this->filters[$ns][$name][$type] ?? null; - } - - if (!isset($this->ns[$ns])) { - return null; - } - - $this->filters[$ns][$name] = $this->ns[$ns]->resolveAll($name); - - return $this->filters[$ns][$name][$type] ?? null; - } - - /** - * @param string $name - * @return Filter[]|callable[]|null - */ - public function resolveAll($name) - { - list($ns, $name) = $this->parseName($name); - - if (isset($this->filters[$ns][$name])) { - return $this->filters[$ns][$name]; - } - - if (!isset($this->ns[$ns])) { - return null; - } - - return $this->filters[$ns][$name] = $this->ns[$ns]->resolveAll($name); - } - - /** - * @param string $type - * @param string $name - * @param Filter $filter - * @return FilterResolver - */ - public function register($type, $name, $filter): self - { - list($ns, $name) = $this->parseName($name); - - $this->filters[$ns][$name][$type] = $filter; - - return $this; - } - - /** - * @param string $name - * @param string|null $type - * @return bool - */ - public function unregister($name, $type = null): bool - { - list($ns, $name) = $this->parseName($name); - if (!isset($this->filters[$ns][$name])) { - return false; - } - - if ($type === null) { - unset($this->filters[$ns][$name]); - - return true; - } - - if (isset($this->filters[$ns][$name][$type])) { - unset($this->filters[$ns][$name][$type]); - - return true; - } - - return false; - } - - /** - * @param string $name - * @param callable|Filter $filter - * @param array|null $types - * @return FilterResolver - */ - public function registerMultipleTypes($name, $filter, $types = null): self - { - list($ns, $name) = $this->parseName($name); - - $types = $types ?? Helper::JSON_TYPES; - - foreach ($types as $type) { - $this->filters[$ns][$name][$type] = $filter; - } - - return $this; - } - - /** - * @param string $type - * @param string $name - * @param callable $filter - * @return FilterResolver - */ - public function registerCallable($type, $name, $filter): self - { - list($ns, $name) = $this->parseName($name); - - $this->filters[$ns][$name][$type] = $filter; - - return $this; - } - - /** - * @param string $ns - * @param FilterResolver $resolver - * @return FilterResolver - */ - public function registerNS($ns, $resolver): self - { - $this->ns[$ns] = $resolver; - - return $this; - } - - /** - * @param string $ns - * @return bool - */ - public function unregisterNS($ns): bool - { - if (isset($this->filters[$ns])) { - unset($this->filters[$ns]); - unset($this->ns[$ns]); - - return true; - } - - if (isset($this->ns[$ns])) { - unset($this->ns[$ns]); - - return true; - } - - return false; - } - - public function __serialize(): array - { - return [ - 'separator' => $this->separator, - 'defaultNS' => $this->defaultNS, - 'ns' => $this->ns, - 'filters' => $this->filters, - ]; - } - - public function __unserialize(array $data) - { - $this->separator = $data['separator']; - $this->defaultNS = $data['defaultNS']; - $this->ns = $data['ns']; - $this->filters = $data['filters']; - } - - /** - * @param string $name - * @return array - */ - protected function parseName($name): array - { - $name = strtolower($name); - - if (strpos($name, $this->separator) === false) { - return [$this->defaultNS, $name]; - } - - return explode($this->separator, $name, 2); - } + DataExistsFilter, + DateTimeFilters, + FilterExistsFilter, + FormatExistsFilter, + SchemaExistsFilter, + SlotExistsFilter, + GlobalVarExistsFilter}; + +class FilterResolver { + + /** @var Filter[][][] */ + protected $filters = array(); + + /** @var FilterResolver[] */ + protected $ns = array(); + + /** + * @var string + */ + protected $separator; + + /** + * @var string + */ + protected $defaultNS; + + /** + * FilterResolver constructor. + * + * @param string $ns_separator + * @param string $default_ns + */ + public function __construct( string $ns_separator = '::', string $default_ns = 'default' ) { + $this->separator = $ns_separator; + $this->defaultNS = $default_ns; + + $this->registerDefaultFilters(); + } + + /** + * You can override this to add/remove default filters + */ + protected function registerDefaultFilters() { + $this->registerMultipleTypes( 'schema-exists', new SchemaExistsFilter() ); + $this->registerMultipleTypes( 'data-exists', new DataExistsFilter() ); + $this->registerMultipleTypes( 'global-exists', new GlobalVarExistsFilter() ); + $this->registerMultipleTypes( 'slot-exists', new SlotExistsFilter() ); + $this->registerMultipleTypes( 'filter-exists', new FilterExistsFilter() ); + $this->registerMultipleTypes( 'format-exists', new FormatExistsFilter() ); + + $cls = DateTimeFilters::class . '::'; + $this->registerCallable( 'string', 'min-date', $cls . 'MinDate' ); + $this->registerCallable( 'string', 'max-date', $cls . 'MaxDate' ); + $this->registerCallable( 'string', 'not-date', $cls . 'NotDate' ); + $this->registerCallable( 'string', 'min-time', $cls . 'MinTime' ); + $this->registerCallable( 'string', 'max-time', $cls . 'MaxTime' ); + $this->registerCallable( 'string', 'min-datetime', $cls . 'MinDateTime' ); + $this->registerCallable( 'string', 'max-datetime', $cls . 'MaxDateTime' ); + + $cls = CommonFilters::class . '::'; + $this->registerCallable( 'string', 'regex', $cls . 'Regex' ); + $this->registerMultipleTypes( 'equals', $cls . 'Equals' ); + } + + + /** + * @param string $name + * @param string $type + * @return Filter|callable|null + */ + public function resolve( $name, $type ) { + list($ns, $name) = $this->parseName( $name ); + + if ( isset( $this->filters[ $ns ][ $name ] ) ) { + return $this->filters[ $ns ][ $name ][ $type ] ?? null; + } + + if ( ! isset( $this->ns[ $ns ] ) ) { + return null; + } + + $this->filters[ $ns ][ $name ] = $this->ns[ $ns ]->resolveAll( $name ); + + return $this->filters[ $ns ][ $name ][ $type ] ?? null; + } + + /** + * @param string $name + * @return Filter[]|callable[]|null + */ + public function resolveAll( $name ) { + list($ns, $name) = $this->parseName( $name ); + + if ( isset( $this->filters[ $ns ][ $name ] ) ) { + return $this->filters[ $ns ][ $name ]; + } + + if ( ! isset( $this->ns[ $ns ] ) ) { + return null; + } + + return $this->filters[ $ns ][ $name ] = $this->ns[ $ns ]->resolveAll( $name ); + } + + /** + * @param string $type + * @param string $name + * @param Filter $filter + * @return FilterResolver + */ + public function register( $type, $name, $filter ): self { + list($ns, $name) = $this->parseName( $name ); + + $this->filters[ $ns ][ $name ][ $type ] = $filter; + + return $this; + } + + /** + * @param string $name + * @param string|null $type + * @return bool + */ + public function unregister( $name, $type = null ): bool { + list($ns, $name) = $this->parseName( $name ); + if ( ! isset( $this->filters[ $ns ][ $name ] ) ) { + return false; + } + + if ( $type === null ) { + unset( $this->filters[ $ns ][ $name ] ); + + return true; + } + + if ( isset( $this->filters[ $ns ][ $name ][ $type ] ) ) { + unset( $this->filters[ $ns ][ $name ][ $type ] ); + + return true; + } + + return false; + } + + /** + * @param string $name + * @param callable|Filter $filter + * @param array|null $types + * @return FilterResolver + */ + public function registerMultipleTypes( $name, $filter, $types = null ): self { + list($ns, $name) = $this->parseName( $name ); + + $types = $types ?? Helper::JSON_TYPES; + + foreach ( $types as $type ) { + $this->filters[ $ns ][ $name ][ $type ] = $filter; + } + + return $this; + } + + /** + * @param string $type + * @param string $name + * @param callable $filter + * @return FilterResolver + */ + public function registerCallable( $type, $name, $filter ): self { + list($ns, $name) = $this->parseName( $name ); + + $this->filters[ $ns ][ $name ][ $type ] = $filter; + + return $this; + } + + /** + * @param string $ns + * @param FilterResolver $resolver + * @return FilterResolver + */ + public function registerNS( $ns, $resolver ): self { + $this->ns[ $ns ] = $resolver; + + return $this; + } + + /** + * @param string $ns + * @return bool + */ + public function unregisterNS( $ns ): bool { + if ( isset( $this->filters[ $ns ] ) ) { + unset( $this->filters[ $ns ] ); + unset( $this->ns[ $ns ] ); + + return true; + } + + if ( isset( $this->ns[ $ns ] ) ) { + unset( $this->ns[ $ns ] ); + + return true; + } + + return false; + } + + public function __serialize(): array { + return array( + 'separator' => $this->separator, + 'defaultNS' => $this->defaultNS, + 'ns' => $this->ns, + 'filters' => $this->filters, + ); + } + + public function __unserialize( array $data ) { + $this->separator = $data['separator']; + $this->defaultNS = $data['defaultNS']; + $this->ns = $data['ns']; + $this->filters = $data['filters']; + } + + /** + * @param string $name + * @return array + */ + protected function parseName( $name ): array { + $name = strtolower( $name ); + + if ( strpos( $name, $this->separator ) === false ) { + return array( $this->defaultNS, $name ); + } + + return explode( $this->separator, $name, 2 ); + } } diff --git a/src/opis/json-schema/src/Resolvers/FormatResolver.php b/src/opis/json-schema/src/Resolvers/FormatResolver.php index 0a259ceb..e8a79aaa 100644 --- a/src/opis/json-schema/src/Resolvers/FormatResolver.php +++ b/src/opis/json-schema/src/Resolvers/FormatResolver.php @@ -1,5 +1,6 @@ formats = [ - 'string' => [ - 'date' => DateTimeFormats::class . '::date', - 'time' => DateTimeFormats::class . '::time', - 'date-time' => DateTimeFormats::class . '::dateTime', - 'duration' => DateTimeFormats::class . '::duration', - - 'uri' => UriFormats::class . '::uri', - 'uri-reference' => UriFormats::class . '::uriReference', - 'uri-template' => UriFormats::class . '::uriTemplate', - - 'regex' => Helper::class . '::isValidPattern', - 'ipv4' => MiscFormats::class . '::ipv4', - 'ipv6' => MiscFormats::class . '::ipv6', - 'uuid' => MiscFormats::class . '::uuid', - - 'email' => MiscFormats::class . '::email', - 'hostname' => Uri::class . '::isValidHost', - - 'json-pointer' => JsonPointer::class . '::isAbsolutePointer', - 'relative-json-pointer' => JsonPointer::class . '::isRelativePointer', - - 'idn-hostname' => IriFormats::class . '::idnHostname', - 'idn-email' => IriFormats::class . '::idnEmail', - 'iri' => IriFormats::class . '::iri', - 'iri-reference' => IriFormats::class . '::iriReference', - ], - ]; - } - - /** - * @param string $name - * @param string $type - * @return callable|Format|null - */ - public function resolve($name, $type) - { - return $this->formats[$type][$name] ?? null; - } - - /** - * @param string $name - * @return Format[]|callable[]|null - */ - public function resolveAll($name) - { - $list = null; - - foreach ($this->formats as $type => $items) { - if (isset($items[$name])) { - $list[$type] = $items[$name]; - } - } - - return $list; - } - - /** - * @param string $type - * @param string $name - * @param Format $format - * @return FormatResolver - */ - public function register($type, $name, $format): self - { - $this->formats[$type][$name] = $format; - - return $this; - } - - /** - * @param string $type - * @param string $name - * @param callable $format - * @return FormatResolver - */ - public function registerCallable($type, $name, $format): self - { - $this->formats[$type][$name] = $format; - - return $this; - } - - /** - * @param string $type - * @param string $name - * @return bool - */ - public function unregister($type, $name): bool - { - if (isset($this->formats[$type][$name])) { - unset($this->formats[$type][$name]); - - return true; - } - - return false; - } - - public function __serialize(): array - { - return ['formats' => $this->formats]; - } - - public function __unserialize(array $data) - { - $this->formats = $data['formats']; - } +class FormatResolver { + + /** @var Format[][]|callable[][] */ + protected $formats = array(); + + /** + * FormatResolver constructor. + */ + public function __construct() { + $this->formats = array( + 'string' => array( + 'date' => DateTimeFormats::class . '::date', + 'time' => DateTimeFormats::class . '::time', + 'date-time' => DateTimeFormats::class . '::dateTime', + 'duration' => DateTimeFormats::class . '::duration', + + 'uri' => UriFormats::class . '::uri', + 'uri-reference' => UriFormats::class . '::uriReference', + 'uri-template' => UriFormats::class . '::uriTemplate', + + 'regex' => Helper::class . '::isValidPattern', + 'ipv4' => MiscFormats::class . '::ipv4', + 'ipv6' => MiscFormats::class . '::ipv6', + 'uuid' => MiscFormats::class . '::uuid', + + 'email' => MiscFormats::class . '::email', + 'hostname' => Uri::class . '::isValidHost', + + 'json-pointer' => JsonPointer::class . '::isAbsolutePointer', + 'relative-json-pointer' => JsonPointer::class . '::isRelativePointer', + + 'idn-hostname' => IriFormats::class . '::idnHostname', + 'idn-email' => IriFormats::class . '::idnEmail', + 'iri' => IriFormats::class . '::iri', + 'iri-reference' => IriFormats::class . '::iriReference', + ), + ); + } + + /** + * @param string $name + * @param string $type + * @return callable|Format|null + */ + public function resolve( $name, $type ) { + return $this->formats[ $type ][ $name ] ?? null; + } + + /** + * @param string $name + * @return Format[]|callable[]|null + */ + public function resolveAll( $name ) { + $list = null; + + foreach ( $this->formats as $type => $items ) { + if ( isset( $items[ $name ] ) ) { + $list[ $type ] = $items[ $name ]; + } + } + + return $list; + } + + /** + * @param string $type + * @param string $name + * @param Format $format + * @return FormatResolver + */ + public function register( $type, $name, $format ): self { + $this->formats[ $type ][ $name ] = $format; + + return $this; + } + + /** + * @param string $type + * @param string $name + * @param callable $format + * @return FormatResolver + */ + public function registerCallable( $type, $name, $format ): self { + $this->formats[ $type ][ $name ] = $format; + + return $this; + } + + /** + * @param string $type + * @param string $name + * @return bool + */ + public function unregister( $type, $name ): bool { + if ( isset( $this->formats[ $type ][ $name ] ) ) { + unset( $this->formats[ $type ][ $name ] ); + + return true; + } + + return false; + } + + public function __serialize(): array { + return array( 'formats' => $this->formats ); + } + + public function __unserialize( array $data ) { + $this->formats = $data['formats']; + } } diff --git a/src/opis/json-schema/src/Resolvers/SchemaResolver.php b/src/opis/json-schema/src/Resolvers/SchemaResolver.php index cf6b625f..97677786 100644 --- a/src/opis/json-schema/src/Resolvers/SchemaResolver.php +++ b/src/opis/json-schema/src/Resolvers/SchemaResolver.php @@ -1,5 +1,6 @@ isAbsolute()) { - return null; - } - - $scheme = $uri->scheme(); - if (isset($this->protocols[$scheme])) { - return ($this->protocols[$scheme])($uri); - } - - $id = (string) $uri; - if (isset($this->raw[$id])) { - return $this->raw[$id]; - } - - $path = $this->resolvePath($uri); - - if ($path === null || !is_file($path)) { - return null; - } - - $data = file_get_contents($path); - if (!is_string($data)) { - return null; - } - - $data = json_decode($data, false); - - return $data; - } - - /** - * @param bool|object|string $schema - * @param string|null $id - * @return bool - */ - public function registerRaw($schema, $id = null): bool - { - if (is_string($schema)) { - $schema = json_decode($schema, false); - } - - if ($id !== null && strpos($id, '#') === false) { - $id .= '#'; - } - - if (is_bool($schema)) { - if ($id === null) { - return false; - } - $this->raw[$id] = $schema; - return true; - } - - if (!is_object($schema)) { - return false; - } - - - if ($id === null) { - if (!isset($schema->{'$id'}) || !is_string($schema->{'$id'})) { - return false; - } - - $id = $schema->{'$id'}; - if (strpos($id, '#') === false) { - $id .= '#'; - } - } - - $this->raw[$id] = $schema; - - return true; - } - - /** - * @param string $id - * @return bool - */ - public function unregisterRaw($id): bool - { - if (strpos($id, '#') === false) { - $id .= '#'; - } - - if (isset($this->raw[$id])) { - unset($this->raw[$id]); - return true; - } - - return false; - } - - /** - * @param string $id - * @param string $file - * @return SchemaResolver - */ - public function registerFile($id, $file): self - { - if (strpos($id, '#') === false) { - $id .= '#'; - } - - $this->files[$id] = $file; - - return $this; - } - - /** - * @param string $id - * @return bool - */ - public function unregisterFile($id): bool - { - if (strpos($id, '#') === false) { - $id .= '#'; - } - - if (!isset($this->files[$id])) { - return false; - } - - unset($this->files[$id]); - - return true; - } - - /** - * @param string $scheme - * @param callable $handler - * @return SchemaResolver - */ - public function registerProtocol($scheme, $handler): self - { - $this->protocols[$scheme] = $handler; - - return $this; - } - - /** - * @param string $scheme - * @return bool - */ - public function unregisterProtocol($scheme): bool - { - if (isset($this->protocols[$scheme])) { - unset($this->protocols[$scheme]); - - return true; - } - - return false; - } - - /** - * @param string $scheme - * @param string $host - * @param string|null $dir - * @return SchemaResolver - */ - public function registerProtocolDir($scheme, $host, $dir): self - { - if ($dir === null) { - unset($this->dirs[$scheme][$host]); - } else { - $this->dirs[$scheme][$host] = rtrim($dir, '/'); - } - - return $this; - } - - /** - * @param string $scheme - * @return bool - */ - public function unregisterProtocolDirs($scheme): bool - { - if (isset($this->dirs[$scheme])) { - unset($this->dirs[$scheme]); - - return true; - } - - return false; - } - - /** - * @param string $prefix - * @param string $dir - * @return SchemaResolver - */ - public function registerPrefix($prefix, $dir): self - { - $this->prefixes[$prefix] = rtrim($dir, '/'); - - // Sort - uksort($this->prefixes, [$this, 'sortPrefixKeys']); - - return $this; - } - - /** - * @param string $prefix - * @return SchemaResolver - */ - public function unregisterPrefix($prefix): self - { - if (isset($this->prefixes[$prefix])) { - unset($this->prefixes[$prefix]); - // Sort - uksort($this->prefixes, [$this, 'sortPrefixKeys']); - } - - return $this; - } - - - public function __serialize(): array - { - return [ - 'raw' => $this->raw, - 'protocols' => $this->protocols, - 'prefixes' => $this->prefixes, - 'dirs' => $this->dirs, - ]; - } - - public function __unserialize(array $data) - { - $this->raw = $data['raw']; - $this->protocols = $data['protocols']; - $this->prefixes = $data['prefixes']; - $this->dirs = $data['dirs']; - } - - /** - * @param string $a - * @param string $b - * @return int - */ - protected function sortPrefixKeys($a, $b): int - { - $la = strlen($a); - $lb = strlen($b); - - if ($lb > $la) { - return 1; - } - - if ($lb === $la) { - return $b < $a ? 1 : ($b === $a ? 0 : -1); - } - - return -1; - } - - /** - * @param Uri $uri - * @return string|null - */ - protected function resolvePath($uri) - { - $id = (string)$uri; - - if (isset($this->files[$id])) { - return $this->files[$id]; - } - - $scheme = $uri->scheme(); - - if (isset($this->dirs[$scheme])) { - $host = (string)$uri->host(); - if (isset($this->dirs[$scheme][$host])) { - return $this->dirs[$scheme][$host] . '/' . ltrim($uri->path(), '/'); - } - unset($host); - } - - $path = null; - foreach ($this->prefixes as $prefix => $dir) { - if ($prefix === '' || strpos($id, $prefix) === 0) { - $path = substr($id, strlen($prefix)); - if ($path === false || $path === '') { - $path = null; - continue; - } - $path = Uri::parseComponents($path); - if ($path && isset($path['path'])) { - $path = $dir . '/' . ltrim($path['path'], '/'); - break; - } - $path = null; - } - } - - return $path; - } +class SchemaResolver { + + /** @var bool[]|object[] */ + protected $raw = array(); + + /** @var string[] */ + protected $files = array(); + + /** @var callable[] */ + protected $protocols = array(); + + /** @var array */ + protected $prefixes = array(); + + /** @var array */ + protected $dirs = array(); + + /** + * @param Uri $uri + * @return bool|mixed|object|null + */ + public function resolve( $uri ) { + if ( ! $uri->isAbsolute() ) { + return null; + } + + $scheme = $uri->scheme(); + if ( isset( $this->protocols[ $scheme ] ) ) { + return ( $this->protocols[ $scheme ] )( $uri ); + } + + $id = (string) $uri; + if ( isset( $this->raw[ $id ] ) ) { + return $this->raw[ $id ]; + } + + $path = $this->resolvePath( $uri ); + + if ( $path === null || ! is_file( $path ) ) { + return null; + } + + $data = file_get_contents( $path ); + if ( ! is_string( $data ) ) { + return null; + } + + $data = json_decode( $data, false ); + + return $data; + } + + /** + * @param bool|object|string $schema + * @param string|null $id + * @return bool + */ + public function registerRaw( $schema, $id = null ): bool { + if ( is_string( $schema ) ) { + $schema = json_decode( $schema, false ); + } + + if ( $id !== null && strpos( $id, '#' ) === false ) { + $id .= '#'; + } + + if ( is_bool( $schema ) ) { + if ( $id === null ) { + return false; + } + $this->raw[ $id ] = $schema; + return true; + } + + if ( ! is_object( $schema ) ) { + return false; + } + + if ( $id === null ) { + if ( ! isset( $schema->{'$id'} ) || ! is_string( $schema->{'$id'} ) ) { + return false; + } + + $id = $schema->{'$id'}; + if ( strpos( $id, '#' ) === false ) { + $id .= '#'; + } + } + + $this->raw[ $id ] = $schema; + + return true; + } + + /** + * @param string $id + * @return bool + */ + public function unregisterRaw( $id ): bool { + if ( strpos( $id, '#' ) === false ) { + $id .= '#'; + } + + if ( isset( $this->raw[ $id ] ) ) { + unset( $this->raw[ $id ] ); + return true; + } + + return false; + } + + /** + * @param string $id + * @param string $file + * @return SchemaResolver + */ + public function registerFile( $id, $file ): self { + if ( strpos( $id, '#' ) === false ) { + $id .= '#'; + } + + $this->files[ $id ] = $file; + + return $this; + } + + /** + * @param string $id + * @return bool + */ + public function unregisterFile( $id ): bool { + if ( strpos( $id, '#' ) === false ) { + $id .= '#'; + } + + if ( ! isset( $this->files[ $id ] ) ) { + return false; + } + + unset( $this->files[ $id ] ); + + return true; + } + + /** + * @param string $scheme + * @param callable $handler + * @return SchemaResolver + */ + public function registerProtocol( $scheme, $handler ): self { + $this->protocols[ $scheme ] = $handler; + + return $this; + } + + /** + * @param string $scheme + * @return bool + */ + public function unregisterProtocol( $scheme ): bool { + if ( isset( $this->protocols[ $scheme ] ) ) { + unset( $this->protocols[ $scheme ] ); + + return true; + } + + return false; + } + + /** + * @param string $scheme + * @param string $host + * @param string|null $dir + * @return SchemaResolver + */ + public function registerProtocolDir( $scheme, $host, $dir ): self { + if ( $dir === null ) { + unset( $this->dirs[ $scheme ][ $host ] ); + } else { + $this->dirs[ $scheme ][ $host ] = rtrim( $dir, '/' ); + } + + return $this; + } + + /** + * @param string $scheme + * @return bool + */ + public function unregisterProtocolDirs( $scheme ): bool { + if ( isset( $this->dirs[ $scheme ] ) ) { + unset( $this->dirs[ $scheme ] ); + + return true; + } + + return false; + } + + /** + * @param string $prefix + * @param string $dir + * @return SchemaResolver + */ + public function registerPrefix( $prefix, $dir ): self { + $this->prefixes[ $prefix ] = rtrim( $dir, '/' ); + + // Sort + uksort( $this->prefixes, array( $this, 'sortPrefixKeys' ) ); + + return $this; + } + + /** + * @param string $prefix + * @return SchemaResolver + */ + public function unregisterPrefix( $prefix ): self { + if ( isset( $this->prefixes[ $prefix ] ) ) { + unset( $this->prefixes[ $prefix ] ); + // Sort + uksort( $this->prefixes, array( $this, 'sortPrefixKeys' ) ); + } + + return $this; + } + + + public function __serialize(): array { + return array( + 'raw' => $this->raw, + 'protocols' => $this->protocols, + 'prefixes' => $this->prefixes, + 'dirs' => $this->dirs, + ); + } + + public function __unserialize( array $data ) { + $this->raw = $data['raw']; + $this->protocols = $data['protocols']; + $this->prefixes = $data['prefixes']; + $this->dirs = $data['dirs']; + } + + /** + * @param string $a + * @param string $b + * @return int + */ + protected function sortPrefixKeys( $a, $b ): int { + $la = strlen( $a ); + $lb = strlen( $b ); + + if ( $lb > $la ) { + return 1; + } + + if ( $lb === $la ) { + return $b < $a ? 1 : ( $b === $a ? 0 : -1 ); + } + + return -1; + } + + /** + * @param Uri $uri + * @return string|null + */ + protected function resolvePath( $uri ) { + $id = (string) $uri; + + if ( isset( $this->files[ $id ] ) ) { + return $this->files[ $id ]; + } + + $scheme = $uri->scheme(); + + if ( isset( $this->dirs[ $scheme ] ) ) { + $host = (string) $uri->host(); + if ( isset( $this->dirs[ $scheme ][ $host ] ) ) { + return $this->dirs[ $scheme ][ $host ] . '/' . ltrim( $uri->path(), '/' ); + } + unset( $host ); + } + + $path = null; + foreach ( $this->prefixes as $prefix => $dir ) { + if ( $prefix === '' || strpos( $id, $prefix ) === 0 ) { + $path = substr( $id, strlen( $prefix ) ); + if ( $path === false || $path === '' ) { + $path = null; + continue; + } + $path = Uri::parseComponents( $path ); + if ( $path && isset( $path['path'] ) ) { + $path = $dir . '/' . ltrim( $path['path'], '/' ); + break; + } + $path = null; + } + } + + return $path; + } } diff --git a/src/opis/json-schema/src/Schema.php b/src/opis/json-schema/src/Schema.php index 821a6a02..9eee17ae 100644 --- a/src/opis/json-schema/src/Schema.php +++ b/src/opis/json-schema/src/Schema.php @@ -1,5 +1,6 @@ dataCache = new SplObjectStorage(); - $this->parser = $parser ?? new SchemaParser(); - $this->resolver = $resolver; - $this->decodeJsonString = $decodeJsonString; - } - - public function baseUri() - { - return $this->base; - } - - /** - * @param \Opis\JsonSchema\Uri|null $uri - */ - public function setBaseUri($uri): self - { - $this->base = $uri; - return $this; - } - - public function parser(): SchemaParser - { - return $this->parser; - } - - /** - * @param \Opis\JsonSchema\Parsers\SchemaParser $parser - */ - public function setParser($parser): self - { - $this->parser = $parser; - - return $this; - } - - public function resolver() - { - return $this->resolver; - } - - /** - * @param \Opis\JsonSchema\Resolvers\SchemaResolver|null $resolver - */ - public function setResolver($resolver): self - { - $this->resolver = $resolver; - - return $this; - } - - /** - * @param object $data - * @param null $id - * @param string|null $draft - * @return Schema - */ - public function loadObjectSchema($data, $id = null, $draft = null): Schema - { - // Check if already loaded - if ($schema = $this->checkExistingObject($data)) { - return $schema; - } - - if (!$id) { - $id = $this->createSchemaId($data); - } - - $handle_id = function (Uri $id) { - return $this->checkExistingUri($id); - }; - - $handle_object = function ($data, Uri $id, string $draft) { - $this->handleObject($data, $id, null, null, [], $draft, (string)$id); - - return $this->checkExistingObject($data); - }; - - return $this->parser->parseRootSchema($data, Uri::parse($id, true), $handle_id, $handle_object, $draft); - } - - /** - * @param bool $data - * @param null $id - * @param string|null $draft - * @return Schema - */ - public function loadBooleanSchema($data, $id = null, $draft = null): Schema - { - if (!$id) { - $id = $this->createSchemaId($data); - } - - return $this->parser->parseSchema(new SchemaInfo($data, Uri::parse($id, true), null, null, [], $draft)); - } - - /** - * @param Uri $uri - * @return Schema|null - */ - public function loadSchemaById($uri) - { - if (!$uri->isAbsolute()) { - if ($this->base === null || !$this->base->isAbsolute()) { - return null; - } - $uri = $this->base->resolveRef($uri); - } - - $fragment = $uri->fragment(); - if ($fragment === null) { - $uri = Uri::merge($uri, null, true); - $fragment = ''; - } - - $schema = $this->checkExistingUri($uri); - - if ($schema !== null) { - return $schema; - } - - if ($fragment === '') { - return $this->resolve($uri); - } - - $root = Uri::merge('#', $uri); - - // Check if already resolved - if (($schema = $this->checkExistingUri($root)) === null) { - // Try to resolve - if (($schema = $this->resolve($root)) === null) { - // Schema not found - return null; - } - } - - // Resolve json pointer - if ($fragment !== '' && $schema && $schema->info()->isObject() && - ($pointer = JsonPointer::parse($fragment)) && $pointer->isAbsolute()) { - $object = $pointer->data($schema->info()->data()); - if (is_bool($object)) { - $schema = $this->loadBooleanSchema($object, $uri, $schema->info()->draft()); - } elseif (is_object($object)) { - $schema = $this->loadObjectSchema($object, $uri, $schema->info()->draft()); - } else { - $schema = null; - } - if ($schema) { - $key = $this->cacheKey((string) $uri); - $this->uriCache[$key] = $schema; - return $schema; - } - } - - // Check fragment - return $this->checkExistingUri($uri); - } - - /** - * Clears internal cache - */ - public function clearCache() - { - $this->dataCache->removeAll($this->dataCache); - $this->uriCache = []; - } - - /** - * @param Uri $uri - * @return null|Schema - */ - protected function resolve($uri) - { - if ($this->resolver === null) { - return null; - } - - $data = $this->resolver->resolve($uri); - - if ($this->decodeJsonString && is_string($data)) { - $data = json_decode($data, false); - } - - if (is_bool($data)) { - $this->handleBoolean($data, $uri, null, null, [], $this->parser->defaultDraftVersion(), (string)$uri); - - return $this->checkExistingUri($uri); - } - - if (is_object($data)) { - if ($data instanceof Schema) { - return $data; - } - - $this->handleObject($data, $uri, null, null, [], $this->parser->defaultDraftVersion(), (string)$uri); - - return $this->checkExistingObject($data); - } - - return null; - } - - /** - * @param object $data - * @return null|Schema - */ - protected function checkExistingObject($data) - { - if (!$this->dataCache->contains($data)) { - return null; - } - - $schema = $this->dataCache[$data]; - - if ($schema instanceof LazySchema) { - $schema = $schema->schema(); - $this->dataCache[$data] = $schema; - } elseif (!($schema instanceof Schema)) { - $schema = null; - } - - return $schema; - } - - /** - * @param Uri $uri - * @return null|Schema - */ - protected function checkExistingUri($uri) - { - if ($uri->fragment() === null || !$uri->isAbsolute()) { - return null; - } - - $key = $this->cacheKey((string)$uri); - - if (!isset($this->uriCache[$key])) { - return null; - } - - $schema = $this->uriCache[$key]; - - if (!($schema instanceof Schema)) { - return $this->uriCache[$key] = $this->checkExistingObject($schema); - } - - if ($schema instanceof LazySchema) { - $schema = $schema->schema(); - $this->uriCache[$key] = $schema; - } - - return $schema; - } - - /** - * @param bool $data - * @param Uri|null $id - * @param Uri|null $base - * @param Uri|null $root - * @param array $path - * @param string $draft - * @param string $pointer - */ - protected function handleBoolean( - $data, - $id, - $base, - $root, - $path, - $draft, - $pointer - ) - { - $key = $this->cacheKey($pointer); - if (isset($this->uriCache[$key])) { - return; - } - - $this->uriCache[$key] = $this->parser->parseSchema(new SchemaInfo($data, $id, $base, $root, $path, $draft)); - } - - /** - * @param array $data - * @param Uri $base - * @param Uri $root - * @param array $path - * @param string $draft - * @param string $pointer - */ - protected function handleArray($data, $base, $root, $path, $draft, $pointer) - { - foreach ($data as $key => $value) { - if (!is_int($key)) { - continue; - } - - if (is_bool($value)) { - $this->handleBoolean($value, null, $base, $root, array_merge($path, [$key]), $draft, - $pointer . '/' . $key); - } elseif (is_array($value)) { - $this->handleArray($value, $base, $root, array_merge($path, [$key]), $draft, $pointer . '/' . $key); - } elseif (is_object($value)) { - $this->handleObject($value, null, $base, $root, array_merge($path, [$key]), $draft, - $pointer . '/' . $key); - } - } - } - - /** - * @param object $data - * @param Uri|null $id - * @param Uri|null $base - * @param Uri|null $root - * @param array $path - * @param string $draft - * @param string $pointer - */ - protected function handleObject( - $data, - $id, - $base, - $root, - $path, - $draft, - $pointer - ) - { - $schema_id = $this->parser->parseId($data); - $schema_anchor = $this->parser->parseAnchor($data, $draft); - $draft = $this->parser->parseSchemaDraft($data) ?? $draft; - - if ($schema_id !== null) { - $id = Uri::merge($schema_id, $base, true); - } elseif ($schema_anchor !== null) { - $id = Uri::merge('#' . $schema_anchor, $base, true); - } - - $lazy = new LazySchema(new SchemaInfo($data, $id, $base, $root, $path, $draft), $this->parser); - - if ($id && $id->isAbsolute()) { - $key = $this->cacheKey((string)$id); - if (isset($this->uriCache[$key])) { - throw new DuplicateSchemaIdException($id, $data); - } - $this->uriCache[$key] = $lazy; - } - - // When $id and $anchor are both present add a reference to the same lazy object - if ($schema_id !== null && $schema_anchor !== null) { - $anchor_id = Uri::merge('#' . $schema_anchor, $id, true); - $key = $this->cacheKey((string)$anchor_id); - if (isset($this->uriCache[$key])) { - throw new DuplicateSchemaIdException($anchor_id, $data); - } - $this->uriCache[$key] = $lazy; - } - - $this->dataCache[$data] = $lazy; - $this->uriCache[$this->cacheKey($pointer)] = $lazy; - - if ($root === null) { - $root = $id; - } - - if ($base === null) { - $base = $id ?? $root; - } elseif ($id !== null) { - $base = $id; - } - - foreach ($data as $key => $value) { - if (!is_string($key)) { - continue; - } - if (is_bool($value)) { - $this->handleBoolean($value, null, $base, $root, array_merge($path, [$key]), $draft, - $pointer . '/' . JsonPointer::encodePath($key)); - } elseif (is_array($value)) { - $this->handleArray($value, $base, $root, array_merge($path, [$key]), $draft, - $pointer . '/' . JsonPointer::encodePath($key)); - } elseif (is_object($value)) { - $this->handleObject($value, null, $base, $root, array_merge($path, [$key]), $draft, - $pointer . '/' . JsonPointer::encodePath($key)); - } - } - } - - /** - * @param string $path - * @return string - */ - protected function cacheKey($path): string - { - return isset($path[32]) ? md5($path) : $path; - } - - /** - * @param bool|object $data - * @return string - */ - protected function createSchemaId($data): string - { - if (is_bool($data)) { - $data = $data ? 'true' : 'false'; - } else { - $data = spl_object_hash($data); - } - - return "schema:///{$data}.json"; - } +class SchemaLoader { + + /** @var Schema[]|object[] */ + protected $uriCache = array(); + + /** + * @var \SplObjectStorage + */ + protected $dataCache; + + /** + * @var \Opis\JsonSchema\Parsers\SchemaParser + */ + protected $parser; + + /** + * @var \Opis\JsonSchema\Resolvers\SchemaResolver|null + */ + protected $resolver; + + /** + * @var bool + */ + protected $decodeJsonString = false; + + /** + * @var \Opis\JsonSchema\Uri|null + */ + protected $base; + + /** + * @param null|SchemaParser $parser + * @param null|SchemaResolver $resolver + * @param bool $decodeJsonString + */ + public function __construct( $parser = null, $resolver = null, bool $decodeJsonString = true ) { + $this->dataCache = new SplObjectStorage(); + $this->parser = $parser ?? new SchemaParser(); + $this->resolver = $resolver; + $this->decodeJsonString = $decodeJsonString; + } + + public function baseUri() { + return $this->base; + } + + /** + * @param \Opis\JsonSchema\Uri|null $uri + */ + public function setBaseUri( $uri ): self { + $this->base = $uri; + return $this; + } + + public function parser(): SchemaParser { + return $this->parser; + } + + /** + * @param \Opis\JsonSchema\Parsers\SchemaParser $parser + */ + public function setParser( $parser ): self { + $this->parser = $parser; + + return $this; + } + + public function resolver() { + return $this->resolver; + } + + /** + * @param \Opis\JsonSchema\Resolvers\SchemaResolver|null $resolver + */ + public function setResolver( $resolver ): self { + $this->resolver = $resolver; + + return $this; + } + + /** + * @param object $data + * @param null $id + * @param string|null $draft + * @return Schema + */ + public function loadObjectSchema( $data, $id = null, $draft = null ): Schema { + // Check if already loaded + if ( $schema = $this->checkExistingObject( $data ) ) { + return $schema; + } + + if ( ! $id ) { + $id = $this->createSchemaId( $data ); + } + + $handle_id = function ( Uri $id ) { + return $this->checkExistingUri( $id ); + }; + + $handle_object = function ( $data, Uri $id, string $draft ) { + $this->handleObject( $data, $id, null, null, array(), $draft, (string) $id ); + + return $this->checkExistingObject( $data ); + }; + + return $this->parser->parseRootSchema( $data, Uri::parse( $id, true ), $handle_id, $handle_object, $draft ); + } + + /** + * @param bool $data + * @param null $id + * @param string|null $draft + * @return Schema + */ + public function loadBooleanSchema( $data, $id = null, $draft = null ): Schema { + if ( ! $id ) { + $id = $this->createSchemaId( $data ); + } + + return $this->parser->parseSchema( new SchemaInfo( $data, Uri::parse( $id, true ), null, null, array(), $draft ) ); + } + + /** + * @param Uri $uri + * @return Schema|null + */ + public function loadSchemaById( $uri ) { + if ( ! $uri->isAbsolute() ) { + if ( $this->base === null || ! $this->base->isAbsolute() ) { + return null; + } + $uri = $this->base->resolveRef( $uri ); + } + + $fragment = $uri->fragment(); + if ( $fragment === null ) { + $uri = Uri::merge( $uri, null, true ); + $fragment = ''; + } + + $schema = $this->checkExistingUri( $uri ); + + if ( $schema !== null ) { + return $schema; + } + + if ( $fragment === '' ) { + return $this->resolve( $uri ); + } + + $root = Uri::merge( '#', $uri ); + + // Check if already resolved + if ( ( $schema = $this->checkExistingUri( $root ) ) === null ) { + // Try to resolve + if ( ( $schema = $this->resolve( $root ) ) === null ) { + // Schema not found + return null; + } + } + + // Resolve json pointer + if ( $fragment !== '' && $schema && $schema->info()->isObject() && + ( $pointer = JsonPointer::parse( $fragment ) ) && $pointer->isAbsolute() ) { + $object = $pointer->data( $schema->info()->data() ); + if ( is_bool( $object ) ) { + $schema = $this->loadBooleanSchema( $object, $uri, $schema->info()->draft() ); + } elseif ( is_object( $object ) ) { + $schema = $this->loadObjectSchema( $object, $uri, $schema->info()->draft() ); + } else { + $schema = null; + } + if ( $schema ) { + $key = $this->cacheKey( (string) $uri ); + $this->uriCache[ $key ] = $schema; + return $schema; + } + } + + // Check fragment + return $this->checkExistingUri( $uri ); + } + + /** + * Clears internal cache + */ + public function clearCache() { + $this->dataCache->removeAll( $this->dataCache ); + $this->uriCache = array(); + } + + /** + * @param Uri $uri + * @return null|Schema + */ + protected function resolve( $uri ) { + if ( $this->resolver === null ) { + return null; + } + + $data = $this->resolver->resolve( $uri ); + + if ( $this->decodeJsonString && is_string( $data ) ) { + $data = json_decode( $data, false ); + } + + if ( is_bool( $data ) ) { + $this->handleBoolean( $data, $uri, null, null, array(), $this->parser->defaultDraftVersion(), (string) $uri ); + + return $this->checkExistingUri( $uri ); + } + + if ( is_object( $data ) ) { + if ( $data instanceof Schema ) { + return $data; + } + + $this->handleObject( $data, $uri, null, null, array(), $this->parser->defaultDraftVersion(), (string) $uri ); + + return $this->checkExistingObject( $data ); + } + + return null; + } + + /** + * @param object $data + * @return null|Schema + */ + protected function checkExistingObject( $data ) { + if ( ! $this->dataCache->contains( $data ) ) { + return null; + } + + $schema = $this->dataCache[ $data ]; + + if ( $schema instanceof LazySchema ) { + $schema = $schema->schema(); + $this->dataCache[ $data ] = $schema; + } elseif ( ! ( $schema instanceof Schema ) ) { + $schema = null; + } + + return $schema; + } + + /** + * @param Uri $uri + * @return null|Schema + */ + protected function checkExistingUri( $uri ) { + if ( $uri->fragment() === null || ! $uri->isAbsolute() ) { + return null; + } + + $key = $this->cacheKey( (string) $uri ); + + if ( ! isset( $this->uriCache[ $key ] ) ) { + return null; + } + + $schema = $this->uriCache[ $key ]; + + if ( ! ( $schema instanceof Schema ) ) { + return $this->uriCache[ $key ] = $this->checkExistingObject( $schema ); + } + + if ( $schema instanceof LazySchema ) { + $schema = $schema->schema(); + $this->uriCache[ $key ] = $schema; + } + + return $schema; + } + + /** + * @param bool $data + * @param Uri|null $id + * @param Uri|null $base + * @param Uri|null $root + * @param array $path + * @param string $draft + * @param string $pointer + */ + protected function handleBoolean( + $data, + $id, + $base, + $root, + $path, + $draft, + $pointer + ) { + $key = $this->cacheKey( $pointer ); + if ( isset( $this->uriCache[ $key ] ) ) { + return; + } + + $this->uriCache[ $key ] = $this->parser->parseSchema( new SchemaInfo( $data, $id, $base, $root, $path, $draft ) ); + } + + /** + * @param array $data + * @param Uri $base + * @param Uri $root + * @param array $path + * @param string $draft + * @param string $pointer + */ + protected function handleArray( $data, $base, $root, $path, $draft, $pointer ) { + foreach ( $data as $key => $value ) { + if ( ! is_int( $key ) ) { + continue; + } + + if ( is_bool( $value ) ) { + $this->handleBoolean( + $value, + null, + $base, + $root, + array_merge( $path, array( $key ) ), + $draft, + $pointer . '/' . $key + ); + } elseif ( is_array( $value ) ) { + $this->handleArray( $value, $base, $root, array_merge( $path, array( $key ) ), $draft, $pointer . '/' . $key ); + } elseif ( is_object( $value ) ) { + $this->handleObject( + $value, + null, + $base, + $root, + array_merge( $path, array( $key ) ), + $draft, + $pointer . '/' . $key + ); + } + } + } + + /** + * @param object $data + * @param Uri|null $id + * @param Uri|null $base + * @param Uri|null $root + * @param array $path + * @param string $draft + * @param string $pointer + */ + protected function handleObject( + $data, + $id, + $base, + $root, + $path, + $draft, + $pointer + ) { + $schema_id = $this->parser->parseId( $data ); + $schema_anchor = $this->parser->parseAnchor( $data, $draft ); + $draft = $this->parser->parseSchemaDraft( $data ) ?? $draft; + + if ( $schema_id !== null ) { + $id = Uri::merge( $schema_id, $base, true ); + } elseif ( $schema_anchor !== null ) { + $id = Uri::merge( '#' . $schema_anchor, $base, true ); + } + + $lazy = new LazySchema( new SchemaInfo( $data, $id, $base, $root, $path, $draft ), $this->parser ); + + if ( $id && $id->isAbsolute() ) { + $key = $this->cacheKey( (string) $id ); + if ( isset( $this->uriCache[ $key ] ) ) { + throw new DuplicateSchemaIdException( $id, $data ); + } + $this->uriCache[ $key ] = $lazy; + } + + // When $id and $anchor are both present add a reference to the same lazy object + if ( $schema_id !== null && $schema_anchor !== null ) { + $anchor_id = Uri::merge( '#' . $schema_anchor, $id, true ); + $key = $this->cacheKey( (string) $anchor_id ); + if ( isset( $this->uriCache[ $key ] ) ) { + throw new DuplicateSchemaIdException( $anchor_id, $data ); + } + $this->uriCache[ $key ] = $lazy; + } + + $this->dataCache[ $data ] = $lazy; + $this->uriCache[ $this->cacheKey( $pointer ) ] = $lazy; + + if ( $root === null ) { + $root = $id; + } + + if ( $base === null ) { + $base = $id ?? $root; + } elseif ( $id !== null ) { + $base = $id; + } + + foreach ( $data as $key => $value ) { + if ( ! is_string( $key ) ) { + continue; + } + if ( is_bool( $value ) ) { + $this->handleBoolean( + $value, + null, + $base, + $root, + array_merge( $path, array( $key ) ), + $draft, + $pointer . '/' . JsonPointer::encodePath( $key ) + ); + } elseif ( is_array( $value ) ) { + $this->handleArray( + $value, + $base, + $root, + array_merge( $path, array( $key ) ), + $draft, + $pointer . '/' . JsonPointer::encodePath( $key ) + ); + } elseif ( is_object( $value ) ) { + $this->handleObject( + $value, + null, + $base, + $root, + array_merge( $path, array( $key ) ), + $draft, + $pointer . '/' . JsonPointer::encodePath( $key ) + ); + } + } + } + + /** + * @param string $path + * @return string + */ + protected function cacheKey( $path ): string { + return isset( $path[32] ) ? md5( $path ) : $path; + } + + /** + * @param bool|object $data + * @return string + */ + protected function createSchemaId( $data ): string { + if ( is_bool( $data ) ) { + $data = $data ? 'true' : 'false'; + } else { + $data = spl_object_hash( $data ); + } + + return "schema:///{$data}.json"; + } } diff --git a/src/opis/json-schema/src/SchemaValidator.php b/src/opis/json-schema/src/SchemaValidator.php index 68c49509..de156cb8 100644 --- a/src/opis/json-schema/src/SchemaValidator.php +++ b/src/opis/json-schema/src/SchemaValidator.php @@ -1,5 +1,6 @@ info = $info; - } + /** + * @var \Opis\JsonSchema\Info\SchemaInfo + */ + protected $info; - /** - * @inheritDoc - */ - public function info(): SchemaInfo - { - return $this->info; - } -} \ No newline at end of file + /** + * @param SchemaInfo $info + */ + public function __construct( SchemaInfo $info ) { + $this->info = $info; + } + + /** + * @inheritDoc + */ + public function info(): SchemaInfo { + return $this->info; + } +} diff --git a/src/opis/json-schema/src/Schemas/BooleanSchema.php b/src/opis/json-schema/src/Schemas/BooleanSchema.php index 884d5dfa..26301f42 100644 --- a/src/opis/json-schema/src/Schemas/BooleanSchema.php +++ b/src/opis/json-schema/src/Schemas/BooleanSchema.php @@ -1,5 +1,6 @@ data = $info->data(); - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function validate($context) - { - if ($this->data) { - return null; - } - - return new ValidationError('', $this, DataInfo::fromContext($context), 'Data not allowed'); - } +final class BooleanSchema extends AbstractSchema { + + + /** + * @var bool + */ + private $data; + + /** + * @param SchemaInfo $info + */ + public function __construct( SchemaInfo $info ) { + parent::__construct( $info ); + $this->data = $info->data(); + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function validate( $context ) { + if ( $this->data ) { + return null; + } + + return new ValidationError( '', $this, DataInfo::fromContext( $context ), 'Data not allowed' ); + } } diff --git a/src/opis/json-schema/src/Schemas/EmptySchema.php b/src/opis/json-schema/src/Schemas/EmptySchema.php index 4e158c46..e89b80b6 100644 --- a/src/opis/json-schema/src/Schemas/EmptySchema.php +++ b/src/opis/json-schema/src/Schemas/EmptySchema.php @@ -1,5 +1,6 @@ keywordValidator = $keywordValidator; - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function validate($context) - { - if (!$this->keywordValidator) { - return null; - } - - $context->pushSharedObject($this); - $error = $this->keywordValidator->validate($context); - $context->popSharedObject(); - - return $error; - } +final class EmptySchema extends AbstractSchema { + + /** + * @var \Opis\JsonSchema\KeywordValidator|null + */ + protected $keywordValidator; + + /** + * @inheritDoc + */ + public function __construct( SchemaInfo $info, $keywordValidator = null ) { + parent::__construct( $info ); + $this->keywordValidator = $keywordValidator; + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function validate( $context ) { + if ( ! $this->keywordValidator ) { + return null; + } + + $context->pushSharedObject( $this ); + $error = $this->keywordValidator->validate( $context ); + $context->popSharedObject(); + + return $error; + } } diff --git a/src/opis/json-schema/src/Schemas/ExceptionSchema.php b/src/opis/json-schema/src/Schemas/ExceptionSchema.php index a1789e9d..abc34659 100644 --- a/src/opis/json-schema/src/Schemas/ExceptionSchema.php +++ b/src/opis/json-schema/src/Schemas/ExceptionSchema.php @@ -1,5 +1,6 @@ exception = $exception; - } + /** + * @var \Opis\JsonSchema\Exceptions\SchemaException + */ + private $exception; - /** - * @inheritDoc - * @throws SchemaException - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function validate($context) - { - throw $this->exception; - } + /** + * @param SchemaInfo $info + * @param SchemaException $exception + */ + public function __construct( SchemaInfo $info, SchemaException $exception ) { + parent::__construct( $info ); + $this->exception = $exception; + } + + /** + * @inheritDoc + * @throws SchemaException + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function validate( $context ) { + throw $this->exception; + } } diff --git a/src/opis/json-schema/src/Schemas/LazySchema.php b/src/opis/json-schema/src/Schemas/LazySchema.php index f5ca5995..858ecfff 100644 --- a/src/opis/json-schema/src/Schemas/LazySchema.php +++ b/src/opis/json-schema/src/Schemas/LazySchema.php @@ -1,5 +1,6 @@ parser = $parser; - } + /** + * @var \Opis\JsonSchema\Schema|null + */ + private $schema; - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function validate($context) - { - return $this->schema()->validate($context); - } + /** + * @param SchemaInfo $info + * @param SchemaParser $parser + */ + public function __construct( SchemaInfo $info, SchemaParser $parser ) { + parent::__construct( $info ); + $this->parser = $parser; + } - /** - * @return Schema - */ - public function schema(): Schema - { - if ($this->schema === null) { - $this->schema = $this->parser->parseSchema($this->info); - } + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function validate( $context ) { + return $this->schema()->validate( $context ); + } - return $this->schema; - } + /** + * @return Schema + */ + public function schema(): Schema { + if ( $this->schema === null ) { + $this->schema = $this->parser->parseSchema( $this->info ); + } + + return $this->schema; + } } diff --git a/src/opis/json-schema/src/Schemas/ObjectSchema.php b/src/opis/json-schema/src/Schemas/ObjectSchema.php index e2d08174..d64b7de1 100644 --- a/src/opis/json-schema/src/Schemas/ObjectSchema.php +++ b/src/opis/json-schema/src/Schemas/ObjectSchema.php @@ -1,5 +1,6 @@ types = $types; - $this->before = $before; - $this->after = $after; - $this->keywordValidator = $keywordValidator; - - if ($keywordValidator) { - while ($next = $keywordValidator->next()) { - $keywordValidator = $next; - } - $keywordValidator->setNext(new CallbackKeywordValidator([$this, 'doValidate'])); - } - } - - /** - * @inheritDoc - * @param \Opis\JsonSchema\ValidationContext $context - */ - public function validate($context) - { - $context->pushSharedObject($this); - $error = $this->keywordValidator ? $this->keywordValidator->validate($context) : $this->doValidate($context); - $context->popSharedObject(); - - return $error; - } - - /** - * @param ValidationContext $context - * @return null|ValidationError - *@internal - */ - public function doValidate($context) - { - if ($this->before && ($error = $this->applyKeywords($this->before, $context))) { - return $error; - } - - if ($this->types && ($type = $context->currentDataType())) { - if (isset($this->types[$type]) && ($error = $this->applyKeywords($this->types[$type], $context))) { - return $error; - } - - if (($type = Helper::getJsonSuperType($type)) && isset($this->types[$type])) { - if ($error = $this->applyKeywords($this->types[$type], $context)) { - return $error; - } - } - - unset($type); - } - - if ($this->after && ($error = $this->applyKeywords($this->after, $context))) { - return $error; - } - - return null; - } - - /** - * @param Keyword[] $keywords - * @param ValidationContext $context - * @return ValidationError|null - */ - protected function applyKeywords($keywords, $context) - { - foreach ($keywords as $keyword) { - if ($error = $keyword->validate($context, $this)) { - return $error; - } - } - - return null; - } +class ObjectSchema extends AbstractSchema { + + /** + * @var \Opis\JsonSchema\KeywordValidator|null + */ + protected $keywordValidator; + + /** @var Keyword[]|null */ + protected $before; + + /** @var Keyword[]|null */ + protected $after; + + /** @var Keyword[][]|null */ + protected $types; + + /** + * @param SchemaInfo $info + * @param KeywordValidator|null $keywordValidator + * @param Keyword[][]|null $types + * @param Keyword[]|null $before + * @param Keyword[]|null $after + */ + public function __construct( SchemaInfo $info, $keywordValidator, $types, $before, $after ) { + parent::__construct( $info ); + $this->types = $types; + $this->before = $before; + $this->after = $after; + $this->keywordValidator = $keywordValidator; + + if ( $keywordValidator ) { + while ( $next = $keywordValidator->next() ) { + $keywordValidator = $next; + } + $keywordValidator->setNext( new CallbackKeywordValidator( array( $this, 'doValidate' ) ) ); + } + } + + /** + * @inheritDoc + * @param \Opis\JsonSchema\ValidationContext $context + */ + public function validate( $context ) { + $context->pushSharedObject( $this ); + $error = $this->keywordValidator ? $this->keywordValidator->validate( $context ) : $this->doValidate( $context ); + $context->popSharedObject(); + + return $error; + } + + /** + * @param ValidationContext $context + * @return null|ValidationError + * @internal + */ + public function doValidate( $context ) { + if ( $this->before && ( $error = $this->applyKeywords( $this->before, $context ) ) ) { + return $error; + } + + if ( $this->types && ( $type = $context->currentDataType() ) ) { + if ( isset( $this->types[ $type ] ) && ( $error = $this->applyKeywords( $this->types[ $type ], $context ) ) ) { + return $error; + } + + if ( ( $type = Helper::getJsonSuperType( $type ) ) && isset( $this->types[ $type ] ) ) { + if ( $error = $this->applyKeywords( $this->types[ $type ], $context ) ) { + return $error; + } + } + + unset( $type ); + } + + if ( $this->after && ( $error = $this->applyKeywords( $this->after, $context ) ) ) { + return $error; + } + + return null; + } + + /** + * @param Keyword[] $keywords + * @param ValidationContext $context + * @return ValidationError|null + */ + protected function applyKeywords( $keywords, $context ) { + foreach ( $keywords as $keyword ) { + if ( $error = $keyword->validate( $context, $this ) ) { + return $error; + } + } + + return null; + } } diff --git a/src/opis/json-schema/src/Uri.php b/src/opis/json-schema/src/Uri.php index 1f9b5d38..17adfac5 100644 --- a/src/opis/json-schema/src/Uri.php +++ b/src/opis/json-schema/src/Uri.php @@ -1,5 +1,6 @@ __toString(); - } + public function __construct( array $components ) { + if ( static::$useNormalizedComponents ) { + $components = self::normalizeComponents( $components ); + } + parent::__construct( $components ); + } - /** - * @param string $uri - * @param bool $ensure_fragment - * @return static|null - */ - public static function parse($uri, $ensure_fragment = false) - { - if ($ensure_fragment && strpos($uri, '#') === false) { - $uri .= '#'; - } + /** + * @inheritDoc + */ + public function jsonSerialize(): string { + return $this->__toString(); + } - return self::create($uri); - } + /** + * @param string $uri + * @param bool $ensure_fragment + * @return static|null + */ + public static function parse( $uri, $ensure_fragment = false ) { + if ( $ensure_fragment && strpos( $uri, '#' ) === false ) { + $uri .= '#'; + } - /** - * @param string|array|static $uri - * @param string|array|static $base - * @param bool $ensure_fragment - * @return static|null - */ - public static function merge($uri, $base, $ensure_fragment = false) - { - $uri = self::resolveComponents($uri); + return self::create( $uri ); + } - if ($uri === null) { - return null; - } + /** + * @param string|array|static $uri + * @param string|array|static $base + * @param bool $ensure_fragment + * @return static|null + */ + public static function merge( $uri, $base, $ensure_fragment = false ) { + $uri = self::resolveComponents( $uri ); - if ($ensure_fragment && !isset($uri['fragment'])) { - $uri['fragment'] = ''; - } + if ( $uri === null ) { + return null; + } - $base = self::resolveComponents($base); + if ( $ensure_fragment && ! isset( $uri['fragment'] ) ) { + $uri['fragment'] = ''; + } - if (!$base) { - return new self($uri); - } + $base = self::resolveComponents( $base ); - return new self(self::mergeComponents($uri, $base)); - } + if ( ! $base ) { + return new self( $uri ); + } - /** - * @param bool $value - */ - public static function useNormalizedComponents($value) - { - self::$useNormalizedComponents = $value; - } + return new self( self::mergeComponents( $uri, $base ) ); + } + + /** + * @param bool $value + */ + public static function useNormalizedComponents( $value ) { + self::$useNormalizedComponents = $value; + } } diff --git a/src/opis/json-schema/src/ValidationContext.php b/src/opis/json-schema/src/ValidationContext.php index 6d6f4928..de182251 100644 --- a/src/opis/json-schema/src/ValidationContext.php +++ b/src/opis/json-schema/src/ValidationContext.php @@ -1,5 +1,6 @@ sender = $sender; - $this->rootData = $data; - $this->loader = $loader; - $this->parent = $parent; - $this->globals = $globals; - $this->slots = null; - $this->maxErrors = $max_errors; - $this->currentData = [ - [$data, false], - ]; - - if ($slots) { - $this->setSlots($slots); - } - } - - /** - * @param $data - * @param Schema|null $sender - * @param array|null $globals - * @param array|null $slots - * @param int|null $max_errors - * @return self - */ - public function newInstance( - $data, - $sender, - $globals = null, - $slots = null, - $max_errors = null - ): self { - return new self($data, $this->loader, $this, $sender, $globals ?? $this->globals, $slots ?? $this->slots, - $max_errors ?? $this->maxErrors); - } - - /** - * @param \Opis\JsonSchema\Schema $sender - * @param \Opis\JsonSchema\Variables|null $mapper - * @param \Opis\JsonSchema\Variables|null $globals - * @param mixed[]|null $slots - * @param int|null $maxErrors - */ - public function create( - $sender, - $mapper = null, - $globals = null, - $slots = null, - $maxErrors = null - ): self { - if ($globals) { - $globals = $globals->resolve($this->rootData(), $this->currentDataPath()); - if (!is_array($globals)) { - $globals = (array)$globals; - } - $globals += $this->globals; - } else { - $globals = $this->globals; - } - - if ($mapper) { - $data = $mapper->resolve($this->rootData(), $this->currentDataPath()); - } else { - $data = $this->currentData(); - } - - return new self($data, $this->loader, $this, $sender, $globals, $slots ?? $this->slots, - $maxErrors ?? $this->maxErrors); - } - - public function sender() - { - return $this->sender; - } - - /** - * @return self|null - */ - public function parent() - { - return $this->parent; - } - - /** - * @return SchemaLoader - */ - public function loader(): SchemaLoader - { - return $this->loader; - } - - /** - * @return mixed - */ - public function rootData() - { - return $this->rootData; - } - - /** - * @return mixed - */ - public function currentData() - { - return $this->currentData[$this->pathIndex][0]; - } - - /** - * @param $value - */ - public function setCurrentData($value) - { - $this->currentData[$this->pathIndex][0] = $value; - $this->currentData[$this->pathIndex][1] = false; - } - - /** - * @return string|null - */ - public function currentDataType() - { - $type = $this->currentData[$this->pathIndex][1]; - if ($type === false) { - $type = Helper::getJsonType($this->currentData[$this->pathIndex][0]); - $this->currentData[$this->pathIndex][1] = $type; - } - - return $type; - } - - public function fullDataPath(): array - { - if ($this->fullPath === null) { - if ($this->parent === null) { - return $this->currentDataPath; - } - $this->fullPath = array_merge($this->parent->fullDataPath(), $this->currentDataPath); - } - - return $this->fullPath; - } - - /** - * @return int[]|string[] - */ - public function currentDataPath(): array - { - return $this->currentDataPath; - } - - /** - * @param string|int $key - * @return $this - */ - public function pushDataPath($key): self - { - $this->currentDataPath[] = $key; - if ($this->fullPath !== null) { - $this->fullPath[] = $key; - } - - $data = $this->currentData[$this->pathIndex][0]; - - if (is_array($data)) { - $data = $data[$key] ?? null; - } elseif (is_object($data)) { - $data = $data->{$key} ?? null; - } else { - $data = null; - } - - $this->currentData[] = [$data, false]; - $this->pathIndex++; - - return $this; - } - - /** - * @return $this - */ - public function popDataPath(): self - { - if ($this->pathIndex < 1) { - return $this; - } - - if ($this->fullPath !== null) { - array_pop($this->fullPath); - } - array_pop($this->currentDataPath); - array_pop($this->currentData); - $this->pathIndex--; - - return $this; - } - - /** - * @return array - */ - public function globals(): array - { - return $this->globals; - } - - /** - * @param array $globals - * @param bool $overwrite - * @return $this - */ - public function setGlobals($globals, $overwrite = false): self - { - if ($overwrite) { - $this->globals = $globals; - } elseif ($globals) { - $this->globals = $globals + $this->globals; - } - - return $this; - } - - /** - * @return object[]|Schema[]|string[]|null - */ - public function slots() - { - return $this->slots; - } - - /** - * @param array|null $slots - * @return $this - */ - public function setSlots($slots): self - { - if ($slots) { - $list = []; - - foreach ($slots as $name => $value) { - if (is_bool($value)) { - $value = $this->loader->loadBooleanSchema($value); - } elseif (is_object($value)) { - if ($value instanceof Schema) { - $list[$name] = $value; - continue; - } - $value = $this->loader->loadObjectSchema($value); - } elseif (is_string($value)) { - if (isset($this->slots[$value])) { - $value = $this->slots[$value]; - } elseif ($this->parent) { - $value = $this->parent->slot($value); - } - } - - if ($value instanceof Schema) { - $list[$name] = $value; - } - } - - $this->slots = $list; - } else { - $this->slots = null; - } - - return $this; - } - - /** - * @param string $name - * @return Schema|null - */ - public function slot($name) - { - return $this->slots[$name] ?? null; - } - - /** - * @return int - */ - public function maxErrors(): int - { - return $this->maxErrors; - } - - /** - * @param int $max - * @return $this - */ - public function setMaxErrors($max): self - { - $this->maxErrors = $max; - - return $this; - } - - /* --------------------- */ - - /** - * @param Schema $schema - * @return $this - */ - public function pushSharedObject($schema): self - { - $unevaluated = !in_array($schema->info()->draft(), ['06', '07']); - if ($unevaluated && ($parser = $this->loader->parser()) && !$parser->option('allowUnevaluated', true)) { - $unevaluated = false; - } - - $this->shared[] = [ - 'schema' => $schema, - 'unevaluated' => $unevaluated, - 'object' => null, - ]; - $this->sharedIndex++; - - return $this; - } - - /** - * @return $this - */ - public function popSharedObject(): self - { - if ($this->sharedIndex < 0) { - return $this; - } - - $data = array_pop($this->shared); - $this->sharedIndex--; - - if ($data['unevaluated'] && $data['object']) { - if ($this->sharedIndex >= 0) { - $this->mergeUnevaluated($data['object']); - } elseif ($this->parent && $this->parent->sharedIndex >= 0) { - $this->parent->mergeUnevaluated($data['object']); - } - } - - return $this; - } - - /** - * @return object|null - */ - public function sharedObject() - { - if ($this->sharedIndex < 0) { - return null; - } - - return $this->shared[$this->sharedIndex]['object'] = $this->shared[$this->sharedIndex]['object'] ?? (object)[]; - } - - public function schema() - { - return $this->shared[$this->sharedIndex]['schema'] ?? null; - } - - public function trackUnevaluated(): bool - { - return $this->shared[$this->sharedIndex]['unevaluated'] ?? false; - } - - /** - * @param object $obj - */ - protected function mergeUnevaluated($obj) - { - switch ($this->currentDataType()) { - case 'object': - if (isset($obj->evaluatedProperties)) { - $this->addEvaluatedProperties($obj->evaluatedProperties); - } - break; - case 'array': - if (isset($obj->evaluatedItems)) { - $this->addEvaluatedItems($obj->evaluatedItems); - } - break; - } - } - - /* ----------------*/ - - public function getStringLength() - { - if ($this->currentDataType() !== 'string') { - return null; - } - - $shared = $this->sharedObject(); - - if (!isset($shared->stringLength)) { - $shared->stringLength = UnicodeString::from($this->currentData())->length(); - } - - return $shared->stringLength; - } - - /** - * @param string $content - */ - public function setDecodedContent($content): bool - { - if ($this->currentDataType() !== 'string') { - return false; - } - - $this->sharedObject()->decodedContent = $content; - - return true; - } - - public function getDecodedContent() - { - if ($this->currentDataType() !== 'string') { - return null; - } - return $this->sharedObject()->decodedContent ?? $this->currentData(); - } - - public function getObjectProperties() - { - if ($this->currentDataType() !== 'object') { - return null; - } - - return $this->sharedObject()->objectProperties = $this->sharedObject()->objectProperties ?? array_keys(get_object_vars($this->currentData())); - } - - /** - * @param mixed[]|null $properties - */ - public function addCheckedProperties($properties): bool - { - if (!$properties) { - return false; - } - - $shared = $this->sharedObject(); - - if (!isset($shared->checkedProperties)) { - $shared->checkedProperties = $properties; - } else { - $shared->checkedProperties = array_values(array_unique(array_merge($shared->checkedProperties, $properties))); - } - - return true; - } - - public function getCheckedProperties() - { - return $this->sharedObject()->checkedProperties ?? null; - } - - public function getUncheckedProperties() - { - $properties = $this->getObjectProperties(); - if (!$properties) { - return $properties; - } - - $checked = $this->sharedObject()->checkedProperties ?? null; - if (!$checked) { - return $properties; - } - - return array_values(array_diff($properties, $checked)); - } - - public function markAllAsEvaluatedProperties(): bool - { - return $this->addEvaluatedProperties($this->getObjectProperties()); - } - - /** - * @param mixed[]|null $properties - */ - public function addEvaluatedProperties($properties): bool - { - if (!$properties || !($this->currentDataType() === 'object') || !$this->trackUnevaluated()) { - return false; - } - - $shared = $this->sharedObject(); - - if (!isset($shared->evaluatedProperties)) { - $shared->evaluatedProperties = $properties; - } else { - $shared->evaluatedProperties = array_values(array_unique(array_merge($shared->evaluatedProperties, $properties))); - } - - return true; - } - - public function getEvaluatedProperties() - { - return $this->sharedObject()->evaluatedProperties ?? null; - } - - public function getUnevaluatedProperties() - { - $properties = $this->getObjectProperties(); - if (!$properties) { - return $properties; - } - - $evaluated = $this->sharedObject()->evaluatedProperties ?? null; - if (!$evaluated) { - return $properties; - } - - return array_values(array_diff($properties, $evaluated)); - } - - public function markAllAsEvaluatedItems(): bool - { - return $this->addEvaluatedItems(range(0, count($this->currentData()))); - } - - /** - * @param int $count - */ - public function markCountAsEvaluatedItems($count): bool - { - if (!$count) { - return false; - } - - return $this->addEvaluatedItems(range(0, $count)); - } - - /** - * @param mixed[]|null $items - */ - public function addEvaluatedItems($items): bool - { - if (!$items || !($this->currentDataType() === 'array') || !$this->trackUnevaluated()) { - return false; - } - - $shared = $this->sharedObject(); - - if (!isset($shared->evaluatedItems)) { - $shared->evaluatedItems = $items; - } else { - $shared->evaluatedItems = array_values(array_unique(array_merge($shared->evaluatedItems, $items), SORT_NUMERIC)); - } - - return true; - } - - public function getEvaluatedItems() - { - return $this->sharedObject()->evaluatedItems ?? null; - } - - public function getUnevaluatedItems() - { - if ($this->currentDataType() !== 'array') { - return null; - } - - $items = array_keys($this->currentData()); - if (!$items) { - return $items; - } - - $evaluated = $this->sharedObject()->evaluatedItems ?? null; - if (!$evaluated) { - return $items; - } - - return array_values(array_diff($items, $evaluated)); - } - - /** - * @param \Opis\JsonSchema\Schema $schema - * @param int|null $maxErrors - * @param bool $reset_on_error_only - * @param \ArrayObject|null $array - */ - public function validateSchemaWithoutEvaluated( - $schema, - $maxErrors = null, - $reset_on_error_only = false, - $array = null - ) { - $currentMaxErrors = $this->maxErrors; - - $this->maxErrors = $maxErrors ?? $currentMaxErrors; - - if ($this->trackUnevaluated()) { - $shared = $this->sharedObject(); - - $props = $shared->evaluatedProperties ?? null; - $items = $shared->evaluatedItems ?? null; - - $error = $schema->validate($this); - - if ($array) { - $value = null; - - if ($shared->evaluatedProperties ?? null) { - if ($props) { - if ($diff = array_diff($shared->evaluatedProperties, $props)) { - $value['properties'] = $diff; - } - } else { - $value['properties'] = $shared->evaluatedProperties; - } - } - - if ($shared->evaluatedItems ?? null) { - if ($items) { - if ($diff = array_diff($shared->evaluatedItems, $items)) { - $value['items'] = $diff; - } - } else { - $value['items'] = $shared->evaluatedItems; - } - } - - if ($value) { - $array[] = $value; - } - } - - if ($reset_on_error_only) { - if ($error) { - $shared->evaluatedProperties = $props; - $shared->evaluatedItems = $items; - } - } else { - $shared->evaluatedProperties = $props; - $shared->evaluatedItems = $items; - } - } else { - $error = $schema->validate($this); - } - - $this->maxErrors = $currentMaxErrors; - - return $error; - } +class ValidationContext { + + /** @var mixed */ + protected $rootData; + + /** @var string[]|int[] */ + protected $currentDataPath = array(); + + /** + * @var mixed[]|null + */ + protected $fullPath; + + /** + * @var mixed[] + */ + protected $globals; + + /** @var mixed */ + protected $currentData = null; + + /** + * @var $this|null + */ + protected $parent; + + /** + * @var \Opis\JsonSchema\SchemaLoader + */ + protected $loader; + + /** + * @var \Opis\JsonSchema\Schema|null + */ + protected $sender; + + /** @var object[]|null[]|null */ + protected $shared; + + /** @var null|string[]|Schema[]|object[] */ + protected $slots; + + /** + * @var int + */ + protected $sharedIndex = -1; + + /** + * @var int + */ + protected $pathIndex = 0; + + /** + * @var int + */ + protected $maxErrors = 1; + + /** + * @param $data + * @param SchemaLoader $loader + * @param null|self $parent + * @param Schema|null $parent + * @param array $globals + * @param null|string[]|Schema[] $slots + * @param int $max_errors + */ + public function __construct( + $data, + SchemaLoader $loader, + $parent = null, + $sender = null, + array $globals = array(), + $slots = null, + int $max_errors = 1 + ) { + $this->sender = $sender; + $this->rootData = $data; + $this->loader = $loader; + $this->parent = $parent; + $this->globals = $globals; + $this->slots = null; + $this->maxErrors = $max_errors; + $this->currentData = array( + array( $data, false ), + ); + + if ( $slots ) { + $this->setSlots( $slots ); + } + } + + /** + * @param $data + * @param Schema|null $sender + * @param array|null $globals + * @param array|null $slots + * @param int|null $max_errors + * @return self + */ + public function newInstance( + $data, + $sender, + $globals = null, + $slots = null, + $max_errors = null + ): self { + return new self( + $data, + $this->loader, + $this, + $sender, + $globals ?? $this->globals, + $slots ?? $this->slots, + $max_errors ?? $this->maxErrors + ); + } + + /** + * @param \Opis\JsonSchema\Schema $sender + * @param \Opis\JsonSchema\Variables|null $mapper + * @param \Opis\JsonSchema\Variables|null $globals + * @param mixed[]|null $slots + * @param int|null $maxErrors + */ + public function create( + $sender, + $mapper = null, + $globals = null, + $slots = null, + $maxErrors = null + ): self { + if ( $globals ) { + $globals = $globals->resolve( $this->rootData(), $this->currentDataPath() ); + if ( ! is_array( $globals ) ) { + $globals = (array) $globals; + } + $globals += $this->globals; + } else { + $globals = $this->globals; + } + + if ( $mapper ) { + $data = $mapper->resolve( $this->rootData(), $this->currentDataPath() ); + } else { + $data = $this->currentData(); + } + + return new self( + $data, + $this->loader, + $this, + $sender, + $globals, + $slots ?? $this->slots, + $maxErrors ?? $this->maxErrors + ); + } + + public function sender() { + return $this->sender; + } + + /** + * @return self|null + */ + public function parent() { + return $this->parent; + } + + /** + * @return SchemaLoader + */ + public function loader(): SchemaLoader { + return $this->loader; + } + + /** + * @return mixed + */ + public function rootData() { + return $this->rootData; + } + + /** + * @return mixed + */ + public function currentData() { + return $this->currentData[ $this->pathIndex ][0]; + } + + /** + * @param $value + */ + public function setCurrentData( $value ) { + $this->currentData[ $this->pathIndex ][0] = $value; + $this->currentData[ $this->pathIndex ][1] = false; + } + + /** + * @return string|null + */ + public function currentDataType() { + $type = $this->currentData[ $this->pathIndex ][1]; + if ( $type === false ) { + $type = Helper::getJsonType( $this->currentData[ $this->pathIndex ][0] ); + $this->currentData[ $this->pathIndex ][1] = $type; + } + + return $type; + } + + public function fullDataPath(): array { + if ( $this->fullPath === null ) { + if ( $this->parent === null ) { + return $this->currentDataPath; + } + $this->fullPath = array_merge( $this->parent->fullDataPath(), $this->currentDataPath ); + } + + return $this->fullPath; + } + + /** + * @return int[]|string[] + */ + public function currentDataPath(): array { + return $this->currentDataPath; + } + + /** + * @param string|int $key + * @return $this + */ + public function pushDataPath( $key ): self { + $this->currentDataPath[] = $key; + if ( $this->fullPath !== null ) { + $this->fullPath[] = $key; + } + + $data = $this->currentData[ $this->pathIndex ][0]; + + if ( is_array( $data ) ) { + $data = $data[ $key ] ?? null; + } elseif ( is_object( $data ) ) { + $data = $data->{$key} ?? null; + } else { + $data = null; + } + + $this->currentData[] = array( $data, false ); + ++$this->pathIndex; + + return $this; + } + + /** + * @return $this + */ + public function popDataPath(): self { + if ( $this->pathIndex < 1 ) { + return $this; + } + + if ( $this->fullPath !== null ) { + array_pop( $this->fullPath ); + } + array_pop( $this->currentDataPath ); + array_pop( $this->currentData ); + --$this->pathIndex; + + return $this; + } + + /** + * @return array + */ + public function globals(): array { + return $this->globals; + } + + /** + * @param array $globals + * @param bool $overwrite + * @return $this + */ + public function setGlobals( $globals, $overwrite = false ): self { + if ( $overwrite ) { + $this->globals = $globals; + } elseif ( $globals ) { + $this->globals = $globals + $this->globals; + } + + return $this; + } + + /** + * @return object[]|Schema[]|string[]|null + */ + public function slots() { + return $this->slots; + } + + /** + * @param array|null $slots + * @return $this + */ + public function setSlots( $slots ): self { + if ( $slots ) { + $list = array(); + + foreach ( $slots as $name => $value ) { + if ( is_bool( $value ) ) { + $value = $this->loader->loadBooleanSchema( $value ); + } elseif ( is_object( $value ) ) { + if ( $value instanceof Schema ) { + $list[ $name ] = $value; + continue; + } + $value = $this->loader->loadObjectSchema( $value ); + } elseif ( is_string( $value ) ) { + if ( isset( $this->slots[ $value ] ) ) { + $value = $this->slots[ $value ]; + } elseif ( $this->parent ) { + $value = $this->parent->slot( $value ); + } + } + + if ( $value instanceof Schema ) { + $list[ $name ] = $value; + } + } + + $this->slots = $list; + } else { + $this->slots = null; + } + + return $this; + } + + /** + * @param string $name + * @return Schema|null + */ + public function slot( $name ) { + return $this->slots[ $name ] ?? null; + } + + /** + * @return int + */ + public function maxErrors(): int { + return $this->maxErrors; + } + + /** + * @param int $max + * @return $this + */ + public function setMaxErrors( $max ): self { + $this->maxErrors = $max; + + return $this; + } + + /* --------------------- */ + + /** + * @param Schema $schema + * @return $this + */ + public function pushSharedObject( $schema ): self { + $unevaluated = ! in_array( $schema->info()->draft(), array( '06', '07' ) ); + if ( $unevaluated && ( $parser = $this->loader->parser() ) && ! $parser->option( 'allowUnevaluated', true ) ) { + $unevaluated = false; + } + + $this->shared[] = array( + 'schema' => $schema, + 'unevaluated' => $unevaluated, + 'object' => null, + ); + ++$this->sharedIndex; + + return $this; + } + + /** + * @return $this + */ + public function popSharedObject(): self { + if ( $this->sharedIndex < 0 ) { + return $this; + } + + $data = array_pop( $this->shared ); + --$this->sharedIndex; + + if ( $data['unevaluated'] && $data['object'] ) { + if ( $this->sharedIndex >= 0 ) { + $this->mergeUnevaluated( $data['object'] ); + } elseif ( $this->parent && $this->parent->sharedIndex >= 0 ) { + $this->parent->mergeUnevaluated( $data['object'] ); + } + } + + return $this; + } + + /** + * @return object|null + */ + public function sharedObject() { + if ( $this->sharedIndex < 0 ) { + return null; + } + + return $this->shared[ $this->sharedIndex ]['object'] = $this->shared[ $this->sharedIndex ]['object'] ?? (object) array(); + } + + public function schema() { + return $this->shared[ $this->sharedIndex ]['schema'] ?? null; + } + + public function trackUnevaluated(): bool { + return $this->shared[ $this->sharedIndex ]['unevaluated'] ?? false; + } + + /** + * @param object $obj + */ + protected function mergeUnevaluated( $obj ) { + switch ( $this->currentDataType() ) { + case 'object': + if ( isset( $obj->evaluatedProperties ) ) { + $this->addEvaluatedProperties( $obj->evaluatedProperties ); + } + break; + case 'array': + if ( isset( $obj->evaluatedItems ) ) { + $this->addEvaluatedItems( $obj->evaluatedItems ); + } + break; + } + } + + /* ----------------*/ + + public function getStringLength() { + if ( $this->currentDataType() !== 'string' ) { + return null; + } + + $shared = $this->sharedObject(); + + if ( ! isset( $shared->stringLength ) ) { + $shared->stringLength = UnicodeString::from( $this->currentData() )->length(); + } + + return $shared->stringLength; + } + + /** + * @param string $content + */ + public function setDecodedContent( $content ): bool { + if ( $this->currentDataType() !== 'string' ) { + return false; + } + + $this->sharedObject()->decodedContent = $content; + + return true; + } + + public function getDecodedContent() { + if ( $this->currentDataType() !== 'string' ) { + return null; + } + return $this->sharedObject()->decodedContent ?? $this->currentData(); + } + + public function getObjectProperties() { + if ( $this->currentDataType() !== 'object' ) { + return null; + } + + return $this->sharedObject()->objectProperties = $this->sharedObject()->objectProperties ?? array_keys( get_object_vars( $this->currentData() ) ); + } + + /** + * @param mixed[]|null $properties + */ + public function addCheckedProperties( $properties ): bool { + if ( ! $properties ) { + return false; + } + + $shared = $this->sharedObject(); + + if ( ! isset( $shared->checkedProperties ) ) { + $shared->checkedProperties = $properties; + } else { + $shared->checkedProperties = array_values( array_unique( array_merge( $shared->checkedProperties, $properties ) ) ); + } + + return true; + } + + public function getCheckedProperties() { + return $this->sharedObject()->checkedProperties ?? null; + } + + public function getUncheckedProperties() { + $properties = $this->getObjectProperties(); + if ( ! $properties ) { + return $properties; + } + + $checked = $this->sharedObject()->checkedProperties ?? null; + if ( ! $checked ) { + return $properties; + } + + return array_values( array_diff( $properties, $checked ) ); + } + + public function markAllAsEvaluatedProperties(): bool { + return $this->addEvaluatedProperties( $this->getObjectProperties() ); + } + + /** + * @param mixed[]|null $properties + */ + public function addEvaluatedProperties( $properties ): bool { + if ( ! $properties || ! ( $this->currentDataType() === 'object' ) || ! $this->trackUnevaluated() ) { + return false; + } + + $shared = $this->sharedObject(); + + if ( ! isset( $shared->evaluatedProperties ) ) { + $shared->evaluatedProperties = $properties; + } else { + $shared->evaluatedProperties = array_values( array_unique( array_merge( $shared->evaluatedProperties, $properties ) ) ); + } + + return true; + } + + public function getEvaluatedProperties() { + return $this->sharedObject()->evaluatedProperties ?? null; + } + + public function getUnevaluatedProperties() { + $properties = $this->getObjectProperties(); + if ( ! $properties ) { + return $properties; + } + + $evaluated = $this->sharedObject()->evaluatedProperties ?? null; + if ( ! $evaluated ) { + return $properties; + } + + return array_values( array_diff( $properties, $evaluated ) ); + } + + public function markAllAsEvaluatedItems(): bool { + return $this->addEvaluatedItems( range( 0, count( $this->currentData() ) ) ); + } + + /** + * @param int $count + */ + public function markCountAsEvaluatedItems( $count ): bool { + if ( ! $count ) { + return false; + } + + return $this->addEvaluatedItems( range( 0, $count ) ); + } + + /** + * @param mixed[]|null $items + */ + public function addEvaluatedItems( $items ): bool { + if ( ! $items || ! ( $this->currentDataType() === 'array' ) || ! $this->trackUnevaluated() ) { + return false; + } + + $shared = $this->sharedObject(); + + if ( ! isset( $shared->evaluatedItems ) ) { + $shared->evaluatedItems = $items; + } else { + $shared->evaluatedItems = array_values( array_unique( array_merge( $shared->evaluatedItems, $items ), SORT_NUMERIC ) ); + } + + return true; + } + + public function getEvaluatedItems() { + return $this->sharedObject()->evaluatedItems ?? null; + } + + public function getUnevaluatedItems() { + if ( $this->currentDataType() !== 'array' ) { + return null; + } + + $items = array_keys( $this->currentData() ); + if ( ! $items ) { + return $items; + } + + $evaluated = $this->sharedObject()->evaluatedItems ?? null; + if ( ! $evaluated ) { + return $items; + } + + return array_values( array_diff( $items, $evaluated ) ); + } + + /** + * @param \Opis\JsonSchema\Schema $schema + * @param int|null $maxErrors + * @param bool $reset_on_error_only + * @param \ArrayObject|null $array + */ + public function validateSchemaWithoutEvaluated( + $schema, + $maxErrors = null, + $reset_on_error_only = false, + $array = null + ) { + $currentMaxErrors = $this->maxErrors; + + $this->maxErrors = $maxErrors ?? $currentMaxErrors; + + if ( $this->trackUnevaluated() ) { + $shared = $this->sharedObject(); + + $props = $shared->evaluatedProperties ?? null; + $items = $shared->evaluatedItems ?? null; + + $error = $schema->validate( $this ); + + if ( $array ) { + $value = null; + + if ( $shared->evaluatedProperties ?? null ) { + if ( $props ) { + if ( $diff = array_diff( $shared->evaluatedProperties, $props ) ) { + $value['properties'] = $diff; + } + } else { + $value['properties'] = $shared->evaluatedProperties; + } + } + + if ( $shared->evaluatedItems ?? null ) { + if ( $items ) { + if ( $diff = array_diff( $shared->evaluatedItems, $items ) ) { + $value['items'] = $diff; + } + } else { + $value['items'] = $shared->evaluatedItems; + } + } + + if ( $value ) { + $array[] = $value; + } + } + + if ( $reset_on_error_only ) { + if ( $error ) { + $shared->evaluatedProperties = $props; + $shared->evaluatedItems = $items; + } + } else { + $shared->evaluatedProperties = $props; + $shared->evaluatedItems = $items; + } + } else { + $error = $schema->validate( $this ); + } + + $this->maxErrors = $currentMaxErrors; + + return $error; + } } diff --git a/src/opis/json-schema/src/ValidationResult.php b/src/opis/json-schema/src/ValidationResult.php index 2c687ac6..33f0f98f 100644 --- a/src/opis/json-schema/src/ValidationResult.php +++ b/src/opis/json-schema/src/ValidationResult.php @@ -1,5 +1,6 @@ error = $error; - } - - public function error() - { - return $this->error; - } - - public function isValid(): bool - { - return $this->error === null; - } - - public function hasError(): bool - { - return $this->error !== null; - } - - public function __toString(): string - { - if ($this->error) { - return $this->error->message(); - } - return ''; - } +class ValidationResult { + + /** + * @var \Opis\JsonSchema\Errors\ValidationError|null + */ + protected $error; + + public function __construct( $error ) { + $this->error = $error; + } + + public function error() { + return $this->error; + } + + public function isValid(): bool { + return $this->error === null; + } + + public function hasError(): bool { + return $this->error !== null; + } + + public function __toString(): string { + if ( $this->error ) { + return $this->error->message(); + } + return ''; + } } diff --git a/src/opis/json-schema/src/Validator.php b/src/opis/json-schema/src/Validator.php index 316179ab..ff71cac3 100644 --- a/src/opis/json-schema/src/Validator.php +++ b/src/opis/json-schema/src/Validator.php @@ -1,5 +1,6 @@ loader = $loader ?? new SchemaLoader(new SchemaParser(), new SchemaResolver(), true); - $this->maxErrors = $max_errors; - } - - /** - * @param $data - * @param bool|string|Uri|Schema|object $schema - * @param array|null $globals - * @param array|null $slots - * @return ValidationResult - */ - public function validate($data, $schema, $globals = null, $slots = null): ValidationResult - { - if (is_string($schema)) { - if ($uri = Uri::parse($schema, true)) { - $schema = $uri; - } else { - $schema = json_decode($schema, false); - } - } - - $error = null; - if (is_bool($schema)) { - $error = $this->dataValidation($data, $schema, $globals, $slots); - } elseif (is_object($schema)) { - if ($schema instanceof Uri) { - $error = $this->uriValidation($data, $schema, $globals, $slots); - } elseif ($schema instanceof Schema) { - $error = $this->schemaValidation($data, $schema, $globals, $slots); - } else { - $error = $this->dataValidation($data, $schema, $globals, $slots); - } - } else { - throw new InvalidArgumentException("Invalid schema"); - } - - return new ValidationResult($error); - } - - /** - * @param $data - * @param Uri|string $uri - * @param array|null $globals - * @param array|null $slots - * @return null|ValidationError - */ - public function uriValidation($data, $uri, $globals = null, $slots = null) - { - if (is_string($uri)) { - $uri = Uri::parse($uri, true); - } - - if (!($uri instanceof Uri)) { - throw new InvalidArgumentException("Invalid uri"); - } - - if ($uri->fragment() === null) { - $uri = Uri::merge($uri, null, true); - } - - $schema = $this->loader->loadSchemaById($uri); - - if ($schema === null) { - throw new RuntimeException("Schema not found: $uri"); - } - - return $this->schemaValidation($data, $schema, $globals, $slots); - } - - /** - * @param $data - * @param string|object|bool $schema - * @param array|null $globals - * @param array|null $slots - * @param string|null $id - * @param string|null $draft - * @return ValidationError|null - */ - public function dataValidation( - $data, - $schema, - $globals = null, - $slots = null, - $id = null, - $draft = null - ) - { - if (is_string($schema)) { - $schema = json_decode($schema, false); - } - - if ($schema === true) { - return null; - } - - if ($schema === false) { - $schema = $this->loader->loadBooleanSchema(false, $id, $draft); - } else { - if (!is_object($schema)) { - throw new InvalidArgumentException("Invalid schema"); - } - - $schema = $this->loader->loadObjectSchema($schema, $id, $draft); - } - - return $this->schemaValidation($data, $schema, $globals, $slots); - } - - /** - * @param $data - * @param Schema $schema - * @param array|null $globals - * @param array|null $slots - * @return null|ValidationError - */ - public function schemaValidation( - $data, - $schema, - $globals = null, - $slots = null - ) - { - return $schema->validate($this->createContext($data, $globals, $slots)); - } - - /** - * @param $data - * @param array|null $globals - * @param array|null $slots - * @return ValidationContext - */ - public function createContext($data, $globals = null, $slots = null): ValidationContext - { - if ($slots) { - $slots = $this->parseSlots($slots); - } - - return new ValidationContext($data, $this->loader, null, null, $globals ?? [], $slots, $this->maxErrors); - } - - /** - * @return SchemaParser - */ - public function parser(): SchemaParser - { - return $this->loader->parser(); - } - - /** - * @param SchemaParser $parser - * @return Validator - */ - public function setParser($parser): self - { - $this->loader->setParser($parser); - - return $this; - } - - /** - * @return SchemaResolver|null - */ - public function resolver() - { - return $this->loader->resolver(); - } - - /** - * @param SchemaResolver|null $resolver - * @return Validator - */ - public function setResolver($resolver): self - { - $this->loader->setResolver($resolver); - - return $this; - } - - /** - * @return SchemaLoader - */ - public function loader(): SchemaLoader - { - return $this->loader; - } - - /** - * @param SchemaLoader $loader - * @return Validator - */ - public function setLoader($loader): self - { - $this->loader = $loader; - - return $this; - } - - /** - * @return int - */ - public function getMaxErrors(): int - { - return $this->maxErrors; - } - - /** - * @param int $max_errors - * @return Validator - */ - public function setMaxErrors($max_errors): self - { - $this->maxErrors = $max_errors; - - return $this; - } - - /** - * @param array $slots - * @return array - */ - protected function parseSlots($slots): array - { - foreach ($slots as $name => &$value) { - if (!is_string($name)) { - unset($slots[$name]); - continue; - } - - if (is_string($value)) { - $value = Uri::parse($value, true); - } - - if ($value instanceof Uri) { - $value = $this->loader->loadSchemaById($value); - } elseif (is_bool($value)) { - $value = $this->loader->loadBooleanSchema($value); - } - - if (!is_object($value)) { - unset($slots[$name]); - } - - unset($value); - } - - return $slots; - } +class Validator { + + /** + * @var \Opis\JsonSchema\SchemaLoader + */ + protected $loader; + /** + * @var int + */ + protected $maxErrors = 1; + + /** + * @param SchemaLoader|null $loader + * @param int $max_errors + */ + public function __construct( $loader = null, int $max_errors = 1 ) { + $this->loader = $loader ?? new SchemaLoader( new SchemaParser(), new SchemaResolver(), true ); + $this->maxErrors = $max_errors; + } + + /** + * @param $data + * @param bool|string|Uri|Schema|object $schema + * @param array|null $globals + * @param array|null $slots + * @return ValidationResult + */ + public function validate( $data, $schema, $globals = null, $slots = null ): ValidationResult { + if ( is_string( $schema ) ) { + if ( $uri = Uri::parse( $schema, true ) ) { + $schema = $uri; + } else { + $schema = json_decode( $schema, false ); + } + } + + $error = null; + if ( is_bool( $schema ) ) { + $error = $this->dataValidation( $data, $schema, $globals, $slots ); + } elseif ( is_object( $schema ) ) { + if ( $schema instanceof Uri ) { + $error = $this->uriValidation( $data, $schema, $globals, $slots ); + } elseif ( $schema instanceof Schema ) { + $error = $this->schemaValidation( $data, $schema, $globals, $slots ); + } else { + $error = $this->dataValidation( $data, $schema, $globals, $slots ); + } + } else { + throw new InvalidArgumentException( 'Invalid schema' ); + } + + return new ValidationResult( $error ); + } + + /** + * @param $data + * @param Uri|string $uri + * @param array|null $globals + * @param array|null $slots + * @return null|ValidationError + */ + public function uriValidation( $data, $uri, $globals = null, $slots = null ) { + if ( is_string( $uri ) ) { + $uri = Uri::parse( $uri, true ); + } + + if ( ! ( $uri instanceof Uri ) ) { + throw new InvalidArgumentException( 'Invalid uri' ); + } + + if ( $uri->fragment() === null ) { + $uri = Uri::merge( $uri, null, true ); + } + + $schema = $this->loader->loadSchemaById( $uri ); + + if ( $schema === null ) { + throw new RuntimeException( "Schema not found: $uri" ); + } + + return $this->schemaValidation( $data, $schema, $globals, $slots ); + } + + /** + * @param $data + * @param string|object|bool $schema + * @param array|null $globals + * @param array|null $slots + * @param string|null $id + * @param string|null $draft + * @return ValidationError|null + */ + public function dataValidation( + $data, + $schema, + $globals = null, + $slots = null, + $id = null, + $draft = null + ) { + if ( is_string( $schema ) ) { + $schema = json_decode( $schema, false ); + } + + if ( $schema === true ) { + return null; + } + + if ( $schema === false ) { + $schema = $this->loader->loadBooleanSchema( false, $id, $draft ); + } else { + if ( ! is_object( $schema ) ) { + throw new InvalidArgumentException( 'Invalid schema' ); + } + + $schema = $this->loader->loadObjectSchema( $schema, $id, $draft ); + } + + return $this->schemaValidation( $data, $schema, $globals, $slots ); + } + + /** + * @param $data + * @param Schema $schema + * @param array|null $globals + * @param array|null $slots + * @return null|ValidationError + */ + public function schemaValidation( + $data, + $schema, + $globals = null, + $slots = null + ) { + return $schema->validate( $this->createContext( $data, $globals, $slots ) ); + } + + /** + * @param $data + * @param array|null $globals + * @param array|null $slots + * @return ValidationContext + */ + public function createContext( $data, $globals = null, $slots = null ): ValidationContext { + if ( $slots ) { + $slots = $this->parseSlots( $slots ); + } + + return new ValidationContext( $data, $this->loader, null, null, $globals ?? array(), $slots, $this->maxErrors ); + } + + /** + * @return SchemaParser + */ + public function parser(): SchemaParser { + return $this->loader->parser(); + } + + /** + * @param SchemaParser $parser + * @return Validator + */ + public function setParser( $parser ): self { + $this->loader->setParser( $parser ); + + return $this; + } + + /** + * @return SchemaResolver|null + */ + public function resolver() { + return $this->loader->resolver(); + } + + /** + * @param SchemaResolver|null $resolver + * @return Validator + */ + public function setResolver( $resolver ): self { + $this->loader->setResolver( $resolver ); + + return $this; + } + + /** + * @return SchemaLoader + */ + public function loader(): SchemaLoader { + return $this->loader; + } + + /** + * @param SchemaLoader $loader + * @return Validator + */ + public function setLoader( $loader ): self { + $this->loader = $loader; + + return $this; + } + + /** + * @return int + */ + public function getMaxErrors(): int { + return $this->maxErrors; + } + + /** + * @param int $max_errors + * @return Validator + */ + public function setMaxErrors( $max_errors ): self { + $this->maxErrors = $max_errors; + + return $this; + } + + /** + * @param array $slots + * @return array + */ + protected function parseSlots( $slots ): array { + foreach ( $slots as $name => &$value ) { + if ( ! is_string( $name ) ) { + unset( $slots[ $name ] ); + continue; + } + + if ( is_string( $value ) ) { + $value = Uri::parse( $value, true ); + } + + if ( $value instanceof Uri ) { + $value = $this->loader->loadSchemaById( $value ); + } elseif ( is_bool( $value ) ) { + $value = $this->loader->loadBooleanSchema( $value ); + } + + if ( ! is_object( $value ) ) { + unset( $slots[ $name ] ); + } + + unset( $value ); + } + + return $slots; + } } diff --git a/src/opis/json-schema/src/Variables.php b/src/opis/json-schema/src/Variables.php index 9837173e..23dbbc5b 100644 --- a/src/opis/json-schema/src/Variables.php +++ b/src/opis/json-schema/src/Variables.php @@ -1,5 +1,6 @@ pointer = $pointer; - $this->each = $each; - $this->hasDefault = func_num_args() === 3; - $this->defaultValue = $default; - } - - /** - * @return JsonPointer - */ - public function pointer(): JsonPointer - { - return $this->pointer; - } - - /** - * @return null|Variables - */ - public function each() - { - return $this->each; - } - - /** - * @return bool - */ - public function hasDefaultValue(): bool - { - return $this->hasDefault; - } - - /** - * @return mixed|null - */ - public function defaultValue() - { - return $this->defaultValue; - } - - /** - * @inheritDoc - * @param mixed[] $path - */ - public function resolve($data, $path = []) - { - $resolved = $this->pointer->data($data, $path, $this); - if ($resolved === $this) { - return $this->defaultValue; - } - - if ($this->each && (is_array($resolved) || is_object($resolved))) { - $path = $this->pointer->absolutePath($path); - foreach ($resolved as $key => &$value) { - $path[] = $key; - $value = $this->each->resolve($data, $path); - array_pop($path); - unset($value); - } - } - - return $resolved; - } +final class RefVariablesContainer implements Variables { + + + /** + * @var \Opis\JsonSchema\JsonPointer + */ + private $pointer; + + /** + * @var \Opis\JsonSchema\Variables|null + */ + private $each; + + /** + * @var bool + */ + private $hasDefault; + + /** @var mixed */ + private $defaultValue; + + /** + * @param JsonPointer $pointer + * @param Variables|null $each + * @param mixed $default + */ + public function __construct( JsonPointer $pointer, $each = null, $default = null ) { + $this->pointer = $pointer; + $this->each = $each; + $this->hasDefault = func_num_args() === 3; + $this->defaultValue = $default; + } + + /** + * @return JsonPointer + */ + public function pointer(): JsonPointer { + return $this->pointer; + } + + /** + * @return null|Variables + */ + public function each() { + return $this->each; + } + + /** + * @return bool + */ + public function hasDefaultValue(): bool { + return $this->hasDefault; + } + + /** + * @return mixed|null + */ + public function defaultValue() { + return $this->defaultValue; + } + + /** + * @inheritDoc + * @param mixed[] $path + */ + public function resolve( $data, $path = array() ) { + $resolved = $this->pointer->data( $data, $path, $this ); + if ( $resolved === $this ) { + return $this->defaultValue; + } + + if ( $this->each && ( is_array( $resolved ) || is_object( $resolved ) ) ) { + $path = $this->pointer->absolutePath( $path ); + foreach ( $resolved as $key => &$value ) { + $path[] = $key; + $value = $this->each->resolve( $data, $path ); + array_pop( $path ); + unset( $value ); + } + } + + return $resolved; + } } diff --git a/src/opis/json-schema/src/Variables/VariablesContainer.php b/src/opis/json-schema/src/Variables/VariablesContainer.php index 0a627d3c..70a406ca 100644 --- a/src/opis/json-schema/src/Variables/VariablesContainer.php +++ b/src/opis/json-schema/src/Variables/VariablesContainer.php @@ -1,5 +1,6 @@ keys = [ - 'ref' => $ref_key, - 'each' => $each_key, - 'default' => $default_key, - ]; - - if ($lazy) { - $this->vars = $data; - } else { - $this->parsed = true; - $this->vars = $this->parse($data); - } - } - - /** - * @inheritdoc - * @param mixed[] $path - */ - public function resolve($data, $path = []) - { - if (!$this->parsed) { - $this->vars = $this->parse($this->vars); - $this->parsed = true; - } - - if (!$this->hasRefs) { - // Nothing to resolve - return $this->vars; - } - - return $this->deepClone($this->vars, $data, $path); - } - - /** - * @param $vars - * @param $data - * @param string[]|int[] $path - * @return array|object|mixed - */ - private function deepClone($vars, $data, array $path) - { - $toObject = false; - if (is_object($vars)) { - if ($vars instanceof Variables) { - return $vars->resolve($data, $path); - } - $vars = get_object_vars($vars); - $toObject = true; - } elseif (!is_array($vars)) { - return $vars; - } - - foreach ($vars as &$var) { - if ($var !== null && !is_scalar($var)) { - $var = $this->deepClone($var, $data, $path); - } - unset($var); - } - - return $toObject ? (object)$vars : $vars; - } - - /** - * @param mixed $data - * @return mixed - */ - private function parse($data) - { - if (is_array($data)) { - return array_map([$this, 'parse'], $data); - } - - if (!is_object($data)) { - return $data; - } - - if ($vars = $this->parseRef($data)) { - $this->hasRefs = true; - - return $vars; - } - - return (object)array_map([$this, 'parse'], get_object_vars($data)); - } - - /** - * @param object $data - * @return null|Variables - */ - private function parseRef($data) - { - if (!property_exists($data, $this->keys['ref'])) { - return null; - } - - $ref = $data->{$this->keys['ref']}; - if (!is_string($ref)) { - return null; - } - - $pointer = JsonPointer::parse($ref); - if ($pointer === null) { - return null; - } - - $each = null; - if (property_exists($data, $this->keys['each']) && is_object($data->{$this->keys['each']})) { - $each = new self($data->{$this->keys['each']}, !$this->parsed, $this->keys['ref'], $this->keys['each'], $this->keys['default']); - } - - if (property_exists($data, $this->keys['default'])) { - return new RefVariablesContainer($pointer, $each, $data->{$this->keys['default']}); - } - - return new RefVariablesContainer($pointer, $each); - } +final class VariablesContainer implements Variables { + + + /** + * @var array|object|Variables|null + */ + private $vars; + + /** + * @var bool + */ + private $parsed = false; + + /** + * @var bool + */ + private $hasRefs = false; + + /** @var string[] */ + private $keys; + + /** + * @param array|object $data + * @param bool $lazy + * @param string $ref_key + * @param string $each_key + * @param string $default_key + */ + public function __construct( $data, bool $lazy = true, string $ref_key = '$ref', string $each_key = '$each', string $default_key = 'default' ) { + $this->keys = array( + 'ref' => $ref_key, + 'each' => $each_key, + 'default' => $default_key, + ); + + if ( $lazy ) { + $this->vars = $data; + } else { + $this->parsed = true; + $this->vars = $this->parse( $data ); + } + } + + /** + * @inheritdoc + * @param mixed[] $path + */ + public function resolve( $data, $path = array() ) { + if ( ! $this->parsed ) { + $this->vars = $this->parse( $this->vars ); + $this->parsed = true; + } + + if ( ! $this->hasRefs ) { + // Nothing to resolve + return $this->vars; + } + + return $this->deepClone( $this->vars, $data, $path ); + } + + /** + * @param $vars + * @param $data + * @param string[]|int[] $path + * @return array|object|mixed + */ + private function deepClone( $vars, $data, array $path ) { + $toObject = false; + if ( is_object( $vars ) ) { + if ( $vars instanceof Variables ) { + return $vars->resolve( $data, $path ); + } + $vars = get_object_vars( $vars ); + $toObject = true; + } elseif ( ! is_array( $vars ) ) { + return $vars; + } + + foreach ( $vars as &$var ) { + if ( $var !== null && ! is_scalar( $var ) ) { + $var = $this->deepClone( $var, $data, $path ); + } + unset( $var ); + } + + return $toObject ? (object) $vars : $vars; + } + + /** + * @param mixed $data + * @return mixed + */ + private function parse( $data ) { + if ( is_array( $data ) ) { + return array_map( array( $this, 'parse' ), $data ); + } + + if ( ! is_object( $data ) ) { + return $data; + } + + if ( $vars = $this->parseRef( $data ) ) { + $this->hasRefs = true; + + return $vars; + } + + return (object) array_map( array( $this, 'parse' ), get_object_vars( $data ) ); + } + + /** + * @param object $data + * @return null|Variables + */ + private function parseRef( $data ) { + if ( ! property_exists( $data, $this->keys['ref'] ) ) { + return null; + } + + $ref = $data->{$this->keys['ref']}; + if ( ! is_string( $ref ) ) { + return null; + } + + $pointer = JsonPointer::parse( $ref ); + if ( $pointer === null ) { + return null; + } + + $each = null; + if ( property_exists( $data, $this->keys['each'] ) && is_object( $data->{$this->keys['each']} ) ) { + $each = new self( $data->{$this->keys['each']}, ! $this->parsed, $this->keys['ref'], $this->keys['each'], $this->keys['default'] ); + } + + if ( property_exists( $data, $this->keys['default'] ) ) { + return new RefVariablesContainer( $pointer, $each, $data->{$this->keys['default']} ); + } + + return new RefVariablesContainer( $pointer, $each ); + } } diff --git a/src/opis/string/res/ascii.php b/src/opis/string/res/ascii.php index 000a19fc..b0a5f0a9 100644 --- a/src/opis/string/res/ascii.php +++ b/src/opis/string/res/ascii.php @@ -1,751 +1,751 @@ 0x28, -0xAA => 0x61, -0xB0 => 0x30, -0xB2 => 0x32, -0xB3 => 0x33, -0xB5 => 0x75, -0xB9 => 0x31, -0xBA => 0x6F, -0xC0 => 0x41, -0xC1 => 0x41, -0xC2 => 0x41, -0xC3 => 0x41, -0xC4 => 0x41, -0xC5 => 0x41, -0xC6 => 0x41, -0xC7 => 0x43, -0xC8 => 0x45, -0xC9 => 0x45, -0xCA => 0x45, -0xCB => 0x45, -0xCC => 0x49, -0xCD => 0x49, -0xCE => 0x49, -0xCF => 0x49, -0xD0 => 0x44, -0xD1 => 0x4E, -0xD2 => 0x4F, -0xD3 => 0x4F, -0xD4 => 0x4F, -0xD5 => 0x4F, -0xD6 => 0x4F, -0xD8 => 0x4F, -0xD9 => 0x55, -0xDA => 0x55, -0xDB => 0x55, -0xDC => 0x55, -0xDD => 0x59, -0xDE => 0x54, -0xDF => 0x73, -0xE0 => 0x61, -0xE1 => 0x61, -0xE2 => 0x61, -0xE3 => 0x61, -0xE4 => 0x61, -0xE5 => 0x61, -0xE6 => 0x61, -0xE7 => 0x63, -0xE8 => 0x65, -0xE9 => 0x65, -0xEA => 0x65, -0xEB => 0x65, -0xEC => 0x69, -0xED => 0x69, -0xEE => 0x69, -0xEF => 0x69, -0xF0 => 0x64, -0xF1 => 0x6E, -0xF2 => 0x6F, -0xF3 => 0x6F, -0xF4 => 0x6F, -0xF5 => 0x6F, -0xF6 => 0x6F, -0xF8 => 0x6F, -0xF9 => 0x75, -0xFA => 0x75, -0xFB => 0x75, -0xFC => 0x75, -0xFD => 0x79, -0xFE => 0x74, -0xFF => 0x79, -0x100 => 0x41, -0x101 => 0x61, -0x102 => 0x41, -0x103 => 0x61, -0x104 => 0x41, -0x105 => 0x61, -0x106 => 0x43, -0x107 => 0x63, -0x108 => 0x43, -0x109 => 0x63, -0x10A => 0x43, -0x10B => 0x63, -0x10C => 0x43, -0x10D => 0x63, -0x10E => 0x44, -0x10F => 0x64, -0x110 => 0x44, -0x111 => 0x64, -0x112 => 0x45, -0x113 => 0x65, -0x114 => 0x45, -0x115 => 0x65, -0x116 => 0x45, -0x117 => 0x65, -0x118 => 0x45, -0x119 => 0x65, -0x11A => 0x45, -0x11B => 0x65, -0x11C => 0x47, -0x11D => 0x67, -0x11E => 0x47, -0x11F => 0x67, -0x120 => 0x47, -0x121 => 0x67, -0x122 => 0x47, -0x123 => 0x67, -0x124 => 0x48, -0x125 => 0x68, -0x126 => 0x48, -0x127 => 0x68, -0x128 => 0x49, -0x129 => 0x69, -0x12A => 0x49, -0x12B => 0x69, -0x12C => 0x49, -0x12D => 0x69, -0x12E => 0x49, -0x12F => 0x69, -0x130 => 0x49, -0x131 => 0x69, -0x132 => 0x49, -0x133 => 0x69, -0x134 => 0x4A, -0x135 => 0x6A, -0x136 => 0x6B, -0x137 => 0x6B, -0x138 => 0x6B, -0x139 => 0x4C, -0x13A => 0x6C, -0x13B => 0x4C, -0x13C => 0x6C, -0x13D => 0x4C, -0x13E => 0x6C, -0x13F => 0x4C, -0x140 => 0x6C, -0x141 => 0x4C, -0x142 => 0x6C, -0x143 => 0x4E, -0x144 => 0x6E, -0x145 => 0x4E, -0x146 => 0x6E, -0x147 => 0x4E, -0x148 => 0x6E, -0x149 => 0x6E, -0x14A => 0x4E, -0x14B => 0x6E, -0x14C => 0x4F, -0x14D => 0x6F, -0x14E => 0x4F, -0x14F => 0x6F, -0x150 => 0x4F, -0x151 => 0x6F, -0x152 => 0x4F, -0x153 => 0x6F, -0x154 => 0x52, -0x155 => 0x72, -0x156 => 0x52, -0x157 => 0x72, -0x158 => 0x52, -0x159 => 0x72, -0x15A => 0x53, -0x15B => 0x73, -0x15C => 0x53, -0x15D => 0x73, -0x15E => 0x53, -0x15F => 0x73, -0x160 => 0x53, -0x161 => 0x73, -0x162 => 0x54, -0x163 => 0x74, -0x164 => 0x54, -0x165 => 0x74, -0x166 => 0x54, -0x167 => 0x74, -0x168 => 0x55, -0x169 => 0x75, -0x16A => 0x55, -0x16B => 0x75, -0x16C => 0x55, -0x16D => 0x75, -0x16E => 0x55, -0x16F => 0x75, -0x170 => 0x55, -0x171 => 0x75, -0x172 => 0x55, -0x173 => 0x75, -0x174 => 0x57, -0x175 => 0x77, -0x176 => 0x59, -0x177 => 0x79, -0x178 => 0x59, -0x179 => 0x5A, -0x17A => 0x7A, -0x17B => 0x5A, -0x17C => 0x7A, -0x17D => 0x5A, -0x17E => 0x7A, -0x17F => 0x73, -0x189 => 0x44, -0x18A => 0x44, -0x18B => 0x44, -0x18C => 0x64, -0x18F => 0x45, -0x192 => 0x66, -0x1A0 => 0x4F, -0x1A1 => 0x6F, -0x1AF => 0x55, -0x1B0 => 0x75, -0x1CD => 0x41, -0x1CE => 0x61, -0x1CF => 0x49, -0x1D0 => 0x69, -0x1D1 => 0x4F, -0x1D2 => 0x6F, -0x1D3 => 0x55, -0x1D4 => 0x75, -0x1D5 => 0x55, -0x1D6 => 0x75, -0x1D7 => 0x55, -0x1D8 => 0x75, -0x1D9 => 0x55, -0x1DA => 0x75, -0x1DB => 0x55, -0x1DC => 0x75, -0x1FA => 0x41, -0x1FB => 0x61, -0x1FC => 0x41, -0x1FD => 0x61, -0x1FE => 0x4F, -0x1FF => 0x6F, -0x218 => 0x53, -0x219 => 0x73, -0x21A => 0x54, -0x21B => 0x74, -0x221 => 0x64, -0x256 => 0x64, -0x257 => 0x64, -0x259 => 0x65, -0x386 => 0x41, -0x388 => 0x45, -0x389 => 0x48, -0x38A => 0x49, -0x38C => 0x4F, -0x38E => 0x59, -0x38F => 0x57, -0x390 => 0x69, -0x391 => 0x41, -0x392 => 0x42, -0x393 => 0x47, -0x394 => 0x44, -0x395 => 0x45, -0x396 => 0x5A, -0x397 => 0x48, -0x398 => 0x4F, -0x399 => 0x49, -0x39A => 0x4B, -0x39B => 0x4C, -0x39C => 0x4D, -0x39D => 0x4E, -0x39E => 0x58, -0x39F => 0x4F, -0x3A0 => 0x50, -0x3A1 => 0x52, -0x3A3 => 0x53, -0x3A4 => 0x54, -0x3A5 => 0x59, -0x3A6 => 0x46, -0x3A7 => 0x58, -0x3A8 => 0x50, -0x3A9 => 0x57, -0x3AA => 0x49, -0x3AB => 0x59, -0x3AC => 0x61, -0x3AD => 0x65, -0x3AE => 0x68, -0x3AF => 0x69, -0x3B0 => 0x79, -0x3B1 => 0x61, -0x3B2 => 0x62, -0x3B3 => 0x67, -0x3B4 => 0x64, -0x3B5 => 0x65, -0x3B6 => 0x7A, -0x3B7 => 0x68, -0x3B8 => 0x6F, -0x3B9 => 0x69, -0x3BA => 0x6B, -0x3BB => 0x6C, -0x3BC => 0x6D, -0x3BD => 0x6E, -0x3BE => 0x78, -0x3BF => 0x6F, -0x3C0 => 0x70, -0x3C1 => 0x72, -0x3C2 => 0x73, -0x3C3 => 0x73, -0x3C4 => 0x74, -0x3C5 => 0x79, -0x3C6 => 0x66, -0x3C7 => 0x78, -0x3C8 => 0x70, -0x3C9 => 0x77, -0x3CA => 0x69, -0x3CB => 0x79, -0x3CC => 0x6F, -0x3CD => 0x79, -0x3CE => 0x77, -0x3D0 => 0x76, -0x3D1 => 0x74, -0x3D2 => 0x49, -0x401 => 0x45, -0x402 => 0x44, -0x404 => 0x45, -0x406 => 0x49, -0x407 => 0x49, -0x408 => 0x6A, -0x409 => 0x4C, -0x40A => 0x4E, -0x40F => 0x44, -0x410 => 0x41, -0x411 => 0x42, -0x412 => 0x56, -0x413 => 0x47, -0x414 => 0x44, -0x415 => 0x45, -0x416 => 0x5A, -0x417 => 0x5A, -0x418 => 0x49, -0x419 => 0x59, -0x41A => 0x4B, -0x41B => 0x4C, -0x41C => 0x4D, -0x41D => 0x4E, -0x41E => 0x4F, -0x41F => 0x50, -0x420 => 0x52, -0x421 => 0x53, -0x422 => 0x54, -0x423 => 0x55, -0x424 => 0x46, -0x425 => 0x4B, -0x426 => 0x54, -0x427 => 0x43, -0x428 => 0x53, -0x429 => 0x53, -0x42A => 0x62, -0x42B => 0x59, -0x42C => 0x62, -0x42D => 0x45, -0x42E => 0x59, -0x42F => 0x59, -0x430 => 0x61, -0x431 => 0x62, -0x432 => 0x76, -0x433 => 0x67, -0x434 => 0x64, -0x435 => 0x65, -0x436 => 0x7A, -0x437 => 0x7A, -0x438 => 0x69, -0x439 => 0x79, -0x43A => 0x6B, -0x43B => 0x6C, -0x43C => 0x6D, -0x43D => 0x6E, -0x43E => 0x6F, -0x43F => 0x70, -0x440 => 0x72, -0x441 => 0x73, -0x442 => 0x74, -0x443 => 0x75, -0x444 => 0x66, -0x445 => 0x6B, -0x446 => 0x74, -0x447 => 0x63, -0x448 => 0x73, -0x449 => 0x73, -0x44B => 0x79, -0x44D => 0x65, -0x44E => 0x79, -0x44F => 0x79, -0x451 => 0x65, -0x452 => 0x64, -0x454 => 0x65, -0x456 => 0x69, -0x457 => 0x69, -0x458 => 0x6A, -0x459 => 0x6C, -0x45A => 0x6E, -0x45F => 0x64, -0x490 => 0x47, -0x491 => 0x67, -0x4E8 => 0x4F, -0x622 => 0x61, -0x623 => 0x61, -0x624 => 0x6F, -0x625 => 0x65, -0x626 => 0x65, -0x627 => 0x61, -0x628 => 0x62, -0x62A => 0x74, -0x62B => 0x74, -0x62C => 0x6A, -0x62D => 0x68, -0x62E => 0x6B, -0x62F => 0x64, -0x630 => 0x74, -0x631 => 0x72, -0x632 => 0x7A, -0x633 => 0x73, -0x634 => 0x73, -0x635 => 0x73, -0x636 => 0x64, -0x637 => 0x74, -0x638 => 0x74, -0x639 => 0x61, -0x63A => 0x67, -0x641 => 0x66, -0x642 => 0x6B, -0x643 => 0x6B, -0x644 => 0x6C, -0x645 => 0x6D, -0x646 => 0x6E, -0x647 => 0x68, -0x648 => 0x6F, -0x64A => 0x79, -0x664 => 0x34, -0x665 => 0x35, -0x666 => 0x36, -0x67E => 0x70, -0x686 => 0x63, -0x698 => 0x7A, -0x6A9 => 0x6B, -0x6AF => 0x67, -0x6CC => 0x69, -0x6F0 => 0x30, -0x6F1 => 0x31, -0x6F2 => 0x32, -0x6F3 => 0x33, -0x6F4 => 0x34, -0x6F5 => 0x35, -0x6F6 => 0x36, -0x6F7 => 0x37, -0x6F8 => 0x38, -0x6F9 => 0x39, -0x905 => 0x61, -0x906 => 0x61, -0x907 => 0x69, -0x908 => 0x69, -0x909 => 0x75, -0x90A => 0x75, -0x90D => 0x65, -0x90F => 0x65, -0x910 => 0x61, -0x911 => 0x6F, -0x912 => 0x6F, -0x913 => 0x6F, -0x92C => 0x42, -0x932 => 0x4C, -0x1000 => 0x6B, -0x1002 => 0x67, -0x1005 => 0x73, -0x1007 => 0x7A, -0x1009 => 0x75, -0x100A => 0x69, -0x100B => 0x74, -0x100D => 0x64, -0x1010 => 0x74, -0x1012 => 0x64, -0x1014 => 0x6E, -0x1015 => 0x70, -0x1017 => 0x62, -0x1019 => 0x6D, -0x101A => 0x79, -0x101C => 0x6C, -0x101D => 0x77, -0x101F => 0x68, -0x1021 => 0x61, -0x1023 => 0x69, -0x1027 => 0x65, -0x102B => 0x61, -0x102C => 0x61, -0x102D => 0x6F, -0x102E => 0x69, -0x102F => 0x75, -0x1030 => 0x75, -0x1031 => 0x65, -0x1032 => 0x65, -0x103D => 0x77, -0x103E => 0x68, -0x10D0 => 0x61, -0x10D1 => 0x62, -0x10D2 => 0x67, -0x10D3 => 0x64, -0x10D4 => 0x65, -0x10D5 => 0x76, -0x10D6 => 0x7A, -0x10D7 => 0x74, -0x10D8 => 0x69, -0x10D9 => 0x6B, -0x10DA => 0x6C, -0x10DB => 0x6D, -0x10DC => 0x6E, -0x10DD => 0x6F, -0x10DE => 0x70, -0x10DF => 0x7A, -0x10E0 => 0x72, -0x10E1 => 0x73, -0x10E2 => 0x74, -0x10E3 => 0x75, -0x10E4 => 0x66, -0x10E5 => 0x6B, -0x10E6 => 0x67, -0x10E7 => 0x71, -0x10E8 => 0x73, -0x10E9 => 0x63, -0x10EA => 0x74, -0x10EB => 0x64, -0x10EC => 0x74, -0x10ED => 0x63, -0x10EE => 0x6B, -0x10EF => 0x6A, -0x10F0 => 0x68, -0x1D05 => 0x44, -0x1D06 => 0x44, -0x1D6D => 0x64, -0x1D81 => 0x64, -0x1D91 => 0x64, -0x1E9E => 0x53, -0x1EA0 => 0x41, -0x1EA1 => 0x61, -0x1EA2 => 0x41, -0x1EA3 => 0x61, -0x1EA4 => 0x41, -0x1EA5 => 0x61, -0x1EA6 => 0x41, -0x1EA7 => 0x61, -0x1EA8 => 0x41, -0x1EA9 => 0x61, -0x1EAA => 0x41, -0x1EAB => 0x61, -0x1EAC => 0x41, -0x1EAD => 0x61, -0x1EAE => 0x41, -0x1EAF => 0x61, -0x1EB0 => 0x41, -0x1EB1 => 0x61, -0x1EB2 => 0x41, -0x1EB3 => 0x61, -0x1EB4 => 0x41, -0x1EB5 => 0x61, -0x1EB6 => 0x41, -0x1EB7 => 0x61, -0x1EB8 => 0x45, -0x1EB9 => 0x65, -0x1EBA => 0x45, -0x1EBB => 0x65, -0x1EBC => 0x45, -0x1EBD => 0x65, -0x1EBE => 0x45, -0x1EBF => 0x65, -0x1EC0 => 0x45, -0x1EC1 => 0x65, -0x1EC2 => 0x45, -0x1EC3 => 0x65, -0x1EC4 => 0x45, -0x1EC5 => 0x65, -0x1EC6 => 0x45, -0x1EC7 => 0x65, -0x1EC8 => 0x49, -0x1EC9 => 0x69, -0x1ECA => 0x49, -0x1ECB => 0x69, -0x1ECC => 0x4F, -0x1ECD => 0x6F, -0x1ECE => 0x4F, -0x1ECF => 0x6F, -0x1ED0 => 0x4F, -0x1ED1 => 0x6F, -0x1ED2 => 0x4F, -0x1ED3 => 0x6F, -0x1ED4 => 0x4F, -0x1ED5 => 0x6F, -0x1ED6 => 0x4F, -0x1ED7 => 0x6F, -0x1ED8 => 0x4F, -0x1ED9 => 0x6F, -0x1EDA => 0x4F, -0x1EDB => 0x6F, -0x1EDC => 0x4F, -0x1EDD => 0x6F, -0x1EDE => 0x4F, -0x1EDF => 0x6F, -0x1EE0 => 0x4F, -0x1EE1 => 0x6F, -0x1EE2 => 0x4F, -0x1EE3 => 0x6F, -0x1EE4 => 0x55, -0x1EE5 => 0x75, -0x1EE6 => 0x55, -0x1EE7 => 0x75, -0x1EE8 => 0x55, -0x1EE9 => 0x75, -0x1EEA => 0x55, -0x1EEB => 0x75, -0x1EEC => 0x55, -0x1EED => 0x75, -0x1EEE => 0x55, -0x1EEF => 0x75, -0x1EF0 => 0x55, -0x1EF1 => 0x75, -0x1EF2 => 0x59, -0x1EF3 => 0x79, -0x1EF4 => 0x59, -0x1EF5 => 0x79, -0x1EF6 => 0x59, -0x1EF7 => 0x79, -0x1EF8 => 0x59, -0x1EF9 => 0x79, -0x1F00 => 0x61, -0x1F01 => 0x61, -0x1F02 => 0x61, -0x1F03 => 0x61, -0x1F04 => 0x61, -0x1F05 => 0x61, -0x1F06 => 0x61, -0x1F07 => 0x61, -0x1F08 => 0x41, -0x1F09 => 0x41, -0x1F0A => 0x41, -0x1F0B => 0x41, -0x1F0C => 0x41, -0x1F0D => 0x41, -0x1F0E => 0x41, -0x1F0F => 0x41, -0x1F10 => 0x65, -0x1F11 => 0x65, -0x1F12 => 0x65, -0x1F13 => 0x65, -0x1F14 => 0x65, -0x1F15 => 0x65, -0x1F18 => 0x45, -0x1F19 => 0x45, -0x1F1A => 0x45, -0x1F1B => 0x45, -0x1F1C => 0x45, -0x1F1D => 0x45, -0x1F30 => 0x69, -0x1F31 => 0x69, -0x1F32 => 0x69, -0x1F33 => 0x69, -0x1F34 => 0x69, -0x1F35 => 0x69, -0x1F36 => 0x69, -0x1F37 => 0x69, -0x1F38 => 0x49, -0x1F39 => 0x49, -0x1F3B => 0x49, -0x1F3C => 0x49, -0x1F3D => 0x49, -0x1F3E => 0x49, -0x1F3F => 0x49, -0x1F40 => 0x6F, -0x1F41 => 0x6F, -0x1F42 => 0x6F, -0x1F43 => 0x6F, -0x1F44 => 0x6F, -0x1F45 => 0x6F, -0x1F48 => 0x4F, -0x1F49 => 0x4F, -0x1F4A => 0x4F, -0x1F4B => 0x4F, -0x1F4C => 0x4F, -0x1F4D => 0x4F, -0x1F70 => 0x61, -0x1F72 => 0x65, -0x1F76 => 0x69, -0x1F78 => 0x6F, -0x1F80 => 0x61, -0x1F81 => 0x61, -0x1F82 => 0x61, -0x1F83 => 0x61, -0x1F84 => 0x61, -0x1F85 => 0x61, -0x1F86 => 0x61, -0x1F87 => 0x61, -0x1F88 => 0x41, -0x1F89 => 0x41, -0x1F8A => 0x41, -0x1F8B => 0x41, -0x1F8C => 0x41, -0x1F8D => 0x41, -0x1F8E => 0x41, -0x1F8F => 0x41, -0x1FB0 => 0x61, -0x1FB1 => 0x61, -0x1FB2 => 0x61, -0x1FB3 => 0x61, -0x1FB4 => 0x61, -0x1FB6 => 0x61, -0x1FB7 => 0x61, -0x1FB8 => 0x41, -0x1FB9 => 0x41, -0x1FBA => 0x41, -0x1FBC => 0x41, -0x1FC8 => 0x45, -0x1FD0 => 0x69, -0x1FD1 => 0x69, -0x1FD2 => 0x69, -0x1FD6 => 0x69, -0x1FD7 => 0x69, -0x1FD8 => 0x49, -0x1FD9 => 0x49, -0x1FDA => 0x49, -0x1FE8 => 0x59, -0x1FE9 => 0x59, -0x1FEA => 0x59, -0x1FF8 => 0x4F, -0x2000 => 0x20, -0x2001 => 0x20, -0x2002 => 0x20, -0x2003 => 0x20, -0x2004 => 0x20, -0x2005 => 0x20, -0x2006 => 0x20, -0x2007 => 0x20, -0x2008 => 0x20, -0x2009 => 0x20, -0x200A => 0x20, -0x202F => 0x20, -0x205F => 0x20, -0x2074 => 0x34, -0x2075 => 0x35, -0x2076 => 0x36, -0x2077 => 0x37, -0x2078 => 0x38, -0x2079 => 0x39, -0x2080 => 0x30, -0x2081 => 0x31, -0x2082 => 0x32, -0x2083 => 0x33, -0x2084 => 0x34, -0x2085 => 0x35, -0x2086 => 0x36, -0x2087 => 0x37, -0x2088 => 0x38, -0x2089 => 0x39, -0x3000 => 0x20, -]; +return array( + 0xA9 => 0x28, + 0xAA => 0x61, + 0xB0 => 0x30, + 0xB2 => 0x32, + 0xB3 => 0x33, + 0xB5 => 0x75, + 0xB9 => 0x31, + 0xBA => 0x6F, + 0xC0 => 0x41, + 0xC1 => 0x41, + 0xC2 => 0x41, + 0xC3 => 0x41, + 0xC4 => 0x41, + 0xC5 => 0x41, + 0xC6 => 0x41, + 0xC7 => 0x43, + 0xC8 => 0x45, + 0xC9 => 0x45, + 0xCA => 0x45, + 0xCB => 0x45, + 0xCC => 0x49, + 0xCD => 0x49, + 0xCE => 0x49, + 0xCF => 0x49, + 0xD0 => 0x44, + 0xD1 => 0x4E, + 0xD2 => 0x4F, + 0xD3 => 0x4F, + 0xD4 => 0x4F, + 0xD5 => 0x4F, + 0xD6 => 0x4F, + 0xD8 => 0x4F, + 0xD9 => 0x55, + 0xDA => 0x55, + 0xDB => 0x55, + 0xDC => 0x55, + 0xDD => 0x59, + 0xDE => 0x54, + 0xDF => 0x73, + 0xE0 => 0x61, + 0xE1 => 0x61, + 0xE2 => 0x61, + 0xE3 => 0x61, + 0xE4 => 0x61, + 0xE5 => 0x61, + 0xE6 => 0x61, + 0xE7 => 0x63, + 0xE8 => 0x65, + 0xE9 => 0x65, + 0xEA => 0x65, + 0xEB => 0x65, + 0xEC => 0x69, + 0xED => 0x69, + 0xEE => 0x69, + 0xEF => 0x69, + 0xF0 => 0x64, + 0xF1 => 0x6E, + 0xF2 => 0x6F, + 0xF3 => 0x6F, + 0xF4 => 0x6F, + 0xF5 => 0x6F, + 0xF6 => 0x6F, + 0xF8 => 0x6F, + 0xF9 => 0x75, + 0xFA => 0x75, + 0xFB => 0x75, + 0xFC => 0x75, + 0xFD => 0x79, + 0xFE => 0x74, + 0xFF => 0x79, + 0x100 => 0x41, + 0x101 => 0x61, + 0x102 => 0x41, + 0x103 => 0x61, + 0x104 => 0x41, + 0x105 => 0x61, + 0x106 => 0x43, + 0x107 => 0x63, + 0x108 => 0x43, + 0x109 => 0x63, + 0x10A => 0x43, + 0x10B => 0x63, + 0x10C => 0x43, + 0x10D => 0x63, + 0x10E => 0x44, + 0x10F => 0x64, + 0x110 => 0x44, + 0x111 => 0x64, + 0x112 => 0x45, + 0x113 => 0x65, + 0x114 => 0x45, + 0x115 => 0x65, + 0x116 => 0x45, + 0x117 => 0x65, + 0x118 => 0x45, + 0x119 => 0x65, + 0x11A => 0x45, + 0x11B => 0x65, + 0x11C => 0x47, + 0x11D => 0x67, + 0x11E => 0x47, + 0x11F => 0x67, + 0x120 => 0x47, + 0x121 => 0x67, + 0x122 => 0x47, + 0x123 => 0x67, + 0x124 => 0x48, + 0x125 => 0x68, + 0x126 => 0x48, + 0x127 => 0x68, + 0x128 => 0x49, + 0x129 => 0x69, + 0x12A => 0x49, + 0x12B => 0x69, + 0x12C => 0x49, + 0x12D => 0x69, + 0x12E => 0x49, + 0x12F => 0x69, + 0x130 => 0x49, + 0x131 => 0x69, + 0x132 => 0x49, + 0x133 => 0x69, + 0x134 => 0x4A, + 0x135 => 0x6A, + 0x136 => 0x6B, + 0x137 => 0x6B, + 0x138 => 0x6B, + 0x139 => 0x4C, + 0x13A => 0x6C, + 0x13B => 0x4C, + 0x13C => 0x6C, + 0x13D => 0x4C, + 0x13E => 0x6C, + 0x13F => 0x4C, + 0x140 => 0x6C, + 0x141 => 0x4C, + 0x142 => 0x6C, + 0x143 => 0x4E, + 0x144 => 0x6E, + 0x145 => 0x4E, + 0x146 => 0x6E, + 0x147 => 0x4E, + 0x148 => 0x6E, + 0x149 => 0x6E, + 0x14A => 0x4E, + 0x14B => 0x6E, + 0x14C => 0x4F, + 0x14D => 0x6F, + 0x14E => 0x4F, + 0x14F => 0x6F, + 0x150 => 0x4F, + 0x151 => 0x6F, + 0x152 => 0x4F, + 0x153 => 0x6F, + 0x154 => 0x52, + 0x155 => 0x72, + 0x156 => 0x52, + 0x157 => 0x72, + 0x158 => 0x52, + 0x159 => 0x72, + 0x15A => 0x53, + 0x15B => 0x73, + 0x15C => 0x53, + 0x15D => 0x73, + 0x15E => 0x53, + 0x15F => 0x73, + 0x160 => 0x53, + 0x161 => 0x73, + 0x162 => 0x54, + 0x163 => 0x74, + 0x164 => 0x54, + 0x165 => 0x74, + 0x166 => 0x54, + 0x167 => 0x74, + 0x168 => 0x55, + 0x169 => 0x75, + 0x16A => 0x55, + 0x16B => 0x75, + 0x16C => 0x55, + 0x16D => 0x75, + 0x16E => 0x55, + 0x16F => 0x75, + 0x170 => 0x55, + 0x171 => 0x75, + 0x172 => 0x55, + 0x173 => 0x75, + 0x174 => 0x57, + 0x175 => 0x77, + 0x176 => 0x59, + 0x177 => 0x79, + 0x178 => 0x59, + 0x179 => 0x5A, + 0x17A => 0x7A, + 0x17B => 0x5A, + 0x17C => 0x7A, + 0x17D => 0x5A, + 0x17E => 0x7A, + 0x17F => 0x73, + 0x189 => 0x44, + 0x18A => 0x44, + 0x18B => 0x44, + 0x18C => 0x64, + 0x18F => 0x45, + 0x192 => 0x66, + 0x1A0 => 0x4F, + 0x1A1 => 0x6F, + 0x1AF => 0x55, + 0x1B0 => 0x75, + 0x1CD => 0x41, + 0x1CE => 0x61, + 0x1CF => 0x49, + 0x1D0 => 0x69, + 0x1D1 => 0x4F, + 0x1D2 => 0x6F, + 0x1D3 => 0x55, + 0x1D4 => 0x75, + 0x1D5 => 0x55, + 0x1D6 => 0x75, + 0x1D7 => 0x55, + 0x1D8 => 0x75, + 0x1D9 => 0x55, + 0x1DA => 0x75, + 0x1DB => 0x55, + 0x1DC => 0x75, + 0x1FA => 0x41, + 0x1FB => 0x61, + 0x1FC => 0x41, + 0x1FD => 0x61, + 0x1FE => 0x4F, + 0x1FF => 0x6F, + 0x218 => 0x53, + 0x219 => 0x73, + 0x21A => 0x54, + 0x21B => 0x74, + 0x221 => 0x64, + 0x256 => 0x64, + 0x257 => 0x64, + 0x259 => 0x65, + 0x386 => 0x41, + 0x388 => 0x45, + 0x389 => 0x48, + 0x38A => 0x49, + 0x38C => 0x4F, + 0x38E => 0x59, + 0x38F => 0x57, + 0x390 => 0x69, + 0x391 => 0x41, + 0x392 => 0x42, + 0x393 => 0x47, + 0x394 => 0x44, + 0x395 => 0x45, + 0x396 => 0x5A, + 0x397 => 0x48, + 0x398 => 0x4F, + 0x399 => 0x49, + 0x39A => 0x4B, + 0x39B => 0x4C, + 0x39C => 0x4D, + 0x39D => 0x4E, + 0x39E => 0x58, + 0x39F => 0x4F, + 0x3A0 => 0x50, + 0x3A1 => 0x52, + 0x3A3 => 0x53, + 0x3A4 => 0x54, + 0x3A5 => 0x59, + 0x3A6 => 0x46, + 0x3A7 => 0x58, + 0x3A8 => 0x50, + 0x3A9 => 0x57, + 0x3AA => 0x49, + 0x3AB => 0x59, + 0x3AC => 0x61, + 0x3AD => 0x65, + 0x3AE => 0x68, + 0x3AF => 0x69, + 0x3B0 => 0x79, + 0x3B1 => 0x61, + 0x3B2 => 0x62, + 0x3B3 => 0x67, + 0x3B4 => 0x64, + 0x3B5 => 0x65, + 0x3B6 => 0x7A, + 0x3B7 => 0x68, + 0x3B8 => 0x6F, + 0x3B9 => 0x69, + 0x3BA => 0x6B, + 0x3BB => 0x6C, + 0x3BC => 0x6D, + 0x3BD => 0x6E, + 0x3BE => 0x78, + 0x3BF => 0x6F, + 0x3C0 => 0x70, + 0x3C1 => 0x72, + 0x3C2 => 0x73, + 0x3C3 => 0x73, + 0x3C4 => 0x74, + 0x3C5 => 0x79, + 0x3C6 => 0x66, + 0x3C7 => 0x78, + 0x3C8 => 0x70, + 0x3C9 => 0x77, + 0x3CA => 0x69, + 0x3CB => 0x79, + 0x3CC => 0x6F, + 0x3CD => 0x79, + 0x3CE => 0x77, + 0x3D0 => 0x76, + 0x3D1 => 0x74, + 0x3D2 => 0x49, + 0x401 => 0x45, + 0x402 => 0x44, + 0x404 => 0x45, + 0x406 => 0x49, + 0x407 => 0x49, + 0x408 => 0x6A, + 0x409 => 0x4C, + 0x40A => 0x4E, + 0x40F => 0x44, + 0x410 => 0x41, + 0x411 => 0x42, + 0x412 => 0x56, + 0x413 => 0x47, + 0x414 => 0x44, + 0x415 => 0x45, + 0x416 => 0x5A, + 0x417 => 0x5A, + 0x418 => 0x49, + 0x419 => 0x59, + 0x41A => 0x4B, + 0x41B => 0x4C, + 0x41C => 0x4D, + 0x41D => 0x4E, + 0x41E => 0x4F, + 0x41F => 0x50, + 0x420 => 0x52, + 0x421 => 0x53, + 0x422 => 0x54, + 0x423 => 0x55, + 0x424 => 0x46, + 0x425 => 0x4B, + 0x426 => 0x54, + 0x427 => 0x43, + 0x428 => 0x53, + 0x429 => 0x53, + 0x42A => 0x62, + 0x42B => 0x59, + 0x42C => 0x62, + 0x42D => 0x45, + 0x42E => 0x59, + 0x42F => 0x59, + 0x430 => 0x61, + 0x431 => 0x62, + 0x432 => 0x76, + 0x433 => 0x67, + 0x434 => 0x64, + 0x435 => 0x65, + 0x436 => 0x7A, + 0x437 => 0x7A, + 0x438 => 0x69, + 0x439 => 0x79, + 0x43A => 0x6B, + 0x43B => 0x6C, + 0x43C => 0x6D, + 0x43D => 0x6E, + 0x43E => 0x6F, + 0x43F => 0x70, + 0x440 => 0x72, + 0x441 => 0x73, + 0x442 => 0x74, + 0x443 => 0x75, + 0x444 => 0x66, + 0x445 => 0x6B, + 0x446 => 0x74, + 0x447 => 0x63, + 0x448 => 0x73, + 0x449 => 0x73, + 0x44B => 0x79, + 0x44D => 0x65, + 0x44E => 0x79, + 0x44F => 0x79, + 0x451 => 0x65, + 0x452 => 0x64, + 0x454 => 0x65, + 0x456 => 0x69, + 0x457 => 0x69, + 0x458 => 0x6A, + 0x459 => 0x6C, + 0x45A => 0x6E, + 0x45F => 0x64, + 0x490 => 0x47, + 0x491 => 0x67, + 0x4E8 => 0x4F, + 0x622 => 0x61, + 0x623 => 0x61, + 0x624 => 0x6F, + 0x625 => 0x65, + 0x626 => 0x65, + 0x627 => 0x61, + 0x628 => 0x62, + 0x62A => 0x74, + 0x62B => 0x74, + 0x62C => 0x6A, + 0x62D => 0x68, + 0x62E => 0x6B, + 0x62F => 0x64, + 0x630 => 0x74, + 0x631 => 0x72, + 0x632 => 0x7A, + 0x633 => 0x73, + 0x634 => 0x73, + 0x635 => 0x73, + 0x636 => 0x64, + 0x637 => 0x74, + 0x638 => 0x74, + 0x639 => 0x61, + 0x63A => 0x67, + 0x641 => 0x66, + 0x642 => 0x6B, + 0x643 => 0x6B, + 0x644 => 0x6C, + 0x645 => 0x6D, + 0x646 => 0x6E, + 0x647 => 0x68, + 0x648 => 0x6F, + 0x64A => 0x79, + 0x664 => 0x34, + 0x665 => 0x35, + 0x666 => 0x36, + 0x67E => 0x70, + 0x686 => 0x63, + 0x698 => 0x7A, + 0x6A9 => 0x6B, + 0x6AF => 0x67, + 0x6CC => 0x69, + 0x6F0 => 0x30, + 0x6F1 => 0x31, + 0x6F2 => 0x32, + 0x6F3 => 0x33, + 0x6F4 => 0x34, + 0x6F5 => 0x35, + 0x6F6 => 0x36, + 0x6F7 => 0x37, + 0x6F8 => 0x38, + 0x6F9 => 0x39, + 0x905 => 0x61, + 0x906 => 0x61, + 0x907 => 0x69, + 0x908 => 0x69, + 0x909 => 0x75, + 0x90A => 0x75, + 0x90D => 0x65, + 0x90F => 0x65, + 0x910 => 0x61, + 0x911 => 0x6F, + 0x912 => 0x6F, + 0x913 => 0x6F, + 0x92C => 0x42, + 0x932 => 0x4C, + 0x1000 => 0x6B, + 0x1002 => 0x67, + 0x1005 => 0x73, + 0x1007 => 0x7A, + 0x1009 => 0x75, + 0x100A => 0x69, + 0x100B => 0x74, + 0x100D => 0x64, + 0x1010 => 0x74, + 0x1012 => 0x64, + 0x1014 => 0x6E, + 0x1015 => 0x70, + 0x1017 => 0x62, + 0x1019 => 0x6D, + 0x101A => 0x79, + 0x101C => 0x6C, + 0x101D => 0x77, + 0x101F => 0x68, + 0x1021 => 0x61, + 0x1023 => 0x69, + 0x1027 => 0x65, + 0x102B => 0x61, + 0x102C => 0x61, + 0x102D => 0x6F, + 0x102E => 0x69, + 0x102F => 0x75, + 0x1030 => 0x75, + 0x1031 => 0x65, + 0x1032 => 0x65, + 0x103D => 0x77, + 0x103E => 0x68, + 0x10D0 => 0x61, + 0x10D1 => 0x62, + 0x10D2 => 0x67, + 0x10D3 => 0x64, + 0x10D4 => 0x65, + 0x10D5 => 0x76, + 0x10D6 => 0x7A, + 0x10D7 => 0x74, + 0x10D8 => 0x69, + 0x10D9 => 0x6B, + 0x10DA => 0x6C, + 0x10DB => 0x6D, + 0x10DC => 0x6E, + 0x10DD => 0x6F, + 0x10DE => 0x70, + 0x10DF => 0x7A, + 0x10E0 => 0x72, + 0x10E1 => 0x73, + 0x10E2 => 0x74, + 0x10E3 => 0x75, + 0x10E4 => 0x66, + 0x10E5 => 0x6B, + 0x10E6 => 0x67, + 0x10E7 => 0x71, + 0x10E8 => 0x73, + 0x10E9 => 0x63, + 0x10EA => 0x74, + 0x10EB => 0x64, + 0x10EC => 0x74, + 0x10ED => 0x63, + 0x10EE => 0x6B, + 0x10EF => 0x6A, + 0x10F0 => 0x68, + 0x1D05 => 0x44, + 0x1D06 => 0x44, + 0x1D6D => 0x64, + 0x1D81 => 0x64, + 0x1D91 => 0x64, + 0x1E9E => 0x53, + 0x1EA0 => 0x41, + 0x1EA1 => 0x61, + 0x1EA2 => 0x41, + 0x1EA3 => 0x61, + 0x1EA4 => 0x41, + 0x1EA5 => 0x61, + 0x1EA6 => 0x41, + 0x1EA7 => 0x61, + 0x1EA8 => 0x41, + 0x1EA9 => 0x61, + 0x1EAA => 0x41, + 0x1EAB => 0x61, + 0x1EAC => 0x41, + 0x1EAD => 0x61, + 0x1EAE => 0x41, + 0x1EAF => 0x61, + 0x1EB0 => 0x41, + 0x1EB1 => 0x61, + 0x1EB2 => 0x41, + 0x1EB3 => 0x61, + 0x1EB4 => 0x41, + 0x1EB5 => 0x61, + 0x1EB6 => 0x41, + 0x1EB7 => 0x61, + 0x1EB8 => 0x45, + 0x1EB9 => 0x65, + 0x1EBA => 0x45, + 0x1EBB => 0x65, + 0x1EBC => 0x45, + 0x1EBD => 0x65, + 0x1EBE => 0x45, + 0x1EBF => 0x65, + 0x1EC0 => 0x45, + 0x1EC1 => 0x65, + 0x1EC2 => 0x45, + 0x1EC3 => 0x65, + 0x1EC4 => 0x45, + 0x1EC5 => 0x65, + 0x1EC6 => 0x45, + 0x1EC7 => 0x65, + 0x1EC8 => 0x49, + 0x1EC9 => 0x69, + 0x1ECA => 0x49, + 0x1ECB => 0x69, + 0x1ECC => 0x4F, + 0x1ECD => 0x6F, + 0x1ECE => 0x4F, + 0x1ECF => 0x6F, + 0x1ED0 => 0x4F, + 0x1ED1 => 0x6F, + 0x1ED2 => 0x4F, + 0x1ED3 => 0x6F, + 0x1ED4 => 0x4F, + 0x1ED5 => 0x6F, + 0x1ED6 => 0x4F, + 0x1ED7 => 0x6F, + 0x1ED8 => 0x4F, + 0x1ED9 => 0x6F, + 0x1EDA => 0x4F, + 0x1EDB => 0x6F, + 0x1EDC => 0x4F, + 0x1EDD => 0x6F, + 0x1EDE => 0x4F, + 0x1EDF => 0x6F, + 0x1EE0 => 0x4F, + 0x1EE1 => 0x6F, + 0x1EE2 => 0x4F, + 0x1EE3 => 0x6F, + 0x1EE4 => 0x55, + 0x1EE5 => 0x75, + 0x1EE6 => 0x55, + 0x1EE7 => 0x75, + 0x1EE8 => 0x55, + 0x1EE9 => 0x75, + 0x1EEA => 0x55, + 0x1EEB => 0x75, + 0x1EEC => 0x55, + 0x1EED => 0x75, + 0x1EEE => 0x55, + 0x1EEF => 0x75, + 0x1EF0 => 0x55, + 0x1EF1 => 0x75, + 0x1EF2 => 0x59, + 0x1EF3 => 0x79, + 0x1EF4 => 0x59, + 0x1EF5 => 0x79, + 0x1EF6 => 0x59, + 0x1EF7 => 0x79, + 0x1EF8 => 0x59, + 0x1EF9 => 0x79, + 0x1F00 => 0x61, + 0x1F01 => 0x61, + 0x1F02 => 0x61, + 0x1F03 => 0x61, + 0x1F04 => 0x61, + 0x1F05 => 0x61, + 0x1F06 => 0x61, + 0x1F07 => 0x61, + 0x1F08 => 0x41, + 0x1F09 => 0x41, + 0x1F0A => 0x41, + 0x1F0B => 0x41, + 0x1F0C => 0x41, + 0x1F0D => 0x41, + 0x1F0E => 0x41, + 0x1F0F => 0x41, + 0x1F10 => 0x65, + 0x1F11 => 0x65, + 0x1F12 => 0x65, + 0x1F13 => 0x65, + 0x1F14 => 0x65, + 0x1F15 => 0x65, + 0x1F18 => 0x45, + 0x1F19 => 0x45, + 0x1F1A => 0x45, + 0x1F1B => 0x45, + 0x1F1C => 0x45, + 0x1F1D => 0x45, + 0x1F30 => 0x69, + 0x1F31 => 0x69, + 0x1F32 => 0x69, + 0x1F33 => 0x69, + 0x1F34 => 0x69, + 0x1F35 => 0x69, + 0x1F36 => 0x69, + 0x1F37 => 0x69, + 0x1F38 => 0x49, + 0x1F39 => 0x49, + 0x1F3B => 0x49, + 0x1F3C => 0x49, + 0x1F3D => 0x49, + 0x1F3E => 0x49, + 0x1F3F => 0x49, + 0x1F40 => 0x6F, + 0x1F41 => 0x6F, + 0x1F42 => 0x6F, + 0x1F43 => 0x6F, + 0x1F44 => 0x6F, + 0x1F45 => 0x6F, + 0x1F48 => 0x4F, + 0x1F49 => 0x4F, + 0x1F4A => 0x4F, + 0x1F4B => 0x4F, + 0x1F4C => 0x4F, + 0x1F4D => 0x4F, + 0x1F70 => 0x61, + 0x1F72 => 0x65, + 0x1F76 => 0x69, + 0x1F78 => 0x6F, + 0x1F80 => 0x61, + 0x1F81 => 0x61, + 0x1F82 => 0x61, + 0x1F83 => 0x61, + 0x1F84 => 0x61, + 0x1F85 => 0x61, + 0x1F86 => 0x61, + 0x1F87 => 0x61, + 0x1F88 => 0x41, + 0x1F89 => 0x41, + 0x1F8A => 0x41, + 0x1F8B => 0x41, + 0x1F8C => 0x41, + 0x1F8D => 0x41, + 0x1F8E => 0x41, + 0x1F8F => 0x41, + 0x1FB0 => 0x61, + 0x1FB1 => 0x61, + 0x1FB2 => 0x61, + 0x1FB3 => 0x61, + 0x1FB4 => 0x61, + 0x1FB6 => 0x61, + 0x1FB7 => 0x61, + 0x1FB8 => 0x41, + 0x1FB9 => 0x41, + 0x1FBA => 0x41, + 0x1FBC => 0x41, + 0x1FC8 => 0x45, + 0x1FD0 => 0x69, + 0x1FD1 => 0x69, + 0x1FD2 => 0x69, + 0x1FD6 => 0x69, + 0x1FD7 => 0x69, + 0x1FD8 => 0x49, + 0x1FD9 => 0x49, + 0x1FDA => 0x49, + 0x1FE8 => 0x59, + 0x1FE9 => 0x59, + 0x1FEA => 0x59, + 0x1FF8 => 0x4F, + 0x2000 => 0x20, + 0x2001 => 0x20, + 0x2002 => 0x20, + 0x2003 => 0x20, + 0x2004 => 0x20, + 0x2005 => 0x20, + 0x2006 => 0x20, + 0x2007 => 0x20, + 0x2008 => 0x20, + 0x2009 => 0x20, + 0x200A => 0x20, + 0x202F => 0x20, + 0x205F => 0x20, + 0x2074 => 0x34, + 0x2075 => 0x35, + 0x2076 => 0x36, + 0x2077 => 0x37, + 0x2078 => 0x38, + 0x2079 => 0x39, + 0x2080 => 0x30, + 0x2081 => 0x31, + 0x2082 => 0x32, + 0x2083 => 0x33, + 0x2084 => 0x34, + 0x2085 => 0x35, + 0x2086 => 0x36, + 0x2087 => 0x37, + 0x2088 => 0x38, + 0x2089 => 0x39, + 0x3000 => 0x20, +); diff --git a/src/opis/string/res/fold.php b/src/opis/string/res/fold.php index e93f9819..efda4601 100644 --- a/src/opis/string/res/fold.php +++ b/src/opis/string/res/fold.php @@ -1,1417 +1,1417 @@ 0x61, -0x42 => 0x62, -0x43 => 0x63, -0x44 => 0x64, -0x45 => 0x65, -0x46 => 0x66, -0x47 => 0x67, -0x48 => 0x68, -0x49 => 0x69, -0x4A => 0x6A, -0x4B => 0x6B, -0x4C => 0x6C, -0x4D => 0x6D, -0x4E => 0x6E, -0x4F => 0x6F, -0x50 => 0x70, -0x51 => 0x71, -0x52 => 0x72, -0x53 => 0x73, -0x54 => 0x74, -0x55 => 0x75, -0x56 => 0x76, -0x57 => 0x77, -0x58 => 0x78, -0x59 => 0x79, -0x5A => 0x7A, -0xB5 => 0x3BC, -0xC0 => 0xE0, -0xC1 => 0xE1, -0xC2 => 0xE2, -0xC3 => 0xE3, -0xC4 => 0xE4, -0xC5 => 0xE5, -0xC6 => 0xE6, -0xC7 => 0xE7, -0xC8 => 0xE8, -0xC9 => 0xE9, -0xCA => 0xEA, -0xCB => 0xEB, -0xCC => 0xEC, -0xCD => 0xED, -0xCE => 0xEE, -0xCF => 0xEF, -0xD0 => 0xF0, -0xD1 => 0xF1, -0xD2 => 0xF2, -0xD3 => 0xF3, -0xD4 => 0xF4, -0xD5 => 0xF5, -0xD6 => 0xF6, -0xD8 => 0xF8, -0xD9 => 0xF9, -0xDA => 0xFA, -0xDB => 0xFB, -0xDC => 0xFC, -0xDD => 0xFD, -0xDE => 0xFE, -0x100 => 0x101, -0x102 => 0x103, -0x104 => 0x105, -0x106 => 0x107, -0x108 => 0x109, -0x10A => 0x10B, -0x10C => 0x10D, -0x10E => 0x10F, -0x110 => 0x111, -0x112 => 0x113, -0x114 => 0x115, -0x116 => 0x117, -0x118 => 0x119, -0x11A => 0x11B, -0x11C => 0x11D, -0x11E => 0x11F, -0x120 => 0x121, -0x122 => 0x123, -0x124 => 0x125, -0x126 => 0x127, -0x128 => 0x129, -0x12A => 0x12B, -0x12C => 0x12D, -0x12E => 0x12F, -0x132 => 0x133, -0x134 => 0x135, -0x136 => 0x137, -0x139 => 0x13A, -0x13B => 0x13C, -0x13D => 0x13E, -0x13F => 0x140, -0x141 => 0x142, -0x143 => 0x144, -0x145 => 0x146, -0x147 => 0x148, -0x14A => 0x14B, -0x14C => 0x14D, -0x14E => 0x14F, -0x150 => 0x151, -0x152 => 0x153, -0x154 => 0x155, -0x156 => 0x157, -0x158 => 0x159, -0x15A => 0x15B, -0x15C => 0x15D, -0x15E => 0x15F, -0x160 => 0x161, -0x162 => 0x163, -0x164 => 0x165, -0x166 => 0x167, -0x168 => 0x169, -0x16A => 0x16B, -0x16C => 0x16D, -0x16E => 0x16F, -0x170 => 0x171, -0x172 => 0x173, -0x174 => 0x175, -0x176 => 0x177, -0x178 => 0xFF, -0x179 => 0x17A, -0x17B => 0x17C, -0x17D => 0x17E, -0x17F => 0x73, -0x181 => 0x253, -0x182 => 0x183, -0x184 => 0x185, -0x186 => 0x254, -0x187 => 0x188, -0x189 => 0x256, -0x18A => 0x257, -0x18B => 0x18C, -0x18E => 0x1DD, -0x18F => 0x259, -0x190 => 0x25B, -0x191 => 0x192, -0x193 => 0x260, -0x194 => 0x263, -0x196 => 0x269, -0x197 => 0x268, -0x198 => 0x199, -0x19C => 0x26F, -0x19D => 0x272, -0x19F => 0x275, -0x1A0 => 0x1A1, -0x1A2 => 0x1A3, -0x1A4 => 0x1A5, -0x1A6 => 0x280, -0x1A7 => 0x1A8, -0x1A9 => 0x283, -0x1AC => 0x1AD, -0x1AE => 0x288, -0x1AF => 0x1B0, -0x1B1 => 0x28A, -0x1B2 => 0x28B, -0x1B3 => 0x1B4, -0x1B5 => 0x1B6, -0x1B7 => 0x292, -0x1B8 => 0x1B9, -0x1BC => 0x1BD, -0x1C4 => 0x1C6, -0x1C5 => 0x1C6, -0x1C7 => 0x1C9, -0x1C8 => 0x1C9, -0x1CA => 0x1CC, -0x1CB => 0x1CC, -0x1CD => 0x1CE, -0x1CF => 0x1D0, -0x1D1 => 0x1D2, -0x1D3 => 0x1D4, -0x1D5 => 0x1D6, -0x1D7 => 0x1D8, -0x1D9 => 0x1DA, -0x1DB => 0x1DC, -0x1DE => 0x1DF, -0x1E0 => 0x1E1, -0x1E2 => 0x1E3, -0x1E4 => 0x1E5, -0x1E6 => 0x1E7, -0x1E8 => 0x1E9, -0x1EA => 0x1EB, -0x1EC => 0x1ED, -0x1EE => 0x1EF, -0x1F1 => 0x1F3, -0x1F2 => 0x1F3, -0x1F4 => 0x1F5, -0x1F6 => 0x195, -0x1F7 => 0x1BF, -0x1F8 => 0x1F9, -0x1FA => 0x1FB, -0x1FC => 0x1FD, -0x1FE => 0x1FF, -0x200 => 0x201, -0x202 => 0x203, -0x204 => 0x205, -0x206 => 0x207, -0x208 => 0x209, -0x20A => 0x20B, -0x20C => 0x20D, -0x20E => 0x20F, -0x210 => 0x211, -0x212 => 0x213, -0x214 => 0x215, -0x216 => 0x217, -0x218 => 0x219, -0x21A => 0x21B, -0x21C => 0x21D, -0x21E => 0x21F, -0x220 => 0x19E, -0x222 => 0x223, -0x224 => 0x225, -0x226 => 0x227, -0x228 => 0x229, -0x22A => 0x22B, -0x22C => 0x22D, -0x22E => 0x22F, -0x230 => 0x231, -0x232 => 0x233, -0x23A => 0x2C65, -0x23B => 0x23C, -0x23D => 0x19A, -0x23E => 0x2C66, -0x241 => 0x242, -0x243 => 0x180, -0x244 => 0x289, -0x245 => 0x28C, -0x246 => 0x247, -0x248 => 0x249, -0x24A => 0x24B, -0x24C => 0x24D, -0x24E => 0x24F, -0x345 => 0x3B9, -0x370 => 0x371, -0x372 => 0x373, -0x376 => 0x377, -0x37F => 0x3F3, -0x386 => 0x3AC, -0x388 => 0x3AD, -0x389 => 0x3AE, -0x38A => 0x3AF, -0x38C => 0x3CC, -0x38E => 0x3CD, -0x38F => 0x3CE, -0x391 => 0x3B1, -0x392 => 0x3B2, -0x393 => 0x3B3, -0x394 => 0x3B4, -0x395 => 0x3B5, -0x396 => 0x3B6, -0x397 => 0x3B7, -0x398 => 0x3B8, -0x399 => 0x3B9, -0x39A => 0x3BA, -0x39B => 0x3BB, -0x39C => 0x3BC, -0x39D => 0x3BD, -0x39E => 0x3BE, -0x39F => 0x3BF, -0x3A0 => 0x3C0, -0x3A1 => 0x3C1, -0x3A3 => 0x3C3, -0x3A4 => 0x3C4, -0x3A5 => 0x3C5, -0x3A6 => 0x3C6, -0x3A7 => 0x3C7, -0x3A8 => 0x3C8, -0x3A9 => 0x3C9, -0x3AA => 0x3CA, -0x3AB => 0x3CB, -0x3C2 => 0x3C3, -0x3CF => 0x3D7, -0x3D0 => 0x3B2, -0x3D1 => 0x3B8, -0x3D5 => 0x3C6, -0x3D6 => 0x3C0, -0x3D8 => 0x3D9, -0x3DA => 0x3DB, -0x3DC => 0x3DD, -0x3DE => 0x3DF, -0x3E0 => 0x3E1, -0x3E2 => 0x3E3, -0x3E4 => 0x3E5, -0x3E6 => 0x3E7, -0x3E8 => 0x3E9, -0x3EA => 0x3EB, -0x3EC => 0x3ED, -0x3EE => 0x3EF, -0x3F0 => 0x3BA, -0x3F1 => 0x3C1, -0x3F4 => 0x3B8, -0x3F5 => 0x3B5, -0x3F7 => 0x3F8, -0x3F9 => 0x3F2, -0x3FA => 0x3FB, -0x3FD => 0x37B, -0x3FE => 0x37C, -0x3FF => 0x37D, -0x400 => 0x450, -0x401 => 0x451, -0x402 => 0x452, -0x403 => 0x453, -0x404 => 0x454, -0x405 => 0x455, -0x406 => 0x456, -0x407 => 0x457, -0x408 => 0x458, -0x409 => 0x459, -0x40A => 0x45A, -0x40B => 0x45B, -0x40C => 0x45C, -0x40D => 0x45D, -0x40E => 0x45E, -0x40F => 0x45F, -0x410 => 0x430, -0x411 => 0x431, -0x412 => 0x432, -0x413 => 0x433, -0x414 => 0x434, -0x415 => 0x435, -0x416 => 0x436, -0x417 => 0x437, -0x418 => 0x438, -0x419 => 0x439, -0x41A => 0x43A, -0x41B => 0x43B, -0x41C => 0x43C, -0x41D => 0x43D, -0x41E => 0x43E, -0x41F => 0x43F, -0x420 => 0x440, -0x421 => 0x441, -0x422 => 0x442, -0x423 => 0x443, -0x424 => 0x444, -0x425 => 0x445, -0x426 => 0x446, -0x427 => 0x447, -0x428 => 0x448, -0x429 => 0x449, -0x42A => 0x44A, -0x42B => 0x44B, -0x42C => 0x44C, -0x42D => 0x44D, -0x42E => 0x44E, -0x42F => 0x44F, -0x460 => 0x461, -0x462 => 0x463, -0x464 => 0x465, -0x466 => 0x467, -0x468 => 0x469, -0x46A => 0x46B, -0x46C => 0x46D, -0x46E => 0x46F, -0x470 => 0x471, -0x472 => 0x473, -0x474 => 0x475, -0x476 => 0x477, -0x478 => 0x479, -0x47A => 0x47B, -0x47C => 0x47D, -0x47E => 0x47F, -0x480 => 0x481, -0x48A => 0x48B, -0x48C => 0x48D, -0x48E => 0x48F, -0x490 => 0x491, -0x492 => 0x493, -0x494 => 0x495, -0x496 => 0x497, -0x498 => 0x499, -0x49A => 0x49B, -0x49C => 0x49D, -0x49E => 0x49F, -0x4A0 => 0x4A1, -0x4A2 => 0x4A3, -0x4A4 => 0x4A5, -0x4A6 => 0x4A7, -0x4A8 => 0x4A9, -0x4AA => 0x4AB, -0x4AC => 0x4AD, -0x4AE => 0x4AF, -0x4B0 => 0x4B1, -0x4B2 => 0x4B3, -0x4B4 => 0x4B5, -0x4B6 => 0x4B7, -0x4B8 => 0x4B9, -0x4BA => 0x4BB, -0x4BC => 0x4BD, -0x4BE => 0x4BF, -0x4C0 => 0x4CF, -0x4C1 => 0x4C2, -0x4C3 => 0x4C4, -0x4C5 => 0x4C6, -0x4C7 => 0x4C8, -0x4C9 => 0x4CA, -0x4CB => 0x4CC, -0x4CD => 0x4CE, -0x4D0 => 0x4D1, -0x4D2 => 0x4D3, -0x4D4 => 0x4D5, -0x4D6 => 0x4D7, -0x4D8 => 0x4D9, -0x4DA => 0x4DB, -0x4DC => 0x4DD, -0x4DE => 0x4DF, -0x4E0 => 0x4E1, -0x4E2 => 0x4E3, -0x4E4 => 0x4E5, -0x4E6 => 0x4E7, -0x4E8 => 0x4E9, -0x4EA => 0x4EB, -0x4EC => 0x4ED, -0x4EE => 0x4EF, -0x4F0 => 0x4F1, -0x4F2 => 0x4F3, -0x4F4 => 0x4F5, -0x4F6 => 0x4F7, -0x4F8 => 0x4F9, -0x4FA => 0x4FB, -0x4FC => 0x4FD, -0x4FE => 0x4FF, -0x500 => 0x501, -0x502 => 0x503, -0x504 => 0x505, -0x506 => 0x507, -0x508 => 0x509, -0x50A => 0x50B, -0x50C => 0x50D, -0x50E => 0x50F, -0x510 => 0x511, -0x512 => 0x513, -0x514 => 0x515, -0x516 => 0x517, -0x518 => 0x519, -0x51A => 0x51B, -0x51C => 0x51D, -0x51E => 0x51F, -0x520 => 0x521, -0x522 => 0x523, -0x524 => 0x525, -0x526 => 0x527, -0x528 => 0x529, -0x52A => 0x52B, -0x52C => 0x52D, -0x52E => 0x52F, -0x531 => 0x561, -0x532 => 0x562, -0x533 => 0x563, -0x534 => 0x564, -0x535 => 0x565, -0x536 => 0x566, -0x537 => 0x567, -0x538 => 0x568, -0x539 => 0x569, -0x53A => 0x56A, -0x53B => 0x56B, -0x53C => 0x56C, -0x53D => 0x56D, -0x53E => 0x56E, -0x53F => 0x56F, -0x540 => 0x570, -0x541 => 0x571, -0x542 => 0x572, -0x543 => 0x573, -0x544 => 0x574, -0x545 => 0x575, -0x546 => 0x576, -0x547 => 0x577, -0x548 => 0x578, -0x549 => 0x579, -0x54A => 0x57A, -0x54B => 0x57B, -0x54C => 0x57C, -0x54D => 0x57D, -0x54E => 0x57E, -0x54F => 0x57F, -0x550 => 0x580, -0x551 => 0x581, -0x552 => 0x582, -0x553 => 0x583, -0x554 => 0x584, -0x555 => 0x585, -0x556 => 0x586, -0x10A0 => 0x2D00, -0x10A1 => 0x2D01, -0x10A2 => 0x2D02, -0x10A3 => 0x2D03, -0x10A4 => 0x2D04, -0x10A5 => 0x2D05, -0x10A6 => 0x2D06, -0x10A7 => 0x2D07, -0x10A8 => 0x2D08, -0x10A9 => 0x2D09, -0x10AA => 0x2D0A, -0x10AB => 0x2D0B, -0x10AC => 0x2D0C, -0x10AD => 0x2D0D, -0x10AE => 0x2D0E, -0x10AF => 0x2D0F, -0x10B0 => 0x2D10, -0x10B1 => 0x2D11, -0x10B2 => 0x2D12, -0x10B3 => 0x2D13, -0x10B4 => 0x2D14, -0x10B5 => 0x2D15, -0x10B6 => 0x2D16, -0x10B7 => 0x2D17, -0x10B8 => 0x2D18, -0x10B9 => 0x2D19, -0x10BA => 0x2D1A, -0x10BB => 0x2D1B, -0x10BC => 0x2D1C, -0x10BD => 0x2D1D, -0x10BE => 0x2D1E, -0x10BF => 0x2D1F, -0x10C0 => 0x2D20, -0x10C1 => 0x2D21, -0x10C2 => 0x2D22, -0x10C3 => 0x2D23, -0x10C4 => 0x2D24, -0x10C5 => 0x2D25, -0x10C7 => 0x2D27, -0x10CD => 0x2D2D, -0x13F8 => 0x13F0, -0x13F9 => 0x13F1, -0x13FA => 0x13F2, -0x13FB => 0x13F3, -0x13FC => 0x13F4, -0x13FD => 0x13F5, -0x1C80 => 0x432, -0x1C81 => 0x434, -0x1C82 => 0x43E, -0x1C83 => 0x441, -0x1C84 => 0x442, -0x1C85 => 0x442, -0x1C86 => 0x44A, -0x1C87 => 0x463, -0x1C88 => 0xA64B, -0x1C90 => 0x10D0, -0x1C91 => 0x10D1, -0x1C92 => 0x10D2, -0x1C93 => 0x10D3, -0x1C94 => 0x10D4, -0x1C95 => 0x10D5, -0x1C96 => 0x10D6, -0x1C97 => 0x10D7, -0x1C98 => 0x10D8, -0x1C99 => 0x10D9, -0x1C9A => 0x10DA, -0x1C9B => 0x10DB, -0x1C9C => 0x10DC, -0x1C9D => 0x10DD, -0x1C9E => 0x10DE, -0x1C9F => 0x10DF, -0x1CA0 => 0x10E0, -0x1CA1 => 0x10E1, -0x1CA2 => 0x10E2, -0x1CA3 => 0x10E3, -0x1CA4 => 0x10E4, -0x1CA5 => 0x10E5, -0x1CA6 => 0x10E6, -0x1CA7 => 0x10E7, -0x1CA8 => 0x10E8, -0x1CA9 => 0x10E9, -0x1CAA => 0x10EA, -0x1CAB => 0x10EB, -0x1CAC => 0x10EC, -0x1CAD => 0x10ED, -0x1CAE => 0x10EE, -0x1CAF => 0x10EF, -0x1CB0 => 0x10F0, -0x1CB1 => 0x10F1, -0x1CB2 => 0x10F2, -0x1CB3 => 0x10F3, -0x1CB4 => 0x10F4, -0x1CB5 => 0x10F5, -0x1CB6 => 0x10F6, -0x1CB7 => 0x10F7, -0x1CB8 => 0x10F8, -0x1CB9 => 0x10F9, -0x1CBA => 0x10FA, -0x1CBD => 0x10FD, -0x1CBE => 0x10FE, -0x1CBF => 0x10FF, -0x1E00 => 0x1E01, -0x1E02 => 0x1E03, -0x1E04 => 0x1E05, -0x1E06 => 0x1E07, -0x1E08 => 0x1E09, -0x1E0A => 0x1E0B, -0x1E0C => 0x1E0D, -0x1E0E => 0x1E0F, -0x1E10 => 0x1E11, -0x1E12 => 0x1E13, -0x1E14 => 0x1E15, -0x1E16 => 0x1E17, -0x1E18 => 0x1E19, -0x1E1A => 0x1E1B, -0x1E1C => 0x1E1D, -0x1E1E => 0x1E1F, -0x1E20 => 0x1E21, -0x1E22 => 0x1E23, -0x1E24 => 0x1E25, -0x1E26 => 0x1E27, -0x1E28 => 0x1E29, -0x1E2A => 0x1E2B, -0x1E2C => 0x1E2D, -0x1E2E => 0x1E2F, -0x1E30 => 0x1E31, -0x1E32 => 0x1E33, -0x1E34 => 0x1E35, -0x1E36 => 0x1E37, -0x1E38 => 0x1E39, -0x1E3A => 0x1E3B, -0x1E3C => 0x1E3D, -0x1E3E => 0x1E3F, -0x1E40 => 0x1E41, -0x1E42 => 0x1E43, -0x1E44 => 0x1E45, -0x1E46 => 0x1E47, -0x1E48 => 0x1E49, -0x1E4A => 0x1E4B, -0x1E4C => 0x1E4D, -0x1E4E => 0x1E4F, -0x1E50 => 0x1E51, -0x1E52 => 0x1E53, -0x1E54 => 0x1E55, -0x1E56 => 0x1E57, -0x1E58 => 0x1E59, -0x1E5A => 0x1E5B, -0x1E5C => 0x1E5D, -0x1E5E => 0x1E5F, -0x1E60 => 0x1E61, -0x1E62 => 0x1E63, -0x1E64 => 0x1E65, -0x1E66 => 0x1E67, -0x1E68 => 0x1E69, -0x1E6A => 0x1E6B, -0x1E6C => 0x1E6D, -0x1E6E => 0x1E6F, -0x1E70 => 0x1E71, -0x1E72 => 0x1E73, -0x1E74 => 0x1E75, -0x1E76 => 0x1E77, -0x1E78 => 0x1E79, -0x1E7A => 0x1E7B, -0x1E7C => 0x1E7D, -0x1E7E => 0x1E7F, -0x1E80 => 0x1E81, -0x1E82 => 0x1E83, -0x1E84 => 0x1E85, -0x1E86 => 0x1E87, -0x1E88 => 0x1E89, -0x1E8A => 0x1E8B, -0x1E8C => 0x1E8D, -0x1E8E => 0x1E8F, -0x1E90 => 0x1E91, -0x1E92 => 0x1E93, -0x1E94 => 0x1E95, -0x1E9B => 0x1E61, -0x1E9E => 0xDF, -0x1EA0 => 0x1EA1, -0x1EA2 => 0x1EA3, -0x1EA4 => 0x1EA5, -0x1EA6 => 0x1EA7, -0x1EA8 => 0x1EA9, -0x1EAA => 0x1EAB, -0x1EAC => 0x1EAD, -0x1EAE => 0x1EAF, -0x1EB0 => 0x1EB1, -0x1EB2 => 0x1EB3, -0x1EB4 => 0x1EB5, -0x1EB6 => 0x1EB7, -0x1EB8 => 0x1EB9, -0x1EBA => 0x1EBB, -0x1EBC => 0x1EBD, -0x1EBE => 0x1EBF, -0x1EC0 => 0x1EC1, -0x1EC2 => 0x1EC3, -0x1EC4 => 0x1EC5, -0x1EC6 => 0x1EC7, -0x1EC8 => 0x1EC9, -0x1ECA => 0x1ECB, -0x1ECC => 0x1ECD, -0x1ECE => 0x1ECF, -0x1ED0 => 0x1ED1, -0x1ED2 => 0x1ED3, -0x1ED4 => 0x1ED5, -0x1ED6 => 0x1ED7, -0x1ED8 => 0x1ED9, -0x1EDA => 0x1EDB, -0x1EDC => 0x1EDD, -0x1EDE => 0x1EDF, -0x1EE0 => 0x1EE1, -0x1EE2 => 0x1EE3, -0x1EE4 => 0x1EE5, -0x1EE6 => 0x1EE7, -0x1EE8 => 0x1EE9, -0x1EEA => 0x1EEB, -0x1EEC => 0x1EED, -0x1EEE => 0x1EEF, -0x1EF0 => 0x1EF1, -0x1EF2 => 0x1EF3, -0x1EF4 => 0x1EF5, -0x1EF6 => 0x1EF7, -0x1EF8 => 0x1EF9, -0x1EFA => 0x1EFB, -0x1EFC => 0x1EFD, -0x1EFE => 0x1EFF, -0x1F08 => 0x1F00, -0x1F09 => 0x1F01, -0x1F0A => 0x1F02, -0x1F0B => 0x1F03, -0x1F0C => 0x1F04, -0x1F0D => 0x1F05, -0x1F0E => 0x1F06, -0x1F0F => 0x1F07, -0x1F18 => 0x1F10, -0x1F19 => 0x1F11, -0x1F1A => 0x1F12, -0x1F1B => 0x1F13, -0x1F1C => 0x1F14, -0x1F1D => 0x1F15, -0x1F28 => 0x1F20, -0x1F29 => 0x1F21, -0x1F2A => 0x1F22, -0x1F2B => 0x1F23, -0x1F2C => 0x1F24, -0x1F2D => 0x1F25, -0x1F2E => 0x1F26, -0x1F2F => 0x1F27, -0x1F38 => 0x1F30, -0x1F39 => 0x1F31, -0x1F3A => 0x1F32, -0x1F3B => 0x1F33, -0x1F3C => 0x1F34, -0x1F3D => 0x1F35, -0x1F3E => 0x1F36, -0x1F3F => 0x1F37, -0x1F48 => 0x1F40, -0x1F49 => 0x1F41, -0x1F4A => 0x1F42, -0x1F4B => 0x1F43, -0x1F4C => 0x1F44, -0x1F4D => 0x1F45, -0x1F59 => 0x1F51, -0x1F5B => 0x1F53, -0x1F5D => 0x1F55, -0x1F5F => 0x1F57, -0x1F68 => 0x1F60, -0x1F69 => 0x1F61, -0x1F6A => 0x1F62, -0x1F6B => 0x1F63, -0x1F6C => 0x1F64, -0x1F6D => 0x1F65, -0x1F6E => 0x1F66, -0x1F6F => 0x1F67, -0x1F88 => 0x1F80, -0x1F89 => 0x1F81, -0x1F8A => 0x1F82, -0x1F8B => 0x1F83, -0x1F8C => 0x1F84, -0x1F8D => 0x1F85, -0x1F8E => 0x1F86, -0x1F8F => 0x1F87, -0x1F98 => 0x1F90, -0x1F99 => 0x1F91, -0x1F9A => 0x1F92, -0x1F9B => 0x1F93, -0x1F9C => 0x1F94, -0x1F9D => 0x1F95, -0x1F9E => 0x1F96, -0x1F9F => 0x1F97, -0x1FA8 => 0x1FA0, -0x1FA9 => 0x1FA1, -0x1FAA => 0x1FA2, -0x1FAB => 0x1FA3, -0x1FAC => 0x1FA4, -0x1FAD => 0x1FA5, -0x1FAE => 0x1FA6, -0x1FAF => 0x1FA7, -0x1FB8 => 0x1FB0, -0x1FB9 => 0x1FB1, -0x1FBA => 0x1F70, -0x1FBB => 0x1F71, -0x1FBC => 0x1FB3, -0x1FBE => 0x3B9, -0x1FC8 => 0x1F72, -0x1FC9 => 0x1F73, -0x1FCA => 0x1F74, -0x1FCB => 0x1F75, -0x1FCC => 0x1FC3, -0x1FD8 => 0x1FD0, -0x1FD9 => 0x1FD1, -0x1FDA => 0x1F76, -0x1FDB => 0x1F77, -0x1FE8 => 0x1FE0, -0x1FE9 => 0x1FE1, -0x1FEA => 0x1F7A, -0x1FEB => 0x1F7B, -0x1FEC => 0x1FE5, -0x1FF8 => 0x1F78, -0x1FF9 => 0x1F79, -0x1FFA => 0x1F7C, -0x1FFB => 0x1F7D, -0x1FFC => 0x1FF3, -0x2126 => 0x3C9, -0x212A => 0x6B, -0x212B => 0xE5, -0x2132 => 0x214E, -0x2160 => 0x2170, -0x2161 => 0x2171, -0x2162 => 0x2172, -0x2163 => 0x2173, -0x2164 => 0x2174, -0x2165 => 0x2175, -0x2166 => 0x2176, -0x2167 => 0x2177, -0x2168 => 0x2178, -0x2169 => 0x2179, -0x216A => 0x217A, -0x216B => 0x217B, -0x216C => 0x217C, -0x216D => 0x217D, -0x216E => 0x217E, -0x216F => 0x217F, -0x2183 => 0x2184, -0x24B6 => 0x24D0, -0x24B7 => 0x24D1, -0x24B8 => 0x24D2, -0x24B9 => 0x24D3, -0x24BA => 0x24D4, -0x24BB => 0x24D5, -0x24BC => 0x24D6, -0x24BD => 0x24D7, -0x24BE => 0x24D8, -0x24BF => 0x24D9, -0x24C0 => 0x24DA, -0x24C1 => 0x24DB, -0x24C2 => 0x24DC, -0x24C3 => 0x24DD, -0x24C4 => 0x24DE, -0x24C5 => 0x24DF, -0x24C6 => 0x24E0, -0x24C7 => 0x24E1, -0x24C8 => 0x24E2, -0x24C9 => 0x24E3, -0x24CA => 0x24E4, -0x24CB => 0x24E5, -0x24CC => 0x24E6, -0x24CD => 0x24E7, -0x24CE => 0x24E8, -0x24CF => 0x24E9, -0x2C00 => 0x2C30, -0x2C01 => 0x2C31, -0x2C02 => 0x2C32, -0x2C03 => 0x2C33, -0x2C04 => 0x2C34, -0x2C05 => 0x2C35, -0x2C06 => 0x2C36, -0x2C07 => 0x2C37, -0x2C08 => 0x2C38, -0x2C09 => 0x2C39, -0x2C0A => 0x2C3A, -0x2C0B => 0x2C3B, -0x2C0C => 0x2C3C, -0x2C0D => 0x2C3D, -0x2C0E => 0x2C3E, -0x2C0F => 0x2C3F, -0x2C10 => 0x2C40, -0x2C11 => 0x2C41, -0x2C12 => 0x2C42, -0x2C13 => 0x2C43, -0x2C14 => 0x2C44, -0x2C15 => 0x2C45, -0x2C16 => 0x2C46, -0x2C17 => 0x2C47, -0x2C18 => 0x2C48, -0x2C19 => 0x2C49, -0x2C1A => 0x2C4A, -0x2C1B => 0x2C4B, -0x2C1C => 0x2C4C, -0x2C1D => 0x2C4D, -0x2C1E => 0x2C4E, -0x2C1F => 0x2C4F, -0x2C20 => 0x2C50, -0x2C21 => 0x2C51, -0x2C22 => 0x2C52, -0x2C23 => 0x2C53, -0x2C24 => 0x2C54, -0x2C25 => 0x2C55, -0x2C26 => 0x2C56, -0x2C27 => 0x2C57, -0x2C28 => 0x2C58, -0x2C29 => 0x2C59, -0x2C2A => 0x2C5A, -0x2C2B => 0x2C5B, -0x2C2C => 0x2C5C, -0x2C2D => 0x2C5D, -0x2C2E => 0x2C5E, -0x2C60 => 0x2C61, -0x2C62 => 0x26B, -0x2C63 => 0x1D7D, -0x2C64 => 0x27D, -0x2C67 => 0x2C68, -0x2C69 => 0x2C6A, -0x2C6B => 0x2C6C, -0x2C6D => 0x251, -0x2C6E => 0x271, -0x2C6F => 0x250, -0x2C70 => 0x252, -0x2C72 => 0x2C73, -0x2C75 => 0x2C76, -0x2C7E => 0x23F, -0x2C7F => 0x240, -0x2C80 => 0x2C81, -0x2C82 => 0x2C83, -0x2C84 => 0x2C85, -0x2C86 => 0x2C87, -0x2C88 => 0x2C89, -0x2C8A => 0x2C8B, -0x2C8C => 0x2C8D, -0x2C8E => 0x2C8F, -0x2C90 => 0x2C91, -0x2C92 => 0x2C93, -0x2C94 => 0x2C95, -0x2C96 => 0x2C97, -0x2C98 => 0x2C99, -0x2C9A => 0x2C9B, -0x2C9C => 0x2C9D, -0x2C9E => 0x2C9F, -0x2CA0 => 0x2CA1, -0x2CA2 => 0x2CA3, -0x2CA4 => 0x2CA5, -0x2CA6 => 0x2CA7, -0x2CA8 => 0x2CA9, -0x2CAA => 0x2CAB, -0x2CAC => 0x2CAD, -0x2CAE => 0x2CAF, -0x2CB0 => 0x2CB1, -0x2CB2 => 0x2CB3, -0x2CB4 => 0x2CB5, -0x2CB6 => 0x2CB7, -0x2CB8 => 0x2CB9, -0x2CBA => 0x2CBB, -0x2CBC => 0x2CBD, -0x2CBE => 0x2CBF, -0x2CC0 => 0x2CC1, -0x2CC2 => 0x2CC3, -0x2CC4 => 0x2CC5, -0x2CC6 => 0x2CC7, -0x2CC8 => 0x2CC9, -0x2CCA => 0x2CCB, -0x2CCC => 0x2CCD, -0x2CCE => 0x2CCF, -0x2CD0 => 0x2CD1, -0x2CD2 => 0x2CD3, -0x2CD4 => 0x2CD5, -0x2CD6 => 0x2CD7, -0x2CD8 => 0x2CD9, -0x2CDA => 0x2CDB, -0x2CDC => 0x2CDD, -0x2CDE => 0x2CDF, -0x2CE0 => 0x2CE1, -0x2CE2 => 0x2CE3, -0x2CEB => 0x2CEC, -0x2CED => 0x2CEE, -0x2CF2 => 0x2CF3, -0xA640 => 0xA641, -0xA642 => 0xA643, -0xA644 => 0xA645, -0xA646 => 0xA647, -0xA648 => 0xA649, -0xA64A => 0xA64B, -0xA64C => 0xA64D, -0xA64E => 0xA64F, -0xA650 => 0xA651, -0xA652 => 0xA653, -0xA654 => 0xA655, -0xA656 => 0xA657, -0xA658 => 0xA659, -0xA65A => 0xA65B, -0xA65C => 0xA65D, -0xA65E => 0xA65F, -0xA660 => 0xA661, -0xA662 => 0xA663, -0xA664 => 0xA665, -0xA666 => 0xA667, -0xA668 => 0xA669, -0xA66A => 0xA66B, -0xA66C => 0xA66D, -0xA680 => 0xA681, -0xA682 => 0xA683, -0xA684 => 0xA685, -0xA686 => 0xA687, -0xA688 => 0xA689, -0xA68A => 0xA68B, -0xA68C => 0xA68D, -0xA68E => 0xA68F, -0xA690 => 0xA691, -0xA692 => 0xA693, -0xA694 => 0xA695, -0xA696 => 0xA697, -0xA698 => 0xA699, -0xA69A => 0xA69B, -0xA722 => 0xA723, -0xA724 => 0xA725, -0xA726 => 0xA727, -0xA728 => 0xA729, -0xA72A => 0xA72B, -0xA72C => 0xA72D, -0xA72E => 0xA72F, -0xA732 => 0xA733, -0xA734 => 0xA735, -0xA736 => 0xA737, -0xA738 => 0xA739, -0xA73A => 0xA73B, -0xA73C => 0xA73D, -0xA73E => 0xA73F, -0xA740 => 0xA741, -0xA742 => 0xA743, -0xA744 => 0xA745, -0xA746 => 0xA747, -0xA748 => 0xA749, -0xA74A => 0xA74B, -0xA74C => 0xA74D, -0xA74E => 0xA74F, -0xA750 => 0xA751, -0xA752 => 0xA753, -0xA754 => 0xA755, -0xA756 => 0xA757, -0xA758 => 0xA759, -0xA75A => 0xA75B, -0xA75C => 0xA75D, -0xA75E => 0xA75F, -0xA760 => 0xA761, -0xA762 => 0xA763, -0xA764 => 0xA765, -0xA766 => 0xA767, -0xA768 => 0xA769, -0xA76A => 0xA76B, -0xA76C => 0xA76D, -0xA76E => 0xA76F, -0xA779 => 0xA77A, -0xA77B => 0xA77C, -0xA77D => 0x1D79, -0xA77E => 0xA77F, -0xA780 => 0xA781, -0xA782 => 0xA783, -0xA784 => 0xA785, -0xA786 => 0xA787, -0xA78B => 0xA78C, -0xA78D => 0x265, -0xA790 => 0xA791, -0xA792 => 0xA793, -0xA796 => 0xA797, -0xA798 => 0xA799, -0xA79A => 0xA79B, -0xA79C => 0xA79D, -0xA79E => 0xA79F, -0xA7A0 => 0xA7A1, -0xA7A2 => 0xA7A3, -0xA7A4 => 0xA7A5, -0xA7A6 => 0xA7A7, -0xA7A8 => 0xA7A9, -0xA7AA => 0x266, -0xA7AB => 0x25C, -0xA7AC => 0x261, -0xA7AD => 0x26C, -0xA7AE => 0x26A, -0xA7B0 => 0x29E, -0xA7B1 => 0x287, -0xA7B2 => 0x29D, -0xA7B3 => 0xAB53, -0xA7B4 => 0xA7B5, -0xA7B6 => 0xA7B7, -0xA7B8 => 0xA7B9, -0xA7BA => 0xA7BB, -0xA7BC => 0xA7BD, -0xA7BE => 0xA7BF, -0xA7C2 => 0xA7C3, -0xA7C4 => 0xA794, -0xA7C5 => 0x282, -0xA7C6 => 0x1D8E, -0xA7C7 => 0xA7C8, -0xA7C9 => 0xA7CA, -0xA7F5 => 0xA7F6, -0xAB70 => 0x13A0, -0xAB71 => 0x13A1, -0xAB72 => 0x13A2, -0xAB73 => 0x13A3, -0xAB74 => 0x13A4, -0xAB75 => 0x13A5, -0xAB76 => 0x13A6, -0xAB77 => 0x13A7, -0xAB78 => 0x13A8, -0xAB79 => 0x13A9, -0xAB7A => 0x13AA, -0xAB7B => 0x13AB, -0xAB7C => 0x13AC, -0xAB7D => 0x13AD, -0xAB7E => 0x13AE, -0xAB7F => 0x13AF, -0xAB80 => 0x13B0, -0xAB81 => 0x13B1, -0xAB82 => 0x13B2, -0xAB83 => 0x13B3, -0xAB84 => 0x13B4, -0xAB85 => 0x13B5, -0xAB86 => 0x13B6, -0xAB87 => 0x13B7, -0xAB88 => 0x13B8, -0xAB89 => 0x13B9, -0xAB8A => 0x13BA, -0xAB8B => 0x13BB, -0xAB8C => 0x13BC, -0xAB8D => 0x13BD, -0xAB8E => 0x13BE, -0xAB8F => 0x13BF, -0xAB90 => 0x13C0, -0xAB91 => 0x13C1, -0xAB92 => 0x13C2, -0xAB93 => 0x13C3, -0xAB94 => 0x13C4, -0xAB95 => 0x13C5, -0xAB96 => 0x13C6, -0xAB97 => 0x13C7, -0xAB98 => 0x13C8, -0xAB99 => 0x13C9, -0xAB9A => 0x13CA, -0xAB9B => 0x13CB, -0xAB9C => 0x13CC, -0xAB9D => 0x13CD, -0xAB9E => 0x13CE, -0xAB9F => 0x13CF, -0xABA0 => 0x13D0, -0xABA1 => 0x13D1, -0xABA2 => 0x13D2, -0xABA3 => 0x13D3, -0xABA4 => 0x13D4, -0xABA5 => 0x13D5, -0xABA6 => 0x13D6, -0xABA7 => 0x13D7, -0xABA8 => 0x13D8, -0xABA9 => 0x13D9, -0xABAA => 0x13DA, -0xABAB => 0x13DB, -0xABAC => 0x13DC, -0xABAD => 0x13DD, -0xABAE => 0x13DE, -0xABAF => 0x13DF, -0xABB0 => 0x13E0, -0xABB1 => 0x13E1, -0xABB2 => 0x13E2, -0xABB3 => 0x13E3, -0xABB4 => 0x13E4, -0xABB5 => 0x13E5, -0xABB6 => 0x13E6, -0xABB7 => 0x13E7, -0xABB8 => 0x13E8, -0xABB9 => 0x13E9, -0xABBA => 0x13EA, -0xABBB => 0x13EB, -0xABBC => 0x13EC, -0xABBD => 0x13ED, -0xABBE => 0x13EE, -0xABBF => 0x13EF, -0xFF21 => 0xFF41, -0xFF22 => 0xFF42, -0xFF23 => 0xFF43, -0xFF24 => 0xFF44, -0xFF25 => 0xFF45, -0xFF26 => 0xFF46, -0xFF27 => 0xFF47, -0xFF28 => 0xFF48, -0xFF29 => 0xFF49, -0xFF2A => 0xFF4A, -0xFF2B => 0xFF4B, -0xFF2C => 0xFF4C, -0xFF2D => 0xFF4D, -0xFF2E => 0xFF4E, -0xFF2F => 0xFF4F, -0xFF30 => 0xFF50, -0xFF31 => 0xFF51, -0xFF32 => 0xFF52, -0xFF33 => 0xFF53, -0xFF34 => 0xFF54, -0xFF35 => 0xFF55, -0xFF36 => 0xFF56, -0xFF37 => 0xFF57, -0xFF38 => 0xFF58, -0xFF39 => 0xFF59, -0xFF3A => 0xFF5A, -0x10400 => 0x10428, -0x10401 => 0x10429, -0x10402 => 0x1042A, -0x10403 => 0x1042B, -0x10404 => 0x1042C, -0x10405 => 0x1042D, -0x10406 => 0x1042E, -0x10407 => 0x1042F, -0x10408 => 0x10430, -0x10409 => 0x10431, -0x1040A => 0x10432, -0x1040B => 0x10433, -0x1040C => 0x10434, -0x1040D => 0x10435, -0x1040E => 0x10436, -0x1040F => 0x10437, -0x10410 => 0x10438, -0x10411 => 0x10439, -0x10412 => 0x1043A, -0x10413 => 0x1043B, -0x10414 => 0x1043C, -0x10415 => 0x1043D, -0x10416 => 0x1043E, -0x10417 => 0x1043F, -0x10418 => 0x10440, -0x10419 => 0x10441, -0x1041A => 0x10442, -0x1041B => 0x10443, -0x1041C => 0x10444, -0x1041D => 0x10445, -0x1041E => 0x10446, -0x1041F => 0x10447, -0x10420 => 0x10448, -0x10421 => 0x10449, -0x10422 => 0x1044A, -0x10423 => 0x1044B, -0x10424 => 0x1044C, -0x10425 => 0x1044D, -0x10426 => 0x1044E, -0x10427 => 0x1044F, -0x104B0 => 0x104D8, -0x104B1 => 0x104D9, -0x104B2 => 0x104DA, -0x104B3 => 0x104DB, -0x104B4 => 0x104DC, -0x104B5 => 0x104DD, -0x104B6 => 0x104DE, -0x104B7 => 0x104DF, -0x104B8 => 0x104E0, -0x104B9 => 0x104E1, -0x104BA => 0x104E2, -0x104BB => 0x104E3, -0x104BC => 0x104E4, -0x104BD => 0x104E5, -0x104BE => 0x104E6, -0x104BF => 0x104E7, -0x104C0 => 0x104E8, -0x104C1 => 0x104E9, -0x104C2 => 0x104EA, -0x104C3 => 0x104EB, -0x104C4 => 0x104EC, -0x104C5 => 0x104ED, -0x104C6 => 0x104EE, -0x104C7 => 0x104EF, -0x104C8 => 0x104F0, -0x104C9 => 0x104F1, -0x104CA => 0x104F2, -0x104CB => 0x104F3, -0x104CC => 0x104F4, -0x104CD => 0x104F5, -0x104CE => 0x104F6, -0x104CF => 0x104F7, -0x104D0 => 0x104F8, -0x104D1 => 0x104F9, -0x104D2 => 0x104FA, -0x104D3 => 0x104FB, -0x10C80 => 0x10CC0, -0x10C81 => 0x10CC1, -0x10C82 => 0x10CC2, -0x10C83 => 0x10CC3, -0x10C84 => 0x10CC4, -0x10C85 => 0x10CC5, -0x10C86 => 0x10CC6, -0x10C87 => 0x10CC7, -0x10C88 => 0x10CC8, -0x10C89 => 0x10CC9, -0x10C8A => 0x10CCA, -0x10C8B => 0x10CCB, -0x10C8C => 0x10CCC, -0x10C8D => 0x10CCD, -0x10C8E => 0x10CCE, -0x10C8F => 0x10CCF, -0x10C90 => 0x10CD0, -0x10C91 => 0x10CD1, -0x10C92 => 0x10CD2, -0x10C93 => 0x10CD3, -0x10C94 => 0x10CD4, -0x10C95 => 0x10CD5, -0x10C96 => 0x10CD6, -0x10C97 => 0x10CD7, -0x10C98 => 0x10CD8, -0x10C99 => 0x10CD9, -0x10C9A => 0x10CDA, -0x10C9B => 0x10CDB, -0x10C9C => 0x10CDC, -0x10C9D => 0x10CDD, -0x10C9E => 0x10CDE, -0x10C9F => 0x10CDF, -0x10CA0 => 0x10CE0, -0x10CA1 => 0x10CE1, -0x10CA2 => 0x10CE2, -0x10CA3 => 0x10CE3, -0x10CA4 => 0x10CE4, -0x10CA5 => 0x10CE5, -0x10CA6 => 0x10CE6, -0x10CA7 => 0x10CE7, -0x10CA8 => 0x10CE8, -0x10CA9 => 0x10CE9, -0x10CAA => 0x10CEA, -0x10CAB => 0x10CEB, -0x10CAC => 0x10CEC, -0x10CAD => 0x10CED, -0x10CAE => 0x10CEE, -0x10CAF => 0x10CEF, -0x10CB0 => 0x10CF0, -0x10CB1 => 0x10CF1, -0x10CB2 => 0x10CF2, -0x118A0 => 0x118C0, -0x118A1 => 0x118C1, -0x118A2 => 0x118C2, -0x118A3 => 0x118C3, -0x118A4 => 0x118C4, -0x118A5 => 0x118C5, -0x118A6 => 0x118C6, -0x118A7 => 0x118C7, -0x118A8 => 0x118C8, -0x118A9 => 0x118C9, -0x118AA => 0x118CA, -0x118AB => 0x118CB, -0x118AC => 0x118CC, -0x118AD => 0x118CD, -0x118AE => 0x118CE, -0x118AF => 0x118CF, -0x118B0 => 0x118D0, -0x118B1 => 0x118D1, -0x118B2 => 0x118D2, -0x118B3 => 0x118D3, -0x118B4 => 0x118D4, -0x118B5 => 0x118D5, -0x118B6 => 0x118D6, -0x118B7 => 0x118D7, -0x118B8 => 0x118D8, -0x118B9 => 0x118D9, -0x118BA => 0x118DA, -0x118BB => 0x118DB, -0x118BC => 0x118DC, -0x118BD => 0x118DD, -0x118BE => 0x118DE, -0x118BF => 0x118DF, -0x16E40 => 0x16E60, -0x16E41 => 0x16E61, -0x16E42 => 0x16E62, -0x16E43 => 0x16E63, -0x16E44 => 0x16E64, -0x16E45 => 0x16E65, -0x16E46 => 0x16E66, -0x16E47 => 0x16E67, -0x16E48 => 0x16E68, -0x16E49 => 0x16E69, -0x16E4A => 0x16E6A, -0x16E4B => 0x16E6B, -0x16E4C => 0x16E6C, -0x16E4D => 0x16E6D, -0x16E4E => 0x16E6E, -0x16E4F => 0x16E6F, -0x16E50 => 0x16E70, -0x16E51 => 0x16E71, -0x16E52 => 0x16E72, -0x16E53 => 0x16E73, -0x16E54 => 0x16E74, -0x16E55 => 0x16E75, -0x16E56 => 0x16E76, -0x16E57 => 0x16E77, -0x16E58 => 0x16E78, -0x16E59 => 0x16E79, -0x16E5A => 0x16E7A, -0x16E5B => 0x16E7B, -0x16E5C => 0x16E7C, -0x16E5D => 0x16E7D, -0x16E5E => 0x16E7E, -0x16E5F => 0x16E7F, -0x1E900 => 0x1E922, -0x1E901 => 0x1E923, -0x1E902 => 0x1E924, -0x1E903 => 0x1E925, -0x1E904 => 0x1E926, -0x1E905 => 0x1E927, -0x1E906 => 0x1E928, -0x1E907 => 0x1E929, -0x1E908 => 0x1E92A, -0x1E909 => 0x1E92B, -0x1E90A => 0x1E92C, -0x1E90B => 0x1E92D, -0x1E90C => 0x1E92E, -0x1E90D => 0x1E92F, -0x1E90E => 0x1E930, -0x1E90F => 0x1E931, -0x1E910 => 0x1E932, -0x1E911 => 0x1E933, -0x1E912 => 0x1E934, -0x1E913 => 0x1E935, -0x1E914 => 0x1E936, -0x1E915 => 0x1E937, -0x1E916 => 0x1E938, -0x1E917 => 0x1E939, -0x1E918 => 0x1E93A, -0x1E919 => 0x1E93B, -0x1E91A => 0x1E93C, -0x1E91B => 0x1E93D, -0x1E91C => 0x1E93E, -0x1E91D => 0x1E93F, -0x1E91E => 0x1E940, -0x1E91F => 0x1E941, -0x1E920 => 0x1E942, -0x1E921 => 0x1E943, -]; +return array( + 0x41 => 0x61, + 0x42 => 0x62, + 0x43 => 0x63, + 0x44 => 0x64, + 0x45 => 0x65, + 0x46 => 0x66, + 0x47 => 0x67, + 0x48 => 0x68, + 0x49 => 0x69, + 0x4A => 0x6A, + 0x4B => 0x6B, + 0x4C => 0x6C, + 0x4D => 0x6D, + 0x4E => 0x6E, + 0x4F => 0x6F, + 0x50 => 0x70, + 0x51 => 0x71, + 0x52 => 0x72, + 0x53 => 0x73, + 0x54 => 0x74, + 0x55 => 0x75, + 0x56 => 0x76, + 0x57 => 0x77, + 0x58 => 0x78, + 0x59 => 0x79, + 0x5A => 0x7A, + 0xB5 => 0x3BC, + 0xC0 => 0xE0, + 0xC1 => 0xE1, + 0xC2 => 0xE2, + 0xC3 => 0xE3, + 0xC4 => 0xE4, + 0xC5 => 0xE5, + 0xC6 => 0xE6, + 0xC7 => 0xE7, + 0xC8 => 0xE8, + 0xC9 => 0xE9, + 0xCA => 0xEA, + 0xCB => 0xEB, + 0xCC => 0xEC, + 0xCD => 0xED, + 0xCE => 0xEE, + 0xCF => 0xEF, + 0xD0 => 0xF0, + 0xD1 => 0xF1, + 0xD2 => 0xF2, + 0xD3 => 0xF3, + 0xD4 => 0xF4, + 0xD5 => 0xF5, + 0xD6 => 0xF6, + 0xD8 => 0xF8, + 0xD9 => 0xF9, + 0xDA => 0xFA, + 0xDB => 0xFB, + 0xDC => 0xFC, + 0xDD => 0xFD, + 0xDE => 0xFE, + 0x100 => 0x101, + 0x102 => 0x103, + 0x104 => 0x105, + 0x106 => 0x107, + 0x108 => 0x109, + 0x10A => 0x10B, + 0x10C => 0x10D, + 0x10E => 0x10F, + 0x110 => 0x111, + 0x112 => 0x113, + 0x114 => 0x115, + 0x116 => 0x117, + 0x118 => 0x119, + 0x11A => 0x11B, + 0x11C => 0x11D, + 0x11E => 0x11F, + 0x120 => 0x121, + 0x122 => 0x123, + 0x124 => 0x125, + 0x126 => 0x127, + 0x128 => 0x129, + 0x12A => 0x12B, + 0x12C => 0x12D, + 0x12E => 0x12F, + 0x132 => 0x133, + 0x134 => 0x135, + 0x136 => 0x137, + 0x139 => 0x13A, + 0x13B => 0x13C, + 0x13D => 0x13E, + 0x13F => 0x140, + 0x141 => 0x142, + 0x143 => 0x144, + 0x145 => 0x146, + 0x147 => 0x148, + 0x14A => 0x14B, + 0x14C => 0x14D, + 0x14E => 0x14F, + 0x150 => 0x151, + 0x152 => 0x153, + 0x154 => 0x155, + 0x156 => 0x157, + 0x158 => 0x159, + 0x15A => 0x15B, + 0x15C => 0x15D, + 0x15E => 0x15F, + 0x160 => 0x161, + 0x162 => 0x163, + 0x164 => 0x165, + 0x166 => 0x167, + 0x168 => 0x169, + 0x16A => 0x16B, + 0x16C => 0x16D, + 0x16E => 0x16F, + 0x170 => 0x171, + 0x172 => 0x173, + 0x174 => 0x175, + 0x176 => 0x177, + 0x178 => 0xFF, + 0x179 => 0x17A, + 0x17B => 0x17C, + 0x17D => 0x17E, + 0x17F => 0x73, + 0x181 => 0x253, + 0x182 => 0x183, + 0x184 => 0x185, + 0x186 => 0x254, + 0x187 => 0x188, + 0x189 => 0x256, + 0x18A => 0x257, + 0x18B => 0x18C, + 0x18E => 0x1DD, + 0x18F => 0x259, + 0x190 => 0x25B, + 0x191 => 0x192, + 0x193 => 0x260, + 0x194 => 0x263, + 0x196 => 0x269, + 0x197 => 0x268, + 0x198 => 0x199, + 0x19C => 0x26F, + 0x19D => 0x272, + 0x19F => 0x275, + 0x1A0 => 0x1A1, + 0x1A2 => 0x1A3, + 0x1A4 => 0x1A5, + 0x1A6 => 0x280, + 0x1A7 => 0x1A8, + 0x1A9 => 0x283, + 0x1AC => 0x1AD, + 0x1AE => 0x288, + 0x1AF => 0x1B0, + 0x1B1 => 0x28A, + 0x1B2 => 0x28B, + 0x1B3 => 0x1B4, + 0x1B5 => 0x1B6, + 0x1B7 => 0x292, + 0x1B8 => 0x1B9, + 0x1BC => 0x1BD, + 0x1C4 => 0x1C6, + 0x1C5 => 0x1C6, + 0x1C7 => 0x1C9, + 0x1C8 => 0x1C9, + 0x1CA => 0x1CC, + 0x1CB => 0x1CC, + 0x1CD => 0x1CE, + 0x1CF => 0x1D0, + 0x1D1 => 0x1D2, + 0x1D3 => 0x1D4, + 0x1D5 => 0x1D6, + 0x1D7 => 0x1D8, + 0x1D9 => 0x1DA, + 0x1DB => 0x1DC, + 0x1DE => 0x1DF, + 0x1E0 => 0x1E1, + 0x1E2 => 0x1E3, + 0x1E4 => 0x1E5, + 0x1E6 => 0x1E7, + 0x1E8 => 0x1E9, + 0x1EA => 0x1EB, + 0x1EC => 0x1ED, + 0x1EE => 0x1EF, + 0x1F1 => 0x1F3, + 0x1F2 => 0x1F3, + 0x1F4 => 0x1F5, + 0x1F6 => 0x195, + 0x1F7 => 0x1BF, + 0x1F8 => 0x1F9, + 0x1FA => 0x1FB, + 0x1FC => 0x1FD, + 0x1FE => 0x1FF, + 0x200 => 0x201, + 0x202 => 0x203, + 0x204 => 0x205, + 0x206 => 0x207, + 0x208 => 0x209, + 0x20A => 0x20B, + 0x20C => 0x20D, + 0x20E => 0x20F, + 0x210 => 0x211, + 0x212 => 0x213, + 0x214 => 0x215, + 0x216 => 0x217, + 0x218 => 0x219, + 0x21A => 0x21B, + 0x21C => 0x21D, + 0x21E => 0x21F, + 0x220 => 0x19E, + 0x222 => 0x223, + 0x224 => 0x225, + 0x226 => 0x227, + 0x228 => 0x229, + 0x22A => 0x22B, + 0x22C => 0x22D, + 0x22E => 0x22F, + 0x230 => 0x231, + 0x232 => 0x233, + 0x23A => 0x2C65, + 0x23B => 0x23C, + 0x23D => 0x19A, + 0x23E => 0x2C66, + 0x241 => 0x242, + 0x243 => 0x180, + 0x244 => 0x289, + 0x245 => 0x28C, + 0x246 => 0x247, + 0x248 => 0x249, + 0x24A => 0x24B, + 0x24C => 0x24D, + 0x24E => 0x24F, + 0x345 => 0x3B9, + 0x370 => 0x371, + 0x372 => 0x373, + 0x376 => 0x377, + 0x37F => 0x3F3, + 0x386 => 0x3AC, + 0x388 => 0x3AD, + 0x389 => 0x3AE, + 0x38A => 0x3AF, + 0x38C => 0x3CC, + 0x38E => 0x3CD, + 0x38F => 0x3CE, + 0x391 => 0x3B1, + 0x392 => 0x3B2, + 0x393 => 0x3B3, + 0x394 => 0x3B4, + 0x395 => 0x3B5, + 0x396 => 0x3B6, + 0x397 => 0x3B7, + 0x398 => 0x3B8, + 0x399 => 0x3B9, + 0x39A => 0x3BA, + 0x39B => 0x3BB, + 0x39C => 0x3BC, + 0x39D => 0x3BD, + 0x39E => 0x3BE, + 0x39F => 0x3BF, + 0x3A0 => 0x3C0, + 0x3A1 => 0x3C1, + 0x3A3 => 0x3C3, + 0x3A4 => 0x3C4, + 0x3A5 => 0x3C5, + 0x3A6 => 0x3C6, + 0x3A7 => 0x3C7, + 0x3A8 => 0x3C8, + 0x3A9 => 0x3C9, + 0x3AA => 0x3CA, + 0x3AB => 0x3CB, + 0x3C2 => 0x3C3, + 0x3CF => 0x3D7, + 0x3D0 => 0x3B2, + 0x3D1 => 0x3B8, + 0x3D5 => 0x3C6, + 0x3D6 => 0x3C0, + 0x3D8 => 0x3D9, + 0x3DA => 0x3DB, + 0x3DC => 0x3DD, + 0x3DE => 0x3DF, + 0x3E0 => 0x3E1, + 0x3E2 => 0x3E3, + 0x3E4 => 0x3E5, + 0x3E6 => 0x3E7, + 0x3E8 => 0x3E9, + 0x3EA => 0x3EB, + 0x3EC => 0x3ED, + 0x3EE => 0x3EF, + 0x3F0 => 0x3BA, + 0x3F1 => 0x3C1, + 0x3F4 => 0x3B8, + 0x3F5 => 0x3B5, + 0x3F7 => 0x3F8, + 0x3F9 => 0x3F2, + 0x3FA => 0x3FB, + 0x3FD => 0x37B, + 0x3FE => 0x37C, + 0x3FF => 0x37D, + 0x400 => 0x450, + 0x401 => 0x451, + 0x402 => 0x452, + 0x403 => 0x453, + 0x404 => 0x454, + 0x405 => 0x455, + 0x406 => 0x456, + 0x407 => 0x457, + 0x408 => 0x458, + 0x409 => 0x459, + 0x40A => 0x45A, + 0x40B => 0x45B, + 0x40C => 0x45C, + 0x40D => 0x45D, + 0x40E => 0x45E, + 0x40F => 0x45F, + 0x410 => 0x430, + 0x411 => 0x431, + 0x412 => 0x432, + 0x413 => 0x433, + 0x414 => 0x434, + 0x415 => 0x435, + 0x416 => 0x436, + 0x417 => 0x437, + 0x418 => 0x438, + 0x419 => 0x439, + 0x41A => 0x43A, + 0x41B => 0x43B, + 0x41C => 0x43C, + 0x41D => 0x43D, + 0x41E => 0x43E, + 0x41F => 0x43F, + 0x420 => 0x440, + 0x421 => 0x441, + 0x422 => 0x442, + 0x423 => 0x443, + 0x424 => 0x444, + 0x425 => 0x445, + 0x426 => 0x446, + 0x427 => 0x447, + 0x428 => 0x448, + 0x429 => 0x449, + 0x42A => 0x44A, + 0x42B => 0x44B, + 0x42C => 0x44C, + 0x42D => 0x44D, + 0x42E => 0x44E, + 0x42F => 0x44F, + 0x460 => 0x461, + 0x462 => 0x463, + 0x464 => 0x465, + 0x466 => 0x467, + 0x468 => 0x469, + 0x46A => 0x46B, + 0x46C => 0x46D, + 0x46E => 0x46F, + 0x470 => 0x471, + 0x472 => 0x473, + 0x474 => 0x475, + 0x476 => 0x477, + 0x478 => 0x479, + 0x47A => 0x47B, + 0x47C => 0x47D, + 0x47E => 0x47F, + 0x480 => 0x481, + 0x48A => 0x48B, + 0x48C => 0x48D, + 0x48E => 0x48F, + 0x490 => 0x491, + 0x492 => 0x493, + 0x494 => 0x495, + 0x496 => 0x497, + 0x498 => 0x499, + 0x49A => 0x49B, + 0x49C => 0x49D, + 0x49E => 0x49F, + 0x4A0 => 0x4A1, + 0x4A2 => 0x4A3, + 0x4A4 => 0x4A5, + 0x4A6 => 0x4A7, + 0x4A8 => 0x4A9, + 0x4AA => 0x4AB, + 0x4AC => 0x4AD, + 0x4AE => 0x4AF, + 0x4B0 => 0x4B1, + 0x4B2 => 0x4B3, + 0x4B4 => 0x4B5, + 0x4B6 => 0x4B7, + 0x4B8 => 0x4B9, + 0x4BA => 0x4BB, + 0x4BC => 0x4BD, + 0x4BE => 0x4BF, + 0x4C0 => 0x4CF, + 0x4C1 => 0x4C2, + 0x4C3 => 0x4C4, + 0x4C5 => 0x4C6, + 0x4C7 => 0x4C8, + 0x4C9 => 0x4CA, + 0x4CB => 0x4CC, + 0x4CD => 0x4CE, + 0x4D0 => 0x4D1, + 0x4D2 => 0x4D3, + 0x4D4 => 0x4D5, + 0x4D6 => 0x4D7, + 0x4D8 => 0x4D9, + 0x4DA => 0x4DB, + 0x4DC => 0x4DD, + 0x4DE => 0x4DF, + 0x4E0 => 0x4E1, + 0x4E2 => 0x4E3, + 0x4E4 => 0x4E5, + 0x4E6 => 0x4E7, + 0x4E8 => 0x4E9, + 0x4EA => 0x4EB, + 0x4EC => 0x4ED, + 0x4EE => 0x4EF, + 0x4F0 => 0x4F1, + 0x4F2 => 0x4F3, + 0x4F4 => 0x4F5, + 0x4F6 => 0x4F7, + 0x4F8 => 0x4F9, + 0x4FA => 0x4FB, + 0x4FC => 0x4FD, + 0x4FE => 0x4FF, + 0x500 => 0x501, + 0x502 => 0x503, + 0x504 => 0x505, + 0x506 => 0x507, + 0x508 => 0x509, + 0x50A => 0x50B, + 0x50C => 0x50D, + 0x50E => 0x50F, + 0x510 => 0x511, + 0x512 => 0x513, + 0x514 => 0x515, + 0x516 => 0x517, + 0x518 => 0x519, + 0x51A => 0x51B, + 0x51C => 0x51D, + 0x51E => 0x51F, + 0x520 => 0x521, + 0x522 => 0x523, + 0x524 => 0x525, + 0x526 => 0x527, + 0x528 => 0x529, + 0x52A => 0x52B, + 0x52C => 0x52D, + 0x52E => 0x52F, + 0x531 => 0x561, + 0x532 => 0x562, + 0x533 => 0x563, + 0x534 => 0x564, + 0x535 => 0x565, + 0x536 => 0x566, + 0x537 => 0x567, + 0x538 => 0x568, + 0x539 => 0x569, + 0x53A => 0x56A, + 0x53B => 0x56B, + 0x53C => 0x56C, + 0x53D => 0x56D, + 0x53E => 0x56E, + 0x53F => 0x56F, + 0x540 => 0x570, + 0x541 => 0x571, + 0x542 => 0x572, + 0x543 => 0x573, + 0x544 => 0x574, + 0x545 => 0x575, + 0x546 => 0x576, + 0x547 => 0x577, + 0x548 => 0x578, + 0x549 => 0x579, + 0x54A => 0x57A, + 0x54B => 0x57B, + 0x54C => 0x57C, + 0x54D => 0x57D, + 0x54E => 0x57E, + 0x54F => 0x57F, + 0x550 => 0x580, + 0x551 => 0x581, + 0x552 => 0x582, + 0x553 => 0x583, + 0x554 => 0x584, + 0x555 => 0x585, + 0x556 => 0x586, + 0x10A0 => 0x2D00, + 0x10A1 => 0x2D01, + 0x10A2 => 0x2D02, + 0x10A3 => 0x2D03, + 0x10A4 => 0x2D04, + 0x10A5 => 0x2D05, + 0x10A6 => 0x2D06, + 0x10A7 => 0x2D07, + 0x10A8 => 0x2D08, + 0x10A9 => 0x2D09, + 0x10AA => 0x2D0A, + 0x10AB => 0x2D0B, + 0x10AC => 0x2D0C, + 0x10AD => 0x2D0D, + 0x10AE => 0x2D0E, + 0x10AF => 0x2D0F, + 0x10B0 => 0x2D10, + 0x10B1 => 0x2D11, + 0x10B2 => 0x2D12, + 0x10B3 => 0x2D13, + 0x10B4 => 0x2D14, + 0x10B5 => 0x2D15, + 0x10B6 => 0x2D16, + 0x10B7 => 0x2D17, + 0x10B8 => 0x2D18, + 0x10B9 => 0x2D19, + 0x10BA => 0x2D1A, + 0x10BB => 0x2D1B, + 0x10BC => 0x2D1C, + 0x10BD => 0x2D1D, + 0x10BE => 0x2D1E, + 0x10BF => 0x2D1F, + 0x10C0 => 0x2D20, + 0x10C1 => 0x2D21, + 0x10C2 => 0x2D22, + 0x10C3 => 0x2D23, + 0x10C4 => 0x2D24, + 0x10C5 => 0x2D25, + 0x10C7 => 0x2D27, + 0x10CD => 0x2D2D, + 0x13F8 => 0x13F0, + 0x13F9 => 0x13F1, + 0x13FA => 0x13F2, + 0x13FB => 0x13F3, + 0x13FC => 0x13F4, + 0x13FD => 0x13F5, + 0x1C80 => 0x432, + 0x1C81 => 0x434, + 0x1C82 => 0x43E, + 0x1C83 => 0x441, + 0x1C84 => 0x442, + 0x1C85 => 0x442, + 0x1C86 => 0x44A, + 0x1C87 => 0x463, + 0x1C88 => 0xA64B, + 0x1C90 => 0x10D0, + 0x1C91 => 0x10D1, + 0x1C92 => 0x10D2, + 0x1C93 => 0x10D3, + 0x1C94 => 0x10D4, + 0x1C95 => 0x10D5, + 0x1C96 => 0x10D6, + 0x1C97 => 0x10D7, + 0x1C98 => 0x10D8, + 0x1C99 => 0x10D9, + 0x1C9A => 0x10DA, + 0x1C9B => 0x10DB, + 0x1C9C => 0x10DC, + 0x1C9D => 0x10DD, + 0x1C9E => 0x10DE, + 0x1C9F => 0x10DF, + 0x1CA0 => 0x10E0, + 0x1CA1 => 0x10E1, + 0x1CA2 => 0x10E2, + 0x1CA3 => 0x10E3, + 0x1CA4 => 0x10E4, + 0x1CA5 => 0x10E5, + 0x1CA6 => 0x10E6, + 0x1CA7 => 0x10E7, + 0x1CA8 => 0x10E8, + 0x1CA9 => 0x10E9, + 0x1CAA => 0x10EA, + 0x1CAB => 0x10EB, + 0x1CAC => 0x10EC, + 0x1CAD => 0x10ED, + 0x1CAE => 0x10EE, + 0x1CAF => 0x10EF, + 0x1CB0 => 0x10F0, + 0x1CB1 => 0x10F1, + 0x1CB2 => 0x10F2, + 0x1CB3 => 0x10F3, + 0x1CB4 => 0x10F4, + 0x1CB5 => 0x10F5, + 0x1CB6 => 0x10F6, + 0x1CB7 => 0x10F7, + 0x1CB8 => 0x10F8, + 0x1CB9 => 0x10F9, + 0x1CBA => 0x10FA, + 0x1CBD => 0x10FD, + 0x1CBE => 0x10FE, + 0x1CBF => 0x10FF, + 0x1E00 => 0x1E01, + 0x1E02 => 0x1E03, + 0x1E04 => 0x1E05, + 0x1E06 => 0x1E07, + 0x1E08 => 0x1E09, + 0x1E0A => 0x1E0B, + 0x1E0C => 0x1E0D, + 0x1E0E => 0x1E0F, + 0x1E10 => 0x1E11, + 0x1E12 => 0x1E13, + 0x1E14 => 0x1E15, + 0x1E16 => 0x1E17, + 0x1E18 => 0x1E19, + 0x1E1A => 0x1E1B, + 0x1E1C => 0x1E1D, + 0x1E1E => 0x1E1F, + 0x1E20 => 0x1E21, + 0x1E22 => 0x1E23, + 0x1E24 => 0x1E25, + 0x1E26 => 0x1E27, + 0x1E28 => 0x1E29, + 0x1E2A => 0x1E2B, + 0x1E2C => 0x1E2D, + 0x1E2E => 0x1E2F, + 0x1E30 => 0x1E31, + 0x1E32 => 0x1E33, + 0x1E34 => 0x1E35, + 0x1E36 => 0x1E37, + 0x1E38 => 0x1E39, + 0x1E3A => 0x1E3B, + 0x1E3C => 0x1E3D, + 0x1E3E => 0x1E3F, + 0x1E40 => 0x1E41, + 0x1E42 => 0x1E43, + 0x1E44 => 0x1E45, + 0x1E46 => 0x1E47, + 0x1E48 => 0x1E49, + 0x1E4A => 0x1E4B, + 0x1E4C => 0x1E4D, + 0x1E4E => 0x1E4F, + 0x1E50 => 0x1E51, + 0x1E52 => 0x1E53, + 0x1E54 => 0x1E55, + 0x1E56 => 0x1E57, + 0x1E58 => 0x1E59, + 0x1E5A => 0x1E5B, + 0x1E5C => 0x1E5D, + 0x1E5E => 0x1E5F, + 0x1E60 => 0x1E61, + 0x1E62 => 0x1E63, + 0x1E64 => 0x1E65, + 0x1E66 => 0x1E67, + 0x1E68 => 0x1E69, + 0x1E6A => 0x1E6B, + 0x1E6C => 0x1E6D, + 0x1E6E => 0x1E6F, + 0x1E70 => 0x1E71, + 0x1E72 => 0x1E73, + 0x1E74 => 0x1E75, + 0x1E76 => 0x1E77, + 0x1E78 => 0x1E79, + 0x1E7A => 0x1E7B, + 0x1E7C => 0x1E7D, + 0x1E7E => 0x1E7F, + 0x1E80 => 0x1E81, + 0x1E82 => 0x1E83, + 0x1E84 => 0x1E85, + 0x1E86 => 0x1E87, + 0x1E88 => 0x1E89, + 0x1E8A => 0x1E8B, + 0x1E8C => 0x1E8D, + 0x1E8E => 0x1E8F, + 0x1E90 => 0x1E91, + 0x1E92 => 0x1E93, + 0x1E94 => 0x1E95, + 0x1E9B => 0x1E61, + 0x1E9E => 0xDF, + 0x1EA0 => 0x1EA1, + 0x1EA2 => 0x1EA3, + 0x1EA4 => 0x1EA5, + 0x1EA6 => 0x1EA7, + 0x1EA8 => 0x1EA9, + 0x1EAA => 0x1EAB, + 0x1EAC => 0x1EAD, + 0x1EAE => 0x1EAF, + 0x1EB0 => 0x1EB1, + 0x1EB2 => 0x1EB3, + 0x1EB4 => 0x1EB5, + 0x1EB6 => 0x1EB7, + 0x1EB8 => 0x1EB9, + 0x1EBA => 0x1EBB, + 0x1EBC => 0x1EBD, + 0x1EBE => 0x1EBF, + 0x1EC0 => 0x1EC1, + 0x1EC2 => 0x1EC3, + 0x1EC4 => 0x1EC5, + 0x1EC6 => 0x1EC7, + 0x1EC8 => 0x1EC9, + 0x1ECA => 0x1ECB, + 0x1ECC => 0x1ECD, + 0x1ECE => 0x1ECF, + 0x1ED0 => 0x1ED1, + 0x1ED2 => 0x1ED3, + 0x1ED4 => 0x1ED5, + 0x1ED6 => 0x1ED7, + 0x1ED8 => 0x1ED9, + 0x1EDA => 0x1EDB, + 0x1EDC => 0x1EDD, + 0x1EDE => 0x1EDF, + 0x1EE0 => 0x1EE1, + 0x1EE2 => 0x1EE3, + 0x1EE4 => 0x1EE5, + 0x1EE6 => 0x1EE7, + 0x1EE8 => 0x1EE9, + 0x1EEA => 0x1EEB, + 0x1EEC => 0x1EED, + 0x1EEE => 0x1EEF, + 0x1EF0 => 0x1EF1, + 0x1EF2 => 0x1EF3, + 0x1EF4 => 0x1EF5, + 0x1EF6 => 0x1EF7, + 0x1EF8 => 0x1EF9, + 0x1EFA => 0x1EFB, + 0x1EFC => 0x1EFD, + 0x1EFE => 0x1EFF, + 0x1F08 => 0x1F00, + 0x1F09 => 0x1F01, + 0x1F0A => 0x1F02, + 0x1F0B => 0x1F03, + 0x1F0C => 0x1F04, + 0x1F0D => 0x1F05, + 0x1F0E => 0x1F06, + 0x1F0F => 0x1F07, + 0x1F18 => 0x1F10, + 0x1F19 => 0x1F11, + 0x1F1A => 0x1F12, + 0x1F1B => 0x1F13, + 0x1F1C => 0x1F14, + 0x1F1D => 0x1F15, + 0x1F28 => 0x1F20, + 0x1F29 => 0x1F21, + 0x1F2A => 0x1F22, + 0x1F2B => 0x1F23, + 0x1F2C => 0x1F24, + 0x1F2D => 0x1F25, + 0x1F2E => 0x1F26, + 0x1F2F => 0x1F27, + 0x1F38 => 0x1F30, + 0x1F39 => 0x1F31, + 0x1F3A => 0x1F32, + 0x1F3B => 0x1F33, + 0x1F3C => 0x1F34, + 0x1F3D => 0x1F35, + 0x1F3E => 0x1F36, + 0x1F3F => 0x1F37, + 0x1F48 => 0x1F40, + 0x1F49 => 0x1F41, + 0x1F4A => 0x1F42, + 0x1F4B => 0x1F43, + 0x1F4C => 0x1F44, + 0x1F4D => 0x1F45, + 0x1F59 => 0x1F51, + 0x1F5B => 0x1F53, + 0x1F5D => 0x1F55, + 0x1F5F => 0x1F57, + 0x1F68 => 0x1F60, + 0x1F69 => 0x1F61, + 0x1F6A => 0x1F62, + 0x1F6B => 0x1F63, + 0x1F6C => 0x1F64, + 0x1F6D => 0x1F65, + 0x1F6E => 0x1F66, + 0x1F6F => 0x1F67, + 0x1F88 => 0x1F80, + 0x1F89 => 0x1F81, + 0x1F8A => 0x1F82, + 0x1F8B => 0x1F83, + 0x1F8C => 0x1F84, + 0x1F8D => 0x1F85, + 0x1F8E => 0x1F86, + 0x1F8F => 0x1F87, + 0x1F98 => 0x1F90, + 0x1F99 => 0x1F91, + 0x1F9A => 0x1F92, + 0x1F9B => 0x1F93, + 0x1F9C => 0x1F94, + 0x1F9D => 0x1F95, + 0x1F9E => 0x1F96, + 0x1F9F => 0x1F97, + 0x1FA8 => 0x1FA0, + 0x1FA9 => 0x1FA1, + 0x1FAA => 0x1FA2, + 0x1FAB => 0x1FA3, + 0x1FAC => 0x1FA4, + 0x1FAD => 0x1FA5, + 0x1FAE => 0x1FA6, + 0x1FAF => 0x1FA7, + 0x1FB8 => 0x1FB0, + 0x1FB9 => 0x1FB1, + 0x1FBA => 0x1F70, + 0x1FBB => 0x1F71, + 0x1FBC => 0x1FB3, + 0x1FBE => 0x3B9, + 0x1FC8 => 0x1F72, + 0x1FC9 => 0x1F73, + 0x1FCA => 0x1F74, + 0x1FCB => 0x1F75, + 0x1FCC => 0x1FC3, + 0x1FD8 => 0x1FD0, + 0x1FD9 => 0x1FD1, + 0x1FDA => 0x1F76, + 0x1FDB => 0x1F77, + 0x1FE8 => 0x1FE0, + 0x1FE9 => 0x1FE1, + 0x1FEA => 0x1F7A, + 0x1FEB => 0x1F7B, + 0x1FEC => 0x1FE5, + 0x1FF8 => 0x1F78, + 0x1FF9 => 0x1F79, + 0x1FFA => 0x1F7C, + 0x1FFB => 0x1F7D, + 0x1FFC => 0x1FF3, + 0x2126 => 0x3C9, + 0x212A => 0x6B, + 0x212B => 0xE5, + 0x2132 => 0x214E, + 0x2160 => 0x2170, + 0x2161 => 0x2171, + 0x2162 => 0x2172, + 0x2163 => 0x2173, + 0x2164 => 0x2174, + 0x2165 => 0x2175, + 0x2166 => 0x2176, + 0x2167 => 0x2177, + 0x2168 => 0x2178, + 0x2169 => 0x2179, + 0x216A => 0x217A, + 0x216B => 0x217B, + 0x216C => 0x217C, + 0x216D => 0x217D, + 0x216E => 0x217E, + 0x216F => 0x217F, + 0x2183 => 0x2184, + 0x24B6 => 0x24D0, + 0x24B7 => 0x24D1, + 0x24B8 => 0x24D2, + 0x24B9 => 0x24D3, + 0x24BA => 0x24D4, + 0x24BB => 0x24D5, + 0x24BC => 0x24D6, + 0x24BD => 0x24D7, + 0x24BE => 0x24D8, + 0x24BF => 0x24D9, + 0x24C0 => 0x24DA, + 0x24C1 => 0x24DB, + 0x24C2 => 0x24DC, + 0x24C3 => 0x24DD, + 0x24C4 => 0x24DE, + 0x24C5 => 0x24DF, + 0x24C6 => 0x24E0, + 0x24C7 => 0x24E1, + 0x24C8 => 0x24E2, + 0x24C9 => 0x24E3, + 0x24CA => 0x24E4, + 0x24CB => 0x24E5, + 0x24CC => 0x24E6, + 0x24CD => 0x24E7, + 0x24CE => 0x24E8, + 0x24CF => 0x24E9, + 0x2C00 => 0x2C30, + 0x2C01 => 0x2C31, + 0x2C02 => 0x2C32, + 0x2C03 => 0x2C33, + 0x2C04 => 0x2C34, + 0x2C05 => 0x2C35, + 0x2C06 => 0x2C36, + 0x2C07 => 0x2C37, + 0x2C08 => 0x2C38, + 0x2C09 => 0x2C39, + 0x2C0A => 0x2C3A, + 0x2C0B => 0x2C3B, + 0x2C0C => 0x2C3C, + 0x2C0D => 0x2C3D, + 0x2C0E => 0x2C3E, + 0x2C0F => 0x2C3F, + 0x2C10 => 0x2C40, + 0x2C11 => 0x2C41, + 0x2C12 => 0x2C42, + 0x2C13 => 0x2C43, + 0x2C14 => 0x2C44, + 0x2C15 => 0x2C45, + 0x2C16 => 0x2C46, + 0x2C17 => 0x2C47, + 0x2C18 => 0x2C48, + 0x2C19 => 0x2C49, + 0x2C1A => 0x2C4A, + 0x2C1B => 0x2C4B, + 0x2C1C => 0x2C4C, + 0x2C1D => 0x2C4D, + 0x2C1E => 0x2C4E, + 0x2C1F => 0x2C4F, + 0x2C20 => 0x2C50, + 0x2C21 => 0x2C51, + 0x2C22 => 0x2C52, + 0x2C23 => 0x2C53, + 0x2C24 => 0x2C54, + 0x2C25 => 0x2C55, + 0x2C26 => 0x2C56, + 0x2C27 => 0x2C57, + 0x2C28 => 0x2C58, + 0x2C29 => 0x2C59, + 0x2C2A => 0x2C5A, + 0x2C2B => 0x2C5B, + 0x2C2C => 0x2C5C, + 0x2C2D => 0x2C5D, + 0x2C2E => 0x2C5E, + 0x2C60 => 0x2C61, + 0x2C62 => 0x26B, + 0x2C63 => 0x1D7D, + 0x2C64 => 0x27D, + 0x2C67 => 0x2C68, + 0x2C69 => 0x2C6A, + 0x2C6B => 0x2C6C, + 0x2C6D => 0x251, + 0x2C6E => 0x271, + 0x2C6F => 0x250, + 0x2C70 => 0x252, + 0x2C72 => 0x2C73, + 0x2C75 => 0x2C76, + 0x2C7E => 0x23F, + 0x2C7F => 0x240, + 0x2C80 => 0x2C81, + 0x2C82 => 0x2C83, + 0x2C84 => 0x2C85, + 0x2C86 => 0x2C87, + 0x2C88 => 0x2C89, + 0x2C8A => 0x2C8B, + 0x2C8C => 0x2C8D, + 0x2C8E => 0x2C8F, + 0x2C90 => 0x2C91, + 0x2C92 => 0x2C93, + 0x2C94 => 0x2C95, + 0x2C96 => 0x2C97, + 0x2C98 => 0x2C99, + 0x2C9A => 0x2C9B, + 0x2C9C => 0x2C9D, + 0x2C9E => 0x2C9F, + 0x2CA0 => 0x2CA1, + 0x2CA2 => 0x2CA3, + 0x2CA4 => 0x2CA5, + 0x2CA6 => 0x2CA7, + 0x2CA8 => 0x2CA9, + 0x2CAA => 0x2CAB, + 0x2CAC => 0x2CAD, + 0x2CAE => 0x2CAF, + 0x2CB0 => 0x2CB1, + 0x2CB2 => 0x2CB3, + 0x2CB4 => 0x2CB5, + 0x2CB6 => 0x2CB7, + 0x2CB8 => 0x2CB9, + 0x2CBA => 0x2CBB, + 0x2CBC => 0x2CBD, + 0x2CBE => 0x2CBF, + 0x2CC0 => 0x2CC1, + 0x2CC2 => 0x2CC3, + 0x2CC4 => 0x2CC5, + 0x2CC6 => 0x2CC7, + 0x2CC8 => 0x2CC9, + 0x2CCA => 0x2CCB, + 0x2CCC => 0x2CCD, + 0x2CCE => 0x2CCF, + 0x2CD0 => 0x2CD1, + 0x2CD2 => 0x2CD3, + 0x2CD4 => 0x2CD5, + 0x2CD6 => 0x2CD7, + 0x2CD8 => 0x2CD9, + 0x2CDA => 0x2CDB, + 0x2CDC => 0x2CDD, + 0x2CDE => 0x2CDF, + 0x2CE0 => 0x2CE1, + 0x2CE2 => 0x2CE3, + 0x2CEB => 0x2CEC, + 0x2CED => 0x2CEE, + 0x2CF2 => 0x2CF3, + 0xA640 => 0xA641, + 0xA642 => 0xA643, + 0xA644 => 0xA645, + 0xA646 => 0xA647, + 0xA648 => 0xA649, + 0xA64A => 0xA64B, + 0xA64C => 0xA64D, + 0xA64E => 0xA64F, + 0xA650 => 0xA651, + 0xA652 => 0xA653, + 0xA654 => 0xA655, + 0xA656 => 0xA657, + 0xA658 => 0xA659, + 0xA65A => 0xA65B, + 0xA65C => 0xA65D, + 0xA65E => 0xA65F, + 0xA660 => 0xA661, + 0xA662 => 0xA663, + 0xA664 => 0xA665, + 0xA666 => 0xA667, + 0xA668 => 0xA669, + 0xA66A => 0xA66B, + 0xA66C => 0xA66D, + 0xA680 => 0xA681, + 0xA682 => 0xA683, + 0xA684 => 0xA685, + 0xA686 => 0xA687, + 0xA688 => 0xA689, + 0xA68A => 0xA68B, + 0xA68C => 0xA68D, + 0xA68E => 0xA68F, + 0xA690 => 0xA691, + 0xA692 => 0xA693, + 0xA694 => 0xA695, + 0xA696 => 0xA697, + 0xA698 => 0xA699, + 0xA69A => 0xA69B, + 0xA722 => 0xA723, + 0xA724 => 0xA725, + 0xA726 => 0xA727, + 0xA728 => 0xA729, + 0xA72A => 0xA72B, + 0xA72C => 0xA72D, + 0xA72E => 0xA72F, + 0xA732 => 0xA733, + 0xA734 => 0xA735, + 0xA736 => 0xA737, + 0xA738 => 0xA739, + 0xA73A => 0xA73B, + 0xA73C => 0xA73D, + 0xA73E => 0xA73F, + 0xA740 => 0xA741, + 0xA742 => 0xA743, + 0xA744 => 0xA745, + 0xA746 => 0xA747, + 0xA748 => 0xA749, + 0xA74A => 0xA74B, + 0xA74C => 0xA74D, + 0xA74E => 0xA74F, + 0xA750 => 0xA751, + 0xA752 => 0xA753, + 0xA754 => 0xA755, + 0xA756 => 0xA757, + 0xA758 => 0xA759, + 0xA75A => 0xA75B, + 0xA75C => 0xA75D, + 0xA75E => 0xA75F, + 0xA760 => 0xA761, + 0xA762 => 0xA763, + 0xA764 => 0xA765, + 0xA766 => 0xA767, + 0xA768 => 0xA769, + 0xA76A => 0xA76B, + 0xA76C => 0xA76D, + 0xA76E => 0xA76F, + 0xA779 => 0xA77A, + 0xA77B => 0xA77C, + 0xA77D => 0x1D79, + 0xA77E => 0xA77F, + 0xA780 => 0xA781, + 0xA782 => 0xA783, + 0xA784 => 0xA785, + 0xA786 => 0xA787, + 0xA78B => 0xA78C, + 0xA78D => 0x265, + 0xA790 => 0xA791, + 0xA792 => 0xA793, + 0xA796 => 0xA797, + 0xA798 => 0xA799, + 0xA79A => 0xA79B, + 0xA79C => 0xA79D, + 0xA79E => 0xA79F, + 0xA7A0 => 0xA7A1, + 0xA7A2 => 0xA7A3, + 0xA7A4 => 0xA7A5, + 0xA7A6 => 0xA7A7, + 0xA7A8 => 0xA7A9, + 0xA7AA => 0x266, + 0xA7AB => 0x25C, + 0xA7AC => 0x261, + 0xA7AD => 0x26C, + 0xA7AE => 0x26A, + 0xA7B0 => 0x29E, + 0xA7B1 => 0x287, + 0xA7B2 => 0x29D, + 0xA7B3 => 0xAB53, + 0xA7B4 => 0xA7B5, + 0xA7B6 => 0xA7B7, + 0xA7B8 => 0xA7B9, + 0xA7BA => 0xA7BB, + 0xA7BC => 0xA7BD, + 0xA7BE => 0xA7BF, + 0xA7C2 => 0xA7C3, + 0xA7C4 => 0xA794, + 0xA7C5 => 0x282, + 0xA7C6 => 0x1D8E, + 0xA7C7 => 0xA7C8, + 0xA7C9 => 0xA7CA, + 0xA7F5 => 0xA7F6, + 0xAB70 => 0x13A0, + 0xAB71 => 0x13A1, + 0xAB72 => 0x13A2, + 0xAB73 => 0x13A3, + 0xAB74 => 0x13A4, + 0xAB75 => 0x13A5, + 0xAB76 => 0x13A6, + 0xAB77 => 0x13A7, + 0xAB78 => 0x13A8, + 0xAB79 => 0x13A9, + 0xAB7A => 0x13AA, + 0xAB7B => 0x13AB, + 0xAB7C => 0x13AC, + 0xAB7D => 0x13AD, + 0xAB7E => 0x13AE, + 0xAB7F => 0x13AF, + 0xAB80 => 0x13B0, + 0xAB81 => 0x13B1, + 0xAB82 => 0x13B2, + 0xAB83 => 0x13B3, + 0xAB84 => 0x13B4, + 0xAB85 => 0x13B5, + 0xAB86 => 0x13B6, + 0xAB87 => 0x13B7, + 0xAB88 => 0x13B8, + 0xAB89 => 0x13B9, + 0xAB8A => 0x13BA, + 0xAB8B => 0x13BB, + 0xAB8C => 0x13BC, + 0xAB8D => 0x13BD, + 0xAB8E => 0x13BE, + 0xAB8F => 0x13BF, + 0xAB90 => 0x13C0, + 0xAB91 => 0x13C1, + 0xAB92 => 0x13C2, + 0xAB93 => 0x13C3, + 0xAB94 => 0x13C4, + 0xAB95 => 0x13C5, + 0xAB96 => 0x13C6, + 0xAB97 => 0x13C7, + 0xAB98 => 0x13C8, + 0xAB99 => 0x13C9, + 0xAB9A => 0x13CA, + 0xAB9B => 0x13CB, + 0xAB9C => 0x13CC, + 0xAB9D => 0x13CD, + 0xAB9E => 0x13CE, + 0xAB9F => 0x13CF, + 0xABA0 => 0x13D0, + 0xABA1 => 0x13D1, + 0xABA2 => 0x13D2, + 0xABA3 => 0x13D3, + 0xABA4 => 0x13D4, + 0xABA5 => 0x13D5, + 0xABA6 => 0x13D6, + 0xABA7 => 0x13D7, + 0xABA8 => 0x13D8, + 0xABA9 => 0x13D9, + 0xABAA => 0x13DA, + 0xABAB => 0x13DB, + 0xABAC => 0x13DC, + 0xABAD => 0x13DD, + 0xABAE => 0x13DE, + 0xABAF => 0x13DF, + 0xABB0 => 0x13E0, + 0xABB1 => 0x13E1, + 0xABB2 => 0x13E2, + 0xABB3 => 0x13E3, + 0xABB4 => 0x13E4, + 0xABB5 => 0x13E5, + 0xABB6 => 0x13E6, + 0xABB7 => 0x13E7, + 0xABB8 => 0x13E8, + 0xABB9 => 0x13E9, + 0xABBA => 0x13EA, + 0xABBB => 0x13EB, + 0xABBC => 0x13EC, + 0xABBD => 0x13ED, + 0xABBE => 0x13EE, + 0xABBF => 0x13EF, + 0xFF21 => 0xFF41, + 0xFF22 => 0xFF42, + 0xFF23 => 0xFF43, + 0xFF24 => 0xFF44, + 0xFF25 => 0xFF45, + 0xFF26 => 0xFF46, + 0xFF27 => 0xFF47, + 0xFF28 => 0xFF48, + 0xFF29 => 0xFF49, + 0xFF2A => 0xFF4A, + 0xFF2B => 0xFF4B, + 0xFF2C => 0xFF4C, + 0xFF2D => 0xFF4D, + 0xFF2E => 0xFF4E, + 0xFF2F => 0xFF4F, + 0xFF30 => 0xFF50, + 0xFF31 => 0xFF51, + 0xFF32 => 0xFF52, + 0xFF33 => 0xFF53, + 0xFF34 => 0xFF54, + 0xFF35 => 0xFF55, + 0xFF36 => 0xFF56, + 0xFF37 => 0xFF57, + 0xFF38 => 0xFF58, + 0xFF39 => 0xFF59, + 0xFF3A => 0xFF5A, + 0x10400 => 0x10428, + 0x10401 => 0x10429, + 0x10402 => 0x1042A, + 0x10403 => 0x1042B, + 0x10404 => 0x1042C, + 0x10405 => 0x1042D, + 0x10406 => 0x1042E, + 0x10407 => 0x1042F, + 0x10408 => 0x10430, + 0x10409 => 0x10431, + 0x1040A => 0x10432, + 0x1040B => 0x10433, + 0x1040C => 0x10434, + 0x1040D => 0x10435, + 0x1040E => 0x10436, + 0x1040F => 0x10437, + 0x10410 => 0x10438, + 0x10411 => 0x10439, + 0x10412 => 0x1043A, + 0x10413 => 0x1043B, + 0x10414 => 0x1043C, + 0x10415 => 0x1043D, + 0x10416 => 0x1043E, + 0x10417 => 0x1043F, + 0x10418 => 0x10440, + 0x10419 => 0x10441, + 0x1041A => 0x10442, + 0x1041B => 0x10443, + 0x1041C => 0x10444, + 0x1041D => 0x10445, + 0x1041E => 0x10446, + 0x1041F => 0x10447, + 0x10420 => 0x10448, + 0x10421 => 0x10449, + 0x10422 => 0x1044A, + 0x10423 => 0x1044B, + 0x10424 => 0x1044C, + 0x10425 => 0x1044D, + 0x10426 => 0x1044E, + 0x10427 => 0x1044F, + 0x104B0 => 0x104D8, + 0x104B1 => 0x104D9, + 0x104B2 => 0x104DA, + 0x104B3 => 0x104DB, + 0x104B4 => 0x104DC, + 0x104B5 => 0x104DD, + 0x104B6 => 0x104DE, + 0x104B7 => 0x104DF, + 0x104B8 => 0x104E0, + 0x104B9 => 0x104E1, + 0x104BA => 0x104E2, + 0x104BB => 0x104E3, + 0x104BC => 0x104E4, + 0x104BD => 0x104E5, + 0x104BE => 0x104E6, + 0x104BF => 0x104E7, + 0x104C0 => 0x104E8, + 0x104C1 => 0x104E9, + 0x104C2 => 0x104EA, + 0x104C3 => 0x104EB, + 0x104C4 => 0x104EC, + 0x104C5 => 0x104ED, + 0x104C6 => 0x104EE, + 0x104C7 => 0x104EF, + 0x104C8 => 0x104F0, + 0x104C9 => 0x104F1, + 0x104CA => 0x104F2, + 0x104CB => 0x104F3, + 0x104CC => 0x104F4, + 0x104CD => 0x104F5, + 0x104CE => 0x104F6, + 0x104CF => 0x104F7, + 0x104D0 => 0x104F8, + 0x104D1 => 0x104F9, + 0x104D2 => 0x104FA, + 0x104D3 => 0x104FB, + 0x10C80 => 0x10CC0, + 0x10C81 => 0x10CC1, + 0x10C82 => 0x10CC2, + 0x10C83 => 0x10CC3, + 0x10C84 => 0x10CC4, + 0x10C85 => 0x10CC5, + 0x10C86 => 0x10CC6, + 0x10C87 => 0x10CC7, + 0x10C88 => 0x10CC8, + 0x10C89 => 0x10CC9, + 0x10C8A => 0x10CCA, + 0x10C8B => 0x10CCB, + 0x10C8C => 0x10CCC, + 0x10C8D => 0x10CCD, + 0x10C8E => 0x10CCE, + 0x10C8F => 0x10CCF, + 0x10C90 => 0x10CD0, + 0x10C91 => 0x10CD1, + 0x10C92 => 0x10CD2, + 0x10C93 => 0x10CD3, + 0x10C94 => 0x10CD4, + 0x10C95 => 0x10CD5, + 0x10C96 => 0x10CD6, + 0x10C97 => 0x10CD7, + 0x10C98 => 0x10CD8, + 0x10C99 => 0x10CD9, + 0x10C9A => 0x10CDA, + 0x10C9B => 0x10CDB, + 0x10C9C => 0x10CDC, + 0x10C9D => 0x10CDD, + 0x10C9E => 0x10CDE, + 0x10C9F => 0x10CDF, + 0x10CA0 => 0x10CE0, + 0x10CA1 => 0x10CE1, + 0x10CA2 => 0x10CE2, + 0x10CA3 => 0x10CE3, + 0x10CA4 => 0x10CE4, + 0x10CA5 => 0x10CE5, + 0x10CA6 => 0x10CE6, + 0x10CA7 => 0x10CE7, + 0x10CA8 => 0x10CE8, + 0x10CA9 => 0x10CE9, + 0x10CAA => 0x10CEA, + 0x10CAB => 0x10CEB, + 0x10CAC => 0x10CEC, + 0x10CAD => 0x10CED, + 0x10CAE => 0x10CEE, + 0x10CAF => 0x10CEF, + 0x10CB0 => 0x10CF0, + 0x10CB1 => 0x10CF1, + 0x10CB2 => 0x10CF2, + 0x118A0 => 0x118C0, + 0x118A1 => 0x118C1, + 0x118A2 => 0x118C2, + 0x118A3 => 0x118C3, + 0x118A4 => 0x118C4, + 0x118A5 => 0x118C5, + 0x118A6 => 0x118C6, + 0x118A7 => 0x118C7, + 0x118A8 => 0x118C8, + 0x118A9 => 0x118C9, + 0x118AA => 0x118CA, + 0x118AB => 0x118CB, + 0x118AC => 0x118CC, + 0x118AD => 0x118CD, + 0x118AE => 0x118CE, + 0x118AF => 0x118CF, + 0x118B0 => 0x118D0, + 0x118B1 => 0x118D1, + 0x118B2 => 0x118D2, + 0x118B3 => 0x118D3, + 0x118B4 => 0x118D4, + 0x118B5 => 0x118D5, + 0x118B6 => 0x118D6, + 0x118B7 => 0x118D7, + 0x118B8 => 0x118D8, + 0x118B9 => 0x118D9, + 0x118BA => 0x118DA, + 0x118BB => 0x118DB, + 0x118BC => 0x118DC, + 0x118BD => 0x118DD, + 0x118BE => 0x118DE, + 0x118BF => 0x118DF, + 0x16E40 => 0x16E60, + 0x16E41 => 0x16E61, + 0x16E42 => 0x16E62, + 0x16E43 => 0x16E63, + 0x16E44 => 0x16E64, + 0x16E45 => 0x16E65, + 0x16E46 => 0x16E66, + 0x16E47 => 0x16E67, + 0x16E48 => 0x16E68, + 0x16E49 => 0x16E69, + 0x16E4A => 0x16E6A, + 0x16E4B => 0x16E6B, + 0x16E4C => 0x16E6C, + 0x16E4D => 0x16E6D, + 0x16E4E => 0x16E6E, + 0x16E4F => 0x16E6F, + 0x16E50 => 0x16E70, + 0x16E51 => 0x16E71, + 0x16E52 => 0x16E72, + 0x16E53 => 0x16E73, + 0x16E54 => 0x16E74, + 0x16E55 => 0x16E75, + 0x16E56 => 0x16E76, + 0x16E57 => 0x16E77, + 0x16E58 => 0x16E78, + 0x16E59 => 0x16E79, + 0x16E5A => 0x16E7A, + 0x16E5B => 0x16E7B, + 0x16E5C => 0x16E7C, + 0x16E5D => 0x16E7D, + 0x16E5E => 0x16E7E, + 0x16E5F => 0x16E7F, + 0x1E900 => 0x1E922, + 0x1E901 => 0x1E923, + 0x1E902 => 0x1E924, + 0x1E903 => 0x1E925, + 0x1E904 => 0x1E926, + 0x1E905 => 0x1E927, + 0x1E906 => 0x1E928, + 0x1E907 => 0x1E929, + 0x1E908 => 0x1E92A, + 0x1E909 => 0x1E92B, + 0x1E90A => 0x1E92C, + 0x1E90B => 0x1E92D, + 0x1E90C => 0x1E92E, + 0x1E90D => 0x1E92F, + 0x1E90E => 0x1E930, + 0x1E90F => 0x1E931, + 0x1E910 => 0x1E932, + 0x1E911 => 0x1E933, + 0x1E912 => 0x1E934, + 0x1E913 => 0x1E935, + 0x1E914 => 0x1E936, + 0x1E915 => 0x1E937, + 0x1E916 => 0x1E938, + 0x1E917 => 0x1E939, + 0x1E918 => 0x1E93A, + 0x1E919 => 0x1E93B, + 0x1E91A => 0x1E93C, + 0x1E91B => 0x1E93D, + 0x1E91C => 0x1E93E, + 0x1E91D => 0x1E93F, + 0x1E91E => 0x1E940, + 0x1E91F => 0x1E941, + 0x1E920 => 0x1E942, + 0x1E921 => 0x1E943, +); diff --git a/src/opis/string/res/lower.php b/src/opis/string/res/lower.php index b5697d1d..efe36c2b 100644 --- a/src/opis/string/res/lower.php +++ b/src/opis/string/res/lower.php @@ -1,1396 +1,1396 @@ 0x61, -0x42 => 0x62, -0x43 => 0x63, -0x44 => 0x64, -0x45 => 0x65, -0x46 => 0x66, -0x47 => 0x67, -0x48 => 0x68, -0x49 => 0x69, -0x4A => 0x6A, -0x4B => 0x6B, -0x4C => 0x6C, -0x4D => 0x6D, -0x4E => 0x6E, -0x4F => 0x6F, -0x50 => 0x70, -0x51 => 0x71, -0x52 => 0x72, -0x53 => 0x73, -0x54 => 0x74, -0x55 => 0x75, -0x56 => 0x76, -0x57 => 0x77, -0x58 => 0x78, -0x59 => 0x79, -0x5A => 0x7A, -0xC0 => 0xE0, -0xC1 => 0xE1, -0xC2 => 0xE2, -0xC3 => 0xE3, -0xC4 => 0xE4, -0xC5 => 0xE5, -0xC6 => 0xE6, -0xC7 => 0xE7, -0xC8 => 0xE8, -0xC9 => 0xE9, -0xCA => 0xEA, -0xCB => 0xEB, -0xCC => 0xEC, -0xCD => 0xED, -0xCE => 0xEE, -0xCF => 0xEF, -0xD0 => 0xF0, -0xD1 => 0xF1, -0xD2 => 0xF2, -0xD3 => 0xF3, -0xD4 => 0xF4, -0xD5 => 0xF5, -0xD6 => 0xF6, -0xD8 => 0xF8, -0xD9 => 0xF9, -0xDA => 0xFA, -0xDB => 0xFB, -0xDC => 0xFC, -0xDD => 0xFD, -0xDE => 0xFE, -0x100 => 0x101, -0x102 => 0x103, -0x104 => 0x105, -0x106 => 0x107, -0x108 => 0x109, -0x10A => 0x10B, -0x10C => 0x10D, -0x10E => 0x10F, -0x110 => 0x111, -0x112 => 0x113, -0x114 => 0x115, -0x116 => 0x117, -0x118 => 0x119, -0x11A => 0x11B, -0x11C => 0x11D, -0x11E => 0x11F, -0x120 => 0x121, -0x122 => 0x123, -0x124 => 0x125, -0x126 => 0x127, -0x128 => 0x129, -0x12A => 0x12B, -0x12C => 0x12D, -0x12E => 0x12F, -0x130 => 0x69, -0x132 => 0x133, -0x134 => 0x135, -0x136 => 0x137, -0x139 => 0x13A, -0x13B => 0x13C, -0x13D => 0x13E, -0x13F => 0x140, -0x141 => 0x142, -0x143 => 0x144, -0x145 => 0x146, -0x147 => 0x148, -0x14A => 0x14B, -0x14C => 0x14D, -0x14E => 0x14F, -0x150 => 0x151, -0x152 => 0x153, -0x154 => 0x155, -0x156 => 0x157, -0x158 => 0x159, -0x15A => 0x15B, -0x15C => 0x15D, -0x15E => 0x15F, -0x160 => 0x161, -0x162 => 0x163, -0x164 => 0x165, -0x166 => 0x167, -0x168 => 0x169, -0x16A => 0x16B, -0x16C => 0x16D, -0x16E => 0x16F, -0x170 => 0x171, -0x172 => 0x173, -0x174 => 0x175, -0x176 => 0x177, -0x178 => 0xFF, -0x179 => 0x17A, -0x17B => 0x17C, -0x17D => 0x17E, -0x181 => 0x253, -0x182 => 0x183, -0x184 => 0x185, -0x186 => 0x254, -0x187 => 0x188, -0x189 => 0x256, -0x18A => 0x257, -0x18B => 0x18C, -0x18E => 0x1DD, -0x18F => 0x259, -0x190 => 0x25B, -0x191 => 0x192, -0x193 => 0x260, -0x194 => 0x263, -0x196 => 0x269, -0x197 => 0x268, -0x198 => 0x199, -0x19C => 0x26F, -0x19D => 0x272, -0x19F => 0x275, -0x1A0 => 0x1A1, -0x1A2 => 0x1A3, -0x1A4 => 0x1A5, -0x1A6 => 0x280, -0x1A7 => 0x1A8, -0x1A9 => 0x283, -0x1AC => 0x1AD, -0x1AE => 0x288, -0x1AF => 0x1B0, -0x1B1 => 0x28A, -0x1B2 => 0x28B, -0x1B3 => 0x1B4, -0x1B5 => 0x1B6, -0x1B7 => 0x292, -0x1B8 => 0x1B9, -0x1BC => 0x1BD, -0x1C4 => 0x1C6, -0x1C5 => 0x1C6, -0x1C7 => 0x1C9, -0x1C8 => 0x1C9, -0x1CA => 0x1CC, -0x1CB => 0x1CC, -0x1CD => 0x1CE, -0x1CF => 0x1D0, -0x1D1 => 0x1D2, -0x1D3 => 0x1D4, -0x1D5 => 0x1D6, -0x1D7 => 0x1D8, -0x1D9 => 0x1DA, -0x1DB => 0x1DC, -0x1DE => 0x1DF, -0x1E0 => 0x1E1, -0x1E2 => 0x1E3, -0x1E4 => 0x1E5, -0x1E6 => 0x1E7, -0x1E8 => 0x1E9, -0x1EA => 0x1EB, -0x1EC => 0x1ED, -0x1EE => 0x1EF, -0x1F1 => 0x1F3, -0x1F2 => 0x1F3, -0x1F4 => 0x1F5, -0x1F6 => 0x195, -0x1F7 => 0x1BF, -0x1F8 => 0x1F9, -0x1FA => 0x1FB, -0x1FC => 0x1FD, -0x1FE => 0x1FF, -0x200 => 0x201, -0x202 => 0x203, -0x204 => 0x205, -0x206 => 0x207, -0x208 => 0x209, -0x20A => 0x20B, -0x20C => 0x20D, -0x20E => 0x20F, -0x210 => 0x211, -0x212 => 0x213, -0x214 => 0x215, -0x216 => 0x217, -0x218 => 0x219, -0x21A => 0x21B, -0x21C => 0x21D, -0x21E => 0x21F, -0x220 => 0x19E, -0x222 => 0x223, -0x224 => 0x225, -0x226 => 0x227, -0x228 => 0x229, -0x22A => 0x22B, -0x22C => 0x22D, -0x22E => 0x22F, -0x230 => 0x231, -0x232 => 0x233, -0x23A => 0x2C65, -0x23B => 0x23C, -0x23D => 0x19A, -0x23E => 0x2C66, -0x241 => 0x242, -0x243 => 0x180, -0x244 => 0x289, -0x245 => 0x28C, -0x246 => 0x247, -0x248 => 0x249, -0x24A => 0x24B, -0x24C => 0x24D, -0x24E => 0x24F, -0x370 => 0x371, -0x372 => 0x373, -0x376 => 0x377, -0x37F => 0x3F3, -0x386 => 0x3AC, -0x388 => 0x3AD, -0x389 => 0x3AE, -0x38A => 0x3AF, -0x38C => 0x3CC, -0x38E => 0x3CD, -0x38F => 0x3CE, -0x391 => 0x3B1, -0x392 => 0x3B2, -0x393 => 0x3B3, -0x394 => 0x3B4, -0x395 => 0x3B5, -0x396 => 0x3B6, -0x397 => 0x3B7, -0x398 => 0x3B8, -0x399 => 0x3B9, -0x39A => 0x3BA, -0x39B => 0x3BB, -0x39C => 0x3BC, -0x39D => 0x3BD, -0x39E => 0x3BE, -0x39F => 0x3BF, -0x3A0 => 0x3C0, -0x3A1 => 0x3C1, -0x3A3 => 0x3C3, -0x3A4 => 0x3C4, -0x3A5 => 0x3C5, -0x3A6 => 0x3C6, -0x3A7 => 0x3C7, -0x3A8 => 0x3C8, -0x3A9 => 0x3C9, -0x3AA => 0x3CA, -0x3AB => 0x3CB, -0x3CF => 0x3D7, -0x3D8 => 0x3D9, -0x3DA => 0x3DB, -0x3DC => 0x3DD, -0x3DE => 0x3DF, -0x3E0 => 0x3E1, -0x3E2 => 0x3E3, -0x3E4 => 0x3E5, -0x3E6 => 0x3E7, -0x3E8 => 0x3E9, -0x3EA => 0x3EB, -0x3EC => 0x3ED, -0x3EE => 0x3EF, -0x3F4 => 0x3B8, -0x3F7 => 0x3F8, -0x3F9 => 0x3F2, -0x3FA => 0x3FB, -0x3FD => 0x37B, -0x3FE => 0x37C, -0x3FF => 0x37D, -0x400 => 0x450, -0x401 => 0x451, -0x402 => 0x452, -0x403 => 0x453, -0x404 => 0x454, -0x405 => 0x455, -0x406 => 0x456, -0x407 => 0x457, -0x408 => 0x458, -0x409 => 0x459, -0x40A => 0x45A, -0x40B => 0x45B, -0x40C => 0x45C, -0x40D => 0x45D, -0x40E => 0x45E, -0x40F => 0x45F, -0x410 => 0x430, -0x411 => 0x431, -0x412 => 0x432, -0x413 => 0x433, -0x414 => 0x434, -0x415 => 0x435, -0x416 => 0x436, -0x417 => 0x437, -0x418 => 0x438, -0x419 => 0x439, -0x41A => 0x43A, -0x41B => 0x43B, -0x41C => 0x43C, -0x41D => 0x43D, -0x41E => 0x43E, -0x41F => 0x43F, -0x420 => 0x440, -0x421 => 0x441, -0x422 => 0x442, -0x423 => 0x443, -0x424 => 0x444, -0x425 => 0x445, -0x426 => 0x446, -0x427 => 0x447, -0x428 => 0x448, -0x429 => 0x449, -0x42A => 0x44A, -0x42B => 0x44B, -0x42C => 0x44C, -0x42D => 0x44D, -0x42E => 0x44E, -0x42F => 0x44F, -0x460 => 0x461, -0x462 => 0x463, -0x464 => 0x465, -0x466 => 0x467, -0x468 => 0x469, -0x46A => 0x46B, -0x46C => 0x46D, -0x46E => 0x46F, -0x470 => 0x471, -0x472 => 0x473, -0x474 => 0x475, -0x476 => 0x477, -0x478 => 0x479, -0x47A => 0x47B, -0x47C => 0x47D, -0x47E => 0x47F, -0x480 => 0x481, -0x48A => 0x48B, -0x48C => 0x48D, -0x48E => 0x48F, -0x490 => 0x491, -0x492 => 0x493, -0x494 => 0x495, -0x496 => 0x497, -0x498 => 0x499, -0x49A => 0x49B, -0x49C => 0x49D, -0x49E => 0x49F, -0x4A0 => 0x4A1, -0x4A2 => 0x4A3, -0x4A4 => 0x4A5, -0x4A6 => 0x4A7, -0x4A8 => 0x4A9, -0x4AA => 0x4AB, -0x4AC => 0x4AD, -0x4AE => 0x4AF, -0x4B0 => 0x4B1, -0x4B2 => 0x4B3, -0x4B4 => 0x4B5, -0x4B6 => 0x4B7, -0x4B8 => 0x4B9, -0x4BA => 0x4BB, -0x4BC => 0x4BD, -0x4BE => 0x4BF, -0x4C0 => 0x4CF, -0x4C1 => 0x4C2, -0x4C3 => 0x4C4, -0x4C5 => 0x4C6, -0x4C7 => 0x4C8, -0x4C9 => 0x4CA, -0x4CB => 0x4CC, -0x4CD => 0x4CE, -0x4D0 => 0x4D1, -0x4D2 => 0x4D3, -0x4D4 => 0x4D5, -0x4D6 => 0x4D7, -0x4D8 => 0x4D9, -0x4DA => 0x4DB, -0x4DC => 0x4DD, -0x4DE => 0x4DF, -0x4E0 => 0x4E1, -0x4E2 => 0x4E3, -0x4E4 => 0x4E5, -0x4E6 => 0x4E7, -0x4E8 => 0x4E9, -0x4EA => 0x4EB, -0x4EC => 0x4ED, -0x4EE => 0x4EF, -0x4F0 => 0x4F1, -0x4F2 => 0x4F3, -0x4F4 => 0x4F5, -0x4F6 => 0x4F7, -0x4F8 => 0x4F9, -0x4FA => 0x4FB, -0x4FC => 0x4FD, -0x4FE => 0x4FF, -0x500 => 0x501, -0x502 => 0x503, -0x504 => 0x505, -0x506 => 0x507, -0x508 => 0x509, -0x50A => 0x50B, -0x50C => 0x50D, -0x50E => 0x50F, -0x510 => 0x511, -0x512 => 0x513, -0x514 => 0x515, -0x516 => 0x517, -0x518 => 0x519, -0x51A => 0x51B, -0x51C => 0x51D, -0x51E => 0x51F, -0x520 => 0x521, -0x522 => 0x523, -0x524 => 0x525, -0x526 => 0x527, -0x528 => 0x529, -0x52A => 0x52B, -0x52C => 0x52D, -0x52E => 0x52F, -0x531 => 0x561, -0x532 => 0x562, -0x533 => 0x563, -0x534 => 0x564, -0x535 => 0x565, -0x536 => 0x566, -0x537 => 0x567, -0x538 => 0x568, -0x539 => 0x569, -0x53A => 0x56A, -0x53B => 0x56B, -0x53C => 0x56C, -0x53D => 0x56D, -0x53E => 0x56E, -0x53F => 0x56F, -0x540 => 0x570, -0x541 => 0x571, -0x542 => 0x572, -0x543 => 0x573, -0x544 => 0x574, -0x545 => 0x575, -0x546 => 0x576, -0x547 => 0x577, -0x548 => 0x578, -0x549 => 0x579, -0x54A => 0x57A, -0x54B => 0x57B, -0x54C => 0x57C, -0x54D => 0x57D, -0x54E => 0x57E, -0x54F => 0x57F, -0x550 => 0x580, -0x551 => 0x581, -0x552 => 0x582, -0x553 => 0x583, -0x554 => 0x584, -0x555 => 0x585, -0x556 => 0x586, -0x10A0 => 0x2D00, -0x10A1 => 0x2D01, -0x10A2 => 0x2D02, -0x10A3 => 0x2D03, -0x10A4 => 0x2D04, -0x10A5 => 0x2D05, -0x10A6 => 0x2D06, -0x10A7 => 0x2D07, -0x10A8 => 0x2D08, -0x10A9 => 0x2D09, -0x10AA => 0x2D0A, -0x10AB => 0x2D0B, -0x10AC => 0x2D0C, -0x10AD => 0x2D0D, -0x10AE => 0x2D0E, -0x10AF => 0x2D0F, -0x10B0 => 0x2D10, -0x10B1 => 0x2D11, -0x10B2 => 0x2D12, -0x10B3 => 0x2D13, -0x10B4 => 0x2D14, -0x10B5 => 0x2D15, -0x10B6 => 0x2D16, -0x10B7 => 0x2D17, -0x10B8 => 0x2D18, -0x10B9 => 0x2D19, -0x10BA => 0x2D1A, -0x10BB => 0x2D1B, -0x10BC => 0x2D1C, -0x10BD => 0x2D1D, -0x10BE => 0x2D1E, -0x10BF => 0x2D1F, -0x10C0 => 0x2D20, -0x10C1 => 0x2D21, -0x10C2 => 0x2D22, -0x10C3 => 0x2D23, -0x10C4 => 0x2D24, -0x10C5 => 0x2D25, -0x10C7 => 0x2D27, -0x10CD => 0x2D2D, -0x13A0 => 0xAB70, -0x13A1 => 0xAB71, -0x13A2 => 0xAB72, -0x13A3 => 0xAB73, -0x13A4 => 0xAB74, -0x13A5 => 0xAB75, -0x13A6 => 0xAB76, -0x13A7 => 0xAB77, -0x13A8 => 0xAB78, -0x13A9 => 0xAB79, -0x13AA => 0xAB7A, -0x13AB => 0xAB7B, -0x13AC => 0xAB7C, -0x13AD => 0xAB7D, -0x13AE => 0xAB7E, -0x13AF => 0xAB7F, -0x13B0 => 0xAB80, -0x13B1 => 0xAB81, -0x13B2 => 0xAB82, -0x13B3 => 0xAB83, -0x13B4 => 0xAB84, -0x13B5 => 0xAB85, -0x13B6 => 0xAB86, -0x13B7 => 0xAB87, -0x13B8 => 0xAB88, -0x13B9 => 0xAB89, -0x13BA => 0xAB8A, -0x13BB => 0xAB8B, -0x13BC => 0xAB8C, -0x13BD => 0xAB8D, -0x13BE => 0xAB8E, -0x13BF => 0xAB8F, -0x13C0 => 0xAB90, -0x13C1 => 0xAB91, -0x13C2 => 0xAB92, -0x13C3 => 0xAB93, -0x13C4 => 0xAB94, -0x13C5 => 0xAB95, -0x13C6 => 0xAB96, -0x13C7 => 0xAB97, -0x13C8 => 0xAB98, -0x13C9 => 0xAB99, -0x13CA => 0xAB9A, -0x13CB => 0xAB9B, -0x13CC => 0xAB9C, -0x13CD => 0xAB9D, -0x13CE => 0xAB9E, -0x13CF => 0xAB9F, -0x13D0 => 0xABA0, -0x13D1 => 0xABA1, -0x13D2 => 0xABA2, -0x13D3 => 0xABA3, -0x13D4 => 0xABA4, -0x13D5 => 0xABA5, -0x13D6 => 0xABA6, -0x13D7 => 0xABA7, -0x13D8 => 0xABA8, -0x13D9 => 0xABA9, -0x13DA => 0xABAA, -0x13DB => 0xABAB, -0x13DC => 0xABAC, -0x13DD => 0xABAD, -0x13DE => 0xABAE, -0x13DF => 0xABAF, -0x13E0 => 0xABB0, -0x13E1 => 0xABB1, -0x13E2 => 0xABB2, -0x13E3 => 0xABB3, -0x13E4 => 0xABB4, -0x13E5 => 0xABB5, -0x13E6 => 0xABB6, -0x13E7 => 0xABB7, -0x13E8 => 0xABB8, -0x13E9 => 0xABB9, -0x13EA => 0xABBA, -0x13EB => 0xABBB, -0x13EC => 0xABBC, -0x13ED => 0xABBD, -0x13EE => 0xABBE, -0x13EF => 0xABBF, -0x13F0 => 0x13F8, -0x13F1 => 0x13F9, -0x13F2 => 0x13FA, -0x13F3 => 0x13FB, -0x13F4 => 0x13FC, -0x13F5 => 0x13FD, -0x1C90 => 0x10D0, -0x1C91 => 0x10D1, -0x1C92 => 0x10D2, -0x1C93 => 0x10D3, -0x1C94 => 0x10D4, -0x1C95 => 0x10D5, -0x1C96 => 0x10D6, -0x1C97 => 0x10D7, -0x1C98 => 0x10D8, -0x1C99 => 0x10D9, -0x1C9A => 0x10DA, -0x1C9B => 0x10DB, -0x1C9C => 0x10DC, -0x1C9D => 0x10DD, -0x1C9E => 0x10DE, -0x1C9F => 0x10DF, -0x1CA0 => 0x10E0, -0x1CA1 => 0x10E1, -0x1CA2 => 0x10E2, -0x1CA3 => 0x10E3, -0x1CA4 => 0x10E4, -0x1CA5 => 0x10E5, -0x1CA6 => 0x10E6, -0x1CA7 => 0x10E7, -0x1CA8 => 0x10E8, -0x1CA9 => 0x10E9, -0x1CAA => 0x10EA, -0x1CAB => 0x10EB, -0x1CAC => 0x10EC, -0x1CAD => 0x10ED, -0x1CAE => 0x10EE, -0x1CAF => 0x10EF, -0x1CB0 => 0x10F0, -0x1CB1 => 0x10F1, -0x1CB2 => 0x10F2, -0x1CB3 => 0x10F3, -0x1CB4 => 0x10F4, -0x1CB5 => 0x10F5, -0x1CB6 => 0x10F6, -0x1CB7 => 0x10F7, -0x1CB8 => 0x10F8, -0x1CB9 => 0x10F9, -0x1CBA => 0x10FA, -0x1CBD => 0x10FD, -0x1CBE => 0x10FE, -0x1CBF => 0x10FF, -0x1E00 => 0x1E01, -0x1E02 => 0x1E03, -0x1E04 => 0x1E05, -0x1E06 => 0x1E07, -0x1E08 => 0x1E09, -0x1E0A => 0x1E0B, -0x1E0C => 0x1E0D, -0x1E0E => 0x1E0F, -0x1E10 => 0x1E11, -0x1E12 => 0x1E13, -0x1E14 => 0x1E15, -0x1E16 => 0x1E17, -0x1E18 => 0x1E19, -0x1E1A => 0x1E1B, -0x1E1C => 0x1E1D, -0x1E1E => 0x1E1F, -0x1E20 => 0x1E21, -0x1E22 => 0x1E23, -0x1E24 => 0x1E25, -0x1E26 => 0x1E27, -0x1E28 => 0x1E29, -0x1E2A => 0x1E2B, -0x1E2C => 0x1E2D, -0x1E2E => 0x1E2F, -0x1E30 => 0x1E31, -0x1E32 => 0x1E33, -0x1E34 => 0x1E35, -0x1E36 => 0x1E37, -0x1E38 => 0x1E39, -0x1E3A => 0x1E3B, -0x1E3C => 0x1E3D, -0x1E3E => 0x1E3F, -0x1E40 => 0x1E41, -0x1E42 => 0x1E43, -0x1E44 => 0x1E45, -0x1E46 => 0x1E47, -0x1E48 => 0x1E49, -0x1E4A => 0x1E4B, -0x1E4C => 0x1E4D, -0x1E4E => 0x1E4F, -0x1E50 => 0x1E51, -0x1E52 => 0x1E53, -0x1E54 => 0x1E55, -0x1E56 => 0x1E57, -0x1E58 => 0x1E59, -0x1E5A => 0x1E5B, -0x1E5C => 0x1E5D, -0x1E5E => 0x1E5F, -0x1E60 => 0x1E61, -0x1E62 => 0x1E63, -0x1E64 => 0x1E65, -0x1E66 => 0x1E67, -0x1E68 => 0x1E69, -0x1E6A => 0x1E6B, -0x1E6C => 0x1E6D, -0x1E6E => 0x1E6F, -0x1E70 => 0x1E71, -0x1E72 => 0x1E73, -0x1E74 => 0x1E75, -0x1E76 => 0x1E77, -0x1E78 => 0x1E79, -0x1E7A => 0x1E7B, -0x1E7C => 0x1E7D, -0x1E7E => 0x1E7F, -0x1E80 => 0x1E81, -0x1E82 => 0x1E83, -0x1E84 => 0x1E85, -0x1E86 => 0x1E87, -0x1E88 => 0x1E89, -0x1E8A => 0x1E8B, -0x1E8C => 0x1E8D, -0x1E8E => 0x1E8F, -0x1E90 => 0x1E91, -0x1E92 => 0x1E93, -0x1E94 => 0x1E95, -0x1E9E => 0xDF, -0x1EA0 => 0x1EA1, -0x1EA2 => 0x1EA3, -0x1EA4 => 0x1EA5, -0x1EA6 => 0x1EA7, -0x1EA8 => 0x1EA9, -0x1EAA => 0x1EAB, -0x1EAC => 0x1EAD, -0x1EAE => 0x1EAF, -0x1EB0 => 0x1EB1, -0x1EB2 => 0x1EB3, -0x1EB4 => 0x1EB5, -0x1EB6 => 0x1EB7, -0x1EB8 => 0x1EB9, -0x1EBA => 0x1EBB, -0x1EBC => 0x1EBD, -0x1EBE => 0x1EBF, -0x1EC0 => 0x1EC1, -0x1EC2 => 0x1EC3, -0x1EC4 => 0x1EC5, -0x1EC6 => 0x1EC7, -0x1EC8 => 0x1EC9, -0x1ECA => 0x1ECB, -0x1ECC => 0x1ECD, -0x1ECE => 0x1ECF, -0x1ED0 => 0x1ED1, -0x1ED2 => 0x1ED3, -0x1ED4 => 0x1ED5, -0x1ED6 => 0x1ED7, -0x1ED8 => 0x1ED9, -0x1EDA => 0x1EDB, -0x1EDC => 0x1EDD, -0x1EDE => 0x1EDF, -0x1EE0 => 0x1EE1, -0x1EE2 => 0x1EE3, -0x1EE4 => 0x1EE5, -0x1EE6 => 0x1EE7, -0x1EE8 => 0x1EE9, -0x1EEA => 0x1EEB, -0x1EEC => 0x1EED, -0x1EEE => 0x1EEF, -0x1EF0 => 0x1EF1, -0x1EF2 => 0x1EF3, -0x1EF4 => 0x1EF5, -0x1EF6 => 0x1EF7, -0x1EF8 => 0x1EF9, -0x1EFA => 0x1EFB, -0x1EFC => 0x1EFD, -0x1EFE => 0x1EFF, -0x1F08 => 0x1F00, -0x1F09 => 0x1F01, -0x1F0A => 0x1F02, -0x1F0B => 0x1F03, -0x1F0C => 0x1F04, -0x1F0D => 0x1F05, -0x1F0E => 0x1F06, -0x1F0F => 0x1F07, -0x1F18 => 0x1F10, -0x1F19 => 0x1F11, -0x1F1A => 0x1F12, -0x1F1B => 0x1F13, -0x1F1C => 0x1F14, -0x1F1D => 0x1F15, -0x1F28 => 0x1F20, -0x1F29 => 0x1F21, -0x1F2A => 0x1F22, -0x1F2B => 0x1F23, -0x1F2C => 0x1F24, -0x1F2D => 0x1F25, -0x1F2E => 0x1F26, -0x1F2F => 0x1F27, -0x1F38 => 0x1F30, -0x1F39 => 0x1F31, -0x1F3A => 0x1F32, -0x1F3B => 0x1F33, -0x1F3C => 0x1F34, -0x1F3D => 0x1F35, -0x1F3E => 0x1F36, -0x1F3F => 0x1F37, -0x1F48 => 0x1F40, -0x1F49 => 0x1F41, -0x1F4A => 0x1F42, -0x1F4B => 0x1F43, -0x1F4C => 0x1F44, -0x1F4D => 0x1F45, -0x1F59 => 0x1F51, -0x1F5B => 0x1F53, -0x1F5D => 0x1F55, -0x1F5F => 0x1F57, -0x1F68 => 0x1F60, -0x1F69 => 0x1F61, -0x1F6A => 0x1F62, -0x1F6B => 0x1F63, -0x1F6C => 0x1F64, -0x1F6D => 0x1F65, -0x1F6E => 0x1F66, -0x1F6F => 0x1F67, -0x1F88 => 0x1F80, -0x1F89 => 0x1F81, -0x1F8A => 0x1F82, -0x1F8B => 0x1F83, -0x1F8C => 0x1F84, -0x1F8D => 0x1F85, -0x1F8E => 0x1F86, -0x1F8F => 0x1F87, -0x1F98 => 0x1F90, -0x1F99 => 0x1F91, -0x1F9A => 0x1F92, -0x1F9B => 0x1F93, -0x1F9C => 0x1F94, -0x1F9D => 0x1F95, -0x1F9E => 0x1F96, -0x1F9F => 0x1F97, -0x1FA8 => 0x1FA0, -0x1FA9 => 0x1FA1, -0x1FAA => 0x1FA2, -0x1FAB => 0x1FA3, -0x1FAC => 0x1FA4, -0x1FAD => 0x1FA5, -0x1FAE => 0x1FA6, -0x1FAF => 0x1FA7, -0x1FB8 => 0x1FB0, -0x1FB9 => 0x1FB1, -0x1FBA => 0x1F70, -0x1FBB => 0x1F71, -0x1FBC => 0x1FB3, -0x1FC8 => 0x1F72, -0x1FC9 => 0x1F73, -0x1FCA => 0x1F74, -0x1FCB => 0x1F75, -0x1FCC => 0x1FC3, -0x1FD8 => 0x1FD0, -0x1FD9 => 0x1FD1, -0x1FDA => 0x1F76, -0x1FDB => 0x1F77, -0x1FE8 => 0x1FE0, -0x1FE9 => 0x1FE1, -0x1FEA => 0x1F7A, -0x1FEB => 0x1F7B, -0x1FEC => 0x1FE5, -0x1FF8 => 0x1F78, -0x1FF9 => 0x1F79, -0x1FFA => 0x1F7C, -0x1FFB => 0x1F7D, -0x1FFC => 0x1FF3, -0x2126 => 0x3C9, -0x212A => 0x6B, -0x212B => 0xE5, -0x2132 => 0x214E, -0x2160 => 0x2170, -0x2161 => 0x2171, -0x2162 => 0x2172, -0x2163 => 0x2173, -0x2164 => 0x2174, -0x2165 => 0x2175, -0x2166 => 0x2176, -0x2167 => 0x2177, -0x2168 => 0x2178, -0x2169 => 0x2179, -0x216A => 0x217A, -0x216B => 0x217B, -0x216C => 0x217C, -0x216D => 0x217D, -0x216E => 0x217E, -0x216F => 0x217F, -0x2183 => 0x2184, -0x24B6 => 0x24D0, -0x24B7 => 0x24D1, -0x24B8 => 0x24D2, -0x24B9 => 0x24D3, -0x24BA => 0x24D4, -0x24BB => 0x24D5, -0x24BC => 0x24D6, -0x24BD => 0x24D7, -0x24BE => 0x24D8, -0x24BF => 0x24D9, -0x24C0 => 0x24DA, -0x24C1 => 0x24DB, -0x24C2 => 0x24DC, -0x24C3 => 0x24DD, -0x24C4 => 0x24DE, -0x24C5 => 0x24DF, -0x24C6 => 0x24E0, -0x24C7 => 0x24E1, -0x24C8 => 0x24E2, -0x24C9 => 0x24E3, -0x24CA => 0x24E4, -0x24CB => 0x24E5, -0x24CC => 0x24E6, -0x24CD => 0x24E7, -0x24CE => 0x24E8, -0x24CF => 0x24E9, -0x2C00 => 0x2C30, -0x2C01 => 0x2C31, -0x2C02 => 0x2C32, -0x2C03 => 0x2C33, -0x2C04 => 0x2C34, -0x2C05 => 0x2C35, -0x2C06 => 0x2C36, -0x2C07 => 0x2C37, -0x2C08 => 0x2C38, -0x2C09 => 0x2C39, -0x2C0A => 0x2C3A, -0x2C0B => 0x2C3B, -0x2C0C => 0x2C3C, -0x2C0D => 0x2C3D, -0x2C0E => 0x2C3E, -0x2C0F => 0x2C3F, -0x2C10 => 0x2C40, -0x2C11 => 0x2C41, -0x2C12 => 0x2C42, -0x2C13 => 0x2C43, -0x2C14 => 0x2C44, -0x2C15 => 0x2C45, -0x2C16 => 0x2C46, -0x2C17 => 0x2C47, -0x2C18 => 0x2C48, -0x2C19 => 0x2C49, -0x2C1A => 0x2C4A, -0x2C1B => 0x2C4B, -0x2C1C => 0x2C4C, -0x2C1D => 0x2C4D, -0x2C1E => 0x2C4E, -0x2C1F => 0x2C4F, -0x2C20 => 0x2C50, -0x2C21 => 0x2C51, -0x2C22 => 0x2C52, -0x2C23 => 0x2C53, -0x2C24 => 0x2C54, -0x2C25 => 0x2C55, -0x2C26 => 0x2C56, -0x2C27 => 0x2C57, -0x2C28 => 0x2C58, -0x2C29 => 0x2C59, -0x2C2A => 0x2C5A, -0x2C2B => 0x2C5B, -0x2C2C => 0x2C5C, -0x2C2D => 0x2C5D, -0x2C2E => 0x2C5E, -0x2C60 => 0x2C61, -0x2C62 => 0x26B, -0x2C63 => 0x1D7D, -0x2C64 => 0x27D, -0x2C67 => 0x2C68, -0x2C69 => 0x2C6A, -0x2C6B => 0x2C6C, -0x2C6D => 0x251, -0x2C6E => 0x271, -0x2C6F => 0x250, -0x2C70 => 0x252, -0x2C72 => 0x2C73, -0x2C75 => 0x2C76, -0x2C7E => 0x23F, -0x2C7F => 0x240, -0x2C80 => 0x2C81, -0x2C82 => 0x2C83, -0x2C84 => 0x2C85, -0x2C86 => 0x2C87, -0x2C88 => 0x2C89, -0x2C8A => 0x2C8B, -0x2C8C => 0x2C8D, -0x2C8E => 0x2C8F, -0x2C90 => 0x2C91, -0x2C92 => 0x2C93, -0x2C94 => 0x2C95, -0x2C96 => 0x2C97, -0x2C98 => 0x2C99, -0x2C9A => 0x2C9B, -0x2C9C => 0x2C9D, -0x2C9E => 0x2C9F, -0x2CA0 => 0x2CA1, -0x2CA2 => 0x2CA3, -0x2CA4 => 0x2CA5, -0x2CA6 => 0x2CA7, -0x2CA8 => 0x2CA9, -0x2CAA => 0x2CAB, -0x2CAC => 0x2CAD, -0x2CAE => 0x2CAF, -0x2CB0 => 0x2CB1, -0x2CB2 => 0x2CB3, -0x2CB4 => 0x2CB5, -0x2CB6 => 0x2CB7, -0x2CB8 => 0x2CB9, -0x2CBA => 0x2CBB, -0x2CBC => 0x2CBD, -0x2CBE => 0x2CBF, -0x2CC0 => 0x2CC1, -0x2CC2 => 0x2CC3, -0x2CC4 => 0x2CC5, -0x2CC6 => 0x2CC7, -0x2CC8 => 0x2CC9, -0x2CCA => 0x2CCB, -0x2CCC => 0x2CCD, -0x2CCE => 0x2CCF, -0x2CD0 => 0x2CD1, -0x2CD2 => 0x2CD3, -0x2CD4 => 0x2CD5, -0x2CD6 => 0x2CD7, -0x2CD8 => 0x2CD9, -0x2CDA => 0x2CDB, -0x2CDC => 0x2CDD, -0x2CDE => 0x2CDF, -0x2CE0 => 0x2CE1, -0x2CE2 => 0x2CE3, -0x2CEB => 0x2CEC, -0x2CED => 0x2CEE, -0x2CF2 => 0x2CF3, -0xA640 => 0xA641, -0xA642 => 0xA643, -0xA644 => 0xA645, -0xA646 => 0xA647, -0xA648 => 0xA649, -0xA64A => 0xA64B, -0xA64C => 0xA64D, -0xA64E => 0xA64F, -0xA650 => 0xA651, -0xA652 => 0xA653, -0xA654 => 0xA655, -0xA656 => 0xA657, -0xA658 => 0xA659, -0xA65A => 0xA65B, -0xA65C => 0xA65D, -0xA65E => 0xA65F, -0xA660 => 0xA661, -0xA662 => 0xA663, -0xA664 => 0xA665, -0xA666 => 0xA667, -0xA668 => 0xA669, -0xA66A => 0xA66B, -0xA66C => 0xA66D, -0xA680 => 0xA681, -0xA682 => 0xA683, -0xA684 => 0xA685, -0xA686 => 0xA687, -0xA688 => 0xA689, -0xA68A => 0xA68B, -0xA68C => 0xA68D, -0xA68E => 0xA68F, -0xA690 => 0xA691, -0xA692 => 0xA693, -0xA694 => 0xA695, -0xA696 => 0xA697, -0xA698 => 0xA699, -0xA69A => 0xA69B, -0xA722 => 0xA723, -0xA724 => 0xA725, -0xA726 => 0xA727, -0xA728 => 0xA729, -0xA72A => 0xA72B, -0xA72C => 0xA72D, -0xA72E => 0xA72F, -0xA732 => 0xA733, -0xA734 => 0xA735, -0xA736 => 0xA737, -0xA738 => 0xA739, -0xA73A => 0xA73B, -0xA73C => 0xA73D, -0xA73E => 0xA73F, -0xA740 => 0xA741, -0xA742 => 0xA743, -0xA744 => 0xA745, -0xA746 => 0xA747, -0xA748 => 0xA749, -0xA74A => 0xA74B, -0xA74C => 0xA74D, -0xA74E => 0xA74F, -0xA750 => 0xA751, -0xA752 => 0xA753, -0xA754 => 0xA755, -0xA756 => 0xA757, -0xA758 => 0xA759, -0xA75A => 0xA75B, -0xA75C => 0xA75D, -0xA75E => 0xA75F, -0xA760 => 0xA761, -0xA762 => 0xA763, -0xA764 => 0xA765, -0xA766 => 0xA767, -0xA768 => 0xA769, -0xA76A => 0xA76B, -0xA76C => 0xA76D, -0xA76E => 0xA76F, -0xA779 => 0xA77A, -0xA77B => 0xA77C, -0xA77D => 0x1D79, -0xA77E => 0xA77F, -0xA780 => 0xA781, -0xA782 => 0xA783, -0xA784 => 0xA785, -0xA786 => 0xA787, -0xA78B => 0xA78C, -0xA78D => 0x265, -0xA790 => 0xA791, -0xA792 => 0xA793, -0xA796 => 0xA797, -0xA798 => 0xA799, -0xA79A => 0xA79B, -0xA79C => 0xA79D, -0xA79E => 0xA79F, -0xA7A0 => 0xA7A1, -0xA7A2 => 0xA7A3, -0xA7A4 => 0xA7A5, -0xA7A6 => 0xA7A7, -0xA7A8 => 0xA7A9, -0xA7AA => 0x266, -0xA7AB => 0x25C, -0xA7AC => 0x261, -0xA7AD => 0x26C, -0xA7AE => 0x26A, -0xA7B0 => 0x29E, -0xA7B1 => 0x287, -0xA7B2 => 0x29D, -0xA7B3 => 0xAB53, -0xA7B4 => 0xA7B5, -0xA7B6 => 0xA7B7, -0xA7B8 => 0xA7B9, -0xA7BA => 0xA7BB, -0xA7BC => 0xA7BD, -0xA7BE => 0xA7BF, -0xA7C2 => 0xA7C3, -0xA7C4 => 0xA794, -0xA7C5 => 0x282, -0xA7C6 => 0x1D8E, -0xA7C7 => 0xA7C8, -0xA7C9 => 0xA7CA, -0xA7F5 => 0xA7F6, -0xFF21 => 0xFF41, -0xFF22 => 0xFF42, -0xFF23 => 0xFF43, -0xFF24 => 0xFF44, -0xFF25 => 0xFF45, -0xFF26 => 0xFF46, -0xFF27 => 0xFF47, -0xFF28 => 0xFF48, -0xFF29 => 0xFF49, -0xFF2A => 0xFF4A, -0xFF2B => 0xFF4B, -0xFF2C => 0xFF4C, -0xFF2D => 0xFF4D, -0xFF2E => 0xFF4E, -0xFF2F => 0xFF4F, -0xFF30 => 0xFF50, -0xFF31 => 0xFF51, -0xFF32 => 0xFF52, -0xFF33 => 0xFF53, -0xFF34 => 0xFF54, -0xFF35 => 0xFF55, -0xFF36 => 0xFF56, -0xFF37 => 0xFF57, -0xFF38 => 0xFF58, -0xFF39 => 0xFF59, -0xFF3A => 0xFF5A, -0x10400 => 0x10428, -0x10401 => 0x10429, -0x10402 => 0x1042A, -0x10403 => 0x1042B, -0x10404 => 0x1042C, -0x10405 => 0x1042D, -0x10406 => 0x1042E, -0x10407 => 0x1042F, -0x10408 => 0x10430, -0x10409 => 0x10431, -0x1040A => 0x10432, -0x1040B => 0x10433, -0x1040C => 0x10434, -0x1040D => 0x10435, -0x1040E => 0x10436, -0x1040F => 0x10437, -0x10410 => 0x10438, -0x10411 => 0x10439, -0x10412 => 0x1043A, -0x10413 => 0x1043B, -0x10414 => 0x1043C, -0x10415 => 0x1043D, -0x10416 => 0x1043E, -0x10417 => 0x1043F, -0x10418 => 0x10440, -0x10419 => 0x10441, -0x1041A => 0x10442, -0x1041B => 0x10443, -0x1041C => 0x10444, -0x1041D => 0x10445, -0x1041E => 0x10446, -0x1041F => 0x10447, -0x10420 => 0x10448, -0x10421 => 0x10449, -0x10422 => 0x1044A, -0x10423 => 0x1044B, -0x10424 => 0x1044C, -0x10425 => 0x1044D, -0x10426 => 0x1044E, -0x10427 => 0x1044F, -0x104B0 => 0x104D8, -0x104B1 => 0x104D9, -0x104B2 => 0x104DA, -0x104B3 => 0x104DB, -0x104B4 => 0x104DC, -0x104B5 => 0x104DD, -0x104B6 => 0x104DE, -0x104B7 => 0x104DF, -0x104B8 => 0x104E0, -0x104B9 => 0x104E1, -0x104BA => 0x104E2, -0x104BB => 0x104E3, -0x104BC => 0x104E4, -0x104BD => 0x104E5, -0x104BE => 0x104E6, -0x104BF => 0x104E7, -0x104C0 => 0x104E8, -0x104C1 => 0x104E9, -0x104C2 => 0x104EA, -0x104C3 => 0x104EB, -0x104C4 => 0x104EC, -0x104C5 => 0x104ED, -0x104C6 => 0x104EE, -0x104C7 => 0x104EF, -0x104C8 => 0x104F0, -0x104C9 => 0x104F1, -0x104CA => 0x104F2, -0x104CB => 0x104F3, -0x104CC => 0x104F4, -0x104CD => 0x104F5, -0x104CE => 0x104F6, -0x104CF => 0x104F7, -0x104D0 => 0x104F8, -0x104D1 => 0x104F9, -0x104D2 => 0x104FA, -0x104D3 => 0x104FB, -0x10C80 => 0x10CC0, -0x10C81 => 0x10CC1, -0x10C82 => 0x10CC2, -0x10C83 => 0x10CC3, -0x10C84 => 0x10CC4, -0x10C85 => 0x10CC5, -0x10C86 => 0x10CC6, -0x10C87 => 0x10CC7, -0x10C88 => 0x10CC8, -0x10C89 => 0x10CC9, -0x10C8A => 0x10CCA, -0x10C8B => 0x10CCB, -0x10C8C => 0x10CCC, -0x10C8D => 0x10CCD, -0x10C8E => 0x10CCE, -0x10C8F => 0x10CCF, -0x10C90 => 0x10CD0, -0x10C91 => 0x10CD1, -0x10C92 => 0x10CD2, -0x10C93 => 0x10CD3, -0x10C94 => 0x10CD4, -0x10C95 => 0x10CD5, -0x10C96 => 0x10CD6, -0x10C97 => 0x10CD7, -0x10C98 => 0x10CD8, -0x10C99 => 0x10CD9, -0x10C9A => 0x10CDA, -0x10C9B => 0x10CDB, -0x10C9C => 0x10CDC, -0x10C9D => 0x10CDD, -0x10C9E => 0x10CDE, -0x10C9F => 0x10CDF, -0x10CA0 => 0x10CE0, -0x10CA1 => 0x10CE1, -0x10CA2 => 0x10CE2, -0x10CA3 => 0x10CE3, -0x10CA4 => 0x10CE4, -0x10CA5 => 0x10CE5, -0x10CA6 => 0x10CE6, -0x10CA7 => 0x10CE7, -0x10CA8 => 0x10CE8, -0x10CA9 => 0x10CE9, -0x10CAA => 0x10CEA, -0x10CAB => 0x10CEB, -0x10CAC => 0x10CEC, -0x10CAD => 0x10CED, -0x10CAE => 0x10CEE, -0x10CAF => 0x10CEF, -0x10CB0 => 0x10CF0, -0x10CB1 => 0x10CF1, -0x10CB2 => 0x10CF2, -0x118A0 => 0x118C0, -0x118A1 => 0x118C1, -0x118A2 => 0x118C2, -0x118A3 => 0x118C3, -0x118A4 => 0x118C4, -0x118A5 => 0x118C5, -0x118A6 => 0x118C6, -0x118A7 => 0x118C7, -0x118A8 => 0x118C8, -0x118A9 => 0x118C9, -0x118AA => 0x118CA, -0x118AB => 0x118CB, -0x118AC => 0x118CC, -0x118AD => 0x118CD, -0x118AE => 0x118CE, -0x118AF => 0x118CF, -0x118B0 => 0x118D0, -0x118B1 => 0x118D1, -0x118B2 => 0x118D2, -0x118B3 => 0x118D3, -0x118B4 => 0x118D4, -0x118B5 => 0x118D5, -0x118B6 => 0x118D6, -0x118B7 => 0x118D7, -0x118B8 => 0x118D8, -0x118B9 => 0x118D9, -0x118BA => 0x118DA, -0x118BB => 0x118DB, -0x118BC => 0x118DC, -0x118BD => 0x118DD, -0x118BE => 0x118DE, -0x118BF => 0x118DF, -0x16E40 => 0x16E60, -0x16E41 => 0x16E61, -0x16E42 => 0x16E62, -0x16E43 => 0x16E63, -0x16E44 => 0x16E64, -0x16E45 => 0x16E65, -0x16E46 => 0x16E66, -0x16E47 => 0x16E67, -0x16E48 => 0x16E68, -0x16E49 => 0x16E69, -0x16E4A => 0x16E6A, -0x16E4B => 0x16E6B, -0x16E4C => 0x16E6C, -0x16E4D => 0x16E6D, -0x16E4E => 0x16E6E, -0x16E4F => 0x16E6F, -0x16E50 => 0x16E70, -0x16E51 => 0x16E71, -0x16E52 => 0x16E72, -0x16E53 => 0x16E73, -0x16E54 => 0x16E74, -0x16E55 => 0x16E75, -0x16E56 => 0x16E76, -0x16E57 => 0x16E77, -0x16E58 => 0x16E78, -0x16E59 => 0x16E79, -0x16E5A => 0x16E7A, -0x16E5B => 0x16E7B, -0x16E5C => 0x16E7C, -0x16E5D => 0x16E7D, -0x16E5E => 0x16E7E, -0x16E5F => 0x16E7F, -0x1E900 => 0x1E922, -0x1E901 => 0x1E923, -0x1E902 => 0x1E924, -0x1E903 => 0x1E925, -0x1E904 => 0x1E926, -0x1E905 => 0x1E927, -0x1E906 => 0x1E928, -0x1E907 => 0x1E929, -0x1E908 => 0x1E92A, -0x1E909 => 0x1E92B, -0x1E90A => 0x1E92C, -0x1E90B => 0x1E92D, -0x1E90C => 0x1E92E, -0x1E90D => 0x1E92F, -0x1E90E => 0x1E930, -0x1E90F => 0x1E931, -0x1E910 => 0x1E932, -0x1E911 => 0x1E933, -0x1E912 => 0x1E934, -0x1E913 => 0x1E935, -0x1E914 => 0x1E936, -0x1E915 => 0x1E937, -0x1E916 => 0x1E938, -0x1E917 => 0x1E939, -0x1E918 => 0x1E93A, -0x1E919 => 0x1E93B, -0x1E91A => 0x1E93C, -0x1E91B => 0x1E93D, -0x1E91C => 0x1E93E, -0x1E91D => 0x1E93F, -0x1E91E => 0x1E940, -0x1E91F => 0x1E941, -0x1E920 => 0x1E942, -0x1E921 => 0x1E943, -]; +return array( + 0x41 => 0x61, + 0x42 => 0x62, + 0x43 => 0x63, + 0x44 => 0x64, + 0x45 => 0x65, + 0x46 => 0x66, + 0x47 => 0x67, + 0x48 => 0x68, + 0x49 => 0x69, + 0x4A => 0x6A, + 0x4B => 0x6B, + 0x4C => 0x6C, + 0x4D => 0x6D, + 0x4E => 0x6E, + 0x4F => 0x6F, + 0x50 => 0x70, + 0x51 => 0x71, + 0x52 => 0x72, + 0x53 => 0x73, + 0x54 => 0x74, + 0x55 => 0x75, + 0x56 => 0x76, + 0x57 => 0x77, + 0x58 => 0x78, + 0x59 => 0x79, + 0x5A => 0x7A, + 0xC0 => 0xE0, + 0xC1 => 0xE1, + 0xC2 => 0xE2, + 0xC3 => 0xE3, + 0xC4 => 0xE4, + 0xC5 => 0xE5, + 0xC6 => 0xE6, + 0xC7 => 0xE7, + 0xC8 => 0xE8, + 0xC9 => 0xE9, + 0xCA => 0xEA, + 0xCB => 0xEB, + 0xCC => 0xEC, + 0xCD => 0xED, + 0xCE => 0xEE, + 0xCF => 0xEF, + 0xD0 => 0xF0, + 0xD1 => 0xF1, + 0xD2 => 0xF2, + 0xD3 => 0xF3, + 0xD4 => 0xF4, + 0xD5 => 0xF5, + 0xD6 => 0xF6, + 0xD8 => 0xF8, + 0xD9 => 0xF9, + 0xDA => 0xFA, + 0xDB => 0xFB, + 0xDC => 0xFC, + 0xDD => 0xFD, + 0xDE => 0xFE, + 0x100 => 0x101, + 0x102 => 0x103, + 0x104 => 0x105, + 0x106 => 0x107, + 0x108 => 0x109, + 0x10A => 0x10B, + 0x10C => 0x10D, + 0x10E => 0x10F, + 0x110 => 0x111, + 0x112 => 0x113, + 0x114 => 0x115, + 0x116 => 0x117, + 0x118 => 0x119, + 0x11A => 0x11B, + 0x11C => 0x11D, + 0x11E => 0x11F, + 0x120 => 0x121, + 0x122 => 0x123, + 0x124 => 0x125, + 0x126 => 0x127, + 0x128 => 0x129, + 0x12A => 0x12B, + 0x12C => 0x12D, + 0x12E => 0x12F, + 0x130 => 0x69, + 0x132 => 0x133, + 0x134 => 0x135, + 0x136 => 0x137, + 0x139 => 0x13A, + 0x13B => 0x13C, + 0x13D => 0x13E, + 0x13F => 0x140, + 0x141 => 0x142, + 0x143 => 0x144, + 0x145 => 0x146, + 0x147 => 0x148, + 0x14A => 0x14B, + 0x14C => 0x14D, + 0x14E => 0x14F, + 0x150 => 0x151, + 0x152 => 0x153, + 0x154 => 0x155, + 0x156 => 0x157, + 0x158 => 0x159, + 0x15A => 0x15B, + 0x15C => 0x15D, + 0x15E => 0x15F, + 0x160 => 0x161, + 0x162 => 0x163, + 0x164 => 0x165, + 0x166 => 0x167, + 0x168 => 0x169, + 0x16A => 0x16B, + 0x16C => 0x16D, + 0x16E => 0x16F, + 0x170 => 0x171, + 0x172 => 0x173, + 0x174 => 0x175, + 0x176 => 0x177, + 0x178 => 0xFF, + 0x179 => 0x17A, + 0x17B => 0x17C, + 0x17D => 0x17E, + 0x181 => 0x253, + 0x182 => 0x183, + 0x184 => 0x185, + 0x186 => 0x254, + 0x187 => 0x188, + 0x189 => 0x256, + 0x18A => 0x257, + 0x18B => 0x18C, + 0x18E => 0x1DD, + 0x18F => 0x259, + 0x190 => 0x25B, + 0x191 => 0x192, + 0x193 => 0x260, + 0x194 => 0x263, + 0x196 => 0x269, + 0x197 => 0x268, + 0x198 => 0x199, + 0x19C => 0x26F, + 0x19D => 0x272, + 0x19F => 0x275, + 0x1A0 => 0x1A1, + 0x1A2 => 0x1A3, + 0x1A4 => 0x1A5, + 0x1A6 => 0x280, + 0x1A7 => 0x1A8, + 0x1A9 => 0x283, + 0x1AC => 0x1AD, + 0x1AE => 0x288, + 0x1AF => 0x1B0, + 0x1B1 => 0x28A, + 0x1B2 => 0x28B, + 0x1B3 => 0x1B4, + 0x1B5 => 0x1B6, + 0x1B7 => 0x292, + 0x1B8 => 0x1B9, + 0x1BC => 0x1BD, + 0x1C4 => 0x1C6, + 0x1C5 => 0x1C6, + 0x1C7 => 0x1C9, + 0x1C8 => 0x1C9, + 0x1CA => 0x1CC, + 0x1CB => 0x1CC, + 0x1CD => 0x1CE, + 0x1CF => 0x1D0, + 0x1D1 => 0x1D2, + 0x1D3 => 0x1D4, + 0x1D5 => 0x1D6, + 0x1D7 => 0x1D8, + 0x1D9 => 0x1DA, + 0x1DB => 0x1DC, + 0x1DE => 0x1DF, + 0x1E0 => 0x1E1, + 0x1E2 => 0x1E3, + 0x1E4 => 0x1E5, + 0x1E6 => 0x1E7, + 0x1E8 => 0x1E9, + 0x1EA => 0x1EB, + 0x1EC => 0x1ED, + 0x1EE => 0x1EF, + 0x1F1 => 0x1F3, + 0x1F2 => 0x1F3, + 0x1F4 => 0x1F5, + 0x1F6 => 0x195, + 0x1F7 => 0x1BF, + 0x1F8 => 0x1F9, + 0x1FA => 0x1FB, + 0x1FC => 0x1FD, + 0x1FE => 0x1FF, + 0x200 => 0x201, + 0x202 => 0x203, + 0x204 => 0x205, + 0x206 => 0x207, + 0x208 => 0x209, + 0x20A => 0x20B, + 0x20C => 0x20D, + 0x20E => 0x20F, + 0x210 => 0x211, + 0x212 => 0x213, + 0x214 => 0x215, + 0x216 => 0x217, + 0x218 => 0x219, + 0x21A => 0x21B, + 0x21C => 0x21D, + 0x21E => 0x21F, + 0x220 => 0x19E, + 0x222 => 0x223, + 0x224 => 0x225, + 0x226 => 0x227, + 0x228 => 0x229, + 0x22A => 0x22B, + 0x22C => 0x22D, + 0x22E => 0x22F, + 0x230 => 0x231, + 0x232 => 0x233, + 0x23A => 0x2C65, + 0x23B => 0x23C, + 0x23D => 0x19A, + 0x23E => 0x2C66, + 0x241 => 0x242, + 0x243 => 0x180, + 0x244 => 0x289, + 0x245 => 0x28C, + 0x246 => 0x247, + 0x248 => 0x249, + 0x24A => 0x24B, + 0x24C => 0x24D, + 0x24E => 0x24F, + 0x370 => 0x371, + 0x372 => 0x373, + 0x376 => 0x377, + 0x37F => 0x3F3, + 0x386 => 0x3AC, + 0x388 => 0x3AD, + 0x389 => 0x3AE, + 0x38A => 0x3AF, + 0x38C => 0x3CC, + 0x38E => 0x3CD, + 0x38F => 0x3CE, + 0x391 => 0x3B1, + 0x392 => 0x3B2, + 0x393 => 0x3B3, + 0x394 => 0x3B4, + 0x395 => 0x3B5, + 0x396 => 0x3B6, + 0x397 => 0x3B7, + 0x398 => 0x3B8, + 0x399 => 0x3B9, + 0x39A => 0x3BA, + 0x39B => 0x3BB, + 0x39C => 0x3BC, + 0x39D => 0x3BD, + 0x39E => 0x3BE, + 0x39F => 0x3BF, + 0x3A0 => 0x3C0, + 0x3A1 => 0x3C1, + 0x3A3 => 0x3C3, + 0x3A4 => 0x3C4, + 0x3A5 => 0x3C5, + 0x3A6 => 0x3C6, + 0x3A7 => 0x3C7, + 0x3A8 => 0x3C8, + 0x3A9 => 0x3C9, + 0x3AA => 0x3CA, + 0x3AB => 0x3CB, + 0x3CF => 0x3D7, + 0x3D8 => 0x3D9, + 0x3DA => 0x3DB, + 0x3DC => 0x3DD, + 0x3DE => 0x3DF, + 0x3E0 => 0x3E1, + 0x3E2 => 0x3E3, + 0x3E4 => 0x3E5, + 0x3E6 => 0x3E7, + 0x3E8 => 0x3E9, + 0x3EA => 0x3EB, + 0x3EC => 0x3ED, + 0x3EE => 0x3EF, + 0x3F4 => 0x3B8, + 0x3F7 => 0x3F8, + 0x3F9 => 0x3F2, + 0x3FA => 0x3FB, + 0x3FD => 0x37B, + 0x3FE => 0x37C, + 0x3FF => 0x37D, + 0x400 => 0x450, + 0x401 => 0x451, + 0x402 => 0x452, + 0x403 => 0x453, + 0x404 => 0x454, + 0x405 => 0x455, + 0x406 => 0x456, + 0x407 => 0x457, + 0x408 => 0x458, + 0x409 => 0x459, + 0x40A => 0x45A, + 0x40B => 0x45B, + 0x40C => 0x45C, + 0x40D => 0x45D, + 0x40E => 0x45E, + 0x40F => 0x45F, + 0x410 => 0x430, + 0x411 => 0x431, + 0x412 => 0x432, + 0x413 => 0x433, + 0x414 => 0x434, + 0x415 => 0x435, + 0x416 => 0x436, + 0x417 => 0x437, + 0x418 => 0x438, + 0x419 => 0x439, + 0x41A => 0x43A, + 0x41B => 0x43B, + 0x41C => 0x43C, + 0x41D => 0x43D, + 0x41E => 0x43E, + 0x41F => 0x43F, + 0x420 => 0x440, + 0x421 => 0x441, + 0x422 => 0x442, + 0x423 => 0x443, + 0x424 => 0x444, + 0x425 => 0x445, + 0x426 => 0x446, + 0x427 => 0x447, + 0x428 => 0x448, + 0x429 => 0x449, + 0x42A => 0x44A, + 0x42B => 0x44B, + 0x42C => 0x44C, + 0x42D => 0x44D, + 0x42E => 0x44E, + 0x42F => 0x44F, + 0x460 => 0x461, + 0x462 => 0x463, + 0x464 => 0x465, + 0x466 => 0x467, + 0x468 => 0x469, + 0x46A => 0x46B, + 0x46C => 0x46D, + 0x46E => 0x46F, + 0x470 => 0x471, + 0x472 => 0x473, + 0x474 => 0x475, + 0x476 => 0x477, + 0x478 => 0x479, + 0x47A => 0x47B, + 0x47C => 0x47D, + 0x47E => 0x47F, + 0x480 => 0x481, + 0x48A => 0x48B, + 0x48C => 0x48D, + 0x48E => 0x48F, + 0x490 => 0x491, + 0x492 => 0x493, + 0x494 => 0x495, + 0x496 => 0x497, + 0x498 => 0x499, + 0x49A => 0x49B, + 0x49C => 0x49D, + 0x49E => 0x49F, + 0x4A0 => 0x4A1, + 0x4A2 => 0x4A3, + 0x4A4 => 0x4A5, + 0x4A6 => 0x4A7, + 0x4A8 => 0x4A9, + 0x4AA => 0x4AB, + 0x4AC => 0x4AD, + 0x4AE => 0x4AF, + 0x4B0 => 0x4B1, + 0x4B2 => 0x4B3, + 0x4B4 => 0x4B5, + 0x4B6 => 0x4B7, + 0x4B8 => 0x4B9, + 0x4BA => 0x4BB, + 0x4BC => 0x4BD, + 0x4BE => 0x4BF, + 0x4C0 => 0x4CF, + 0x4C1 => 0x4C2, + 0x4C3 => 0x4C4, + 0x4C5 => 0x4C6, + 0x4C7 => 0x4C8, + 0x4C9 => 0x4CA, + 0x4CB => 0x4CC, + 0x4CD => 0x4CE, + 0x4D0 => 0x4D1, + 0x4D2 => 0x4D3, + 0x4D4 => 0x4D5, + 0x4D6 => 0x4D7, + 0x4D8 => 0x4D9, + 0x4DA => 0x4DB, + 0x4DC => 0x4DD, + 0x4DE => 0x4DF, + 0x4E0 => 0x4E1, + 0x4E2 => 0x4E3, + 0x4E4 => 0x4E5, + 0x4E6 => 0x4E7, + 0x4E8 => 0x4E9, + 0x4EA => 0x4EB, + 0x4EC => 0x4ED, + 0x4EE => 0x4EF, + 0x4F0 => 0x4F1, + 0x4F2 => 0x4F3, + 0x4F4 => 0x4F5, + 0x4F6 => 0x4F7, + 0x4F8 => 0x4F9, + 0x4FA => 0x4FB, + 0x4FC => 0x4FD, + 0x4FE => 0x4FF, + 0x500 => 0x501, + 0x502 => 0x503, + 0x504 => 0x505, + 0x506 => 0x507, + 0x508 => 0x509, + 0x50A => 0x50B, + 0x50C => 0x50D, + 0x50E => 0x50F, + 0x510 => 0x511, + 0x512 => 0x513, + 0x514 => 0x515, + 0x516 => 0x517, + 0x518 => 0x519, + 0x51A => 0x51B, + 0x51C => 0x51D, + 0x51E => 0x51F, + 0x520 => 0x521, + 0x522 => 0x523, + 0x524 => 0x525, + 0x526 => 0x527, + 0x528 => 0x529, + 0x52A => 0x52B, + 0x52C => 0x52D, + 0x52E => 0x52F, + 0x531 => 0x561, + 0x532 => 0x562, + 0x533 => 0x563, + 0x534 => 0x564, + 0x535 => 0x565, + 0x536 => 0x566, + 0x537 => 0x567, + 0x538 => 0x568, + 0x539 => 0x569, + 0x53A => 0x56A, + 0x53B => 0x56B, + 0x53C => 0x56C, + 0x53D => 0x56D, + 0x53E => 0x56E, + 0x53F => 0x56F, + 0x540 => 0x570, + 0x541 => 0x571, + 0x542 => 0x572, + 0x543 => 0x573, + 0x544 => 0x574, + 0x545 => 0x575, + 0x546 => 0x576, + 0x547 => 0x577, + 0x548 => 0x578, + 0x549 => 0x579, + 0x54A => 0x57A, + 0x54B => 0x57B, + 0x54C => 0x57C, + 0x54D => 0x57D, + 0x54E => 0x57E, + 0x54F => 0x57F, + 0x550 => 0x580, + 0x551 => 0x581, + 0x552 => 0x582, + 0x553 => 0x583, + 0x554 => 0x584, + 0x555 => 0x585, + 0x556 => 0x586, + 0x10A0 => 0x2D00, + 0x10A1 => 0x2D01, + 0x10A2 => 0x2D02, + 0x10A3 => 0x2D03, + 0x10A4 => 0x2D04, + 0x10A5 => 0x2D05, + 0x10A6 => 0x2D06, + 0x10A7 => 0x2D07, + 0x10A8 => 0x2D08, + 0x10A9 => 0x2D09, + 0x10AA => 0x2D0A, + 0x10AB => 0x2D0B, + 0x10AC => 0x2D0C, + 0x10AD => 0x2D0D, + 0x10AE => 0x2D0E, + 0x10AF => 0x2D0F, + 0x10B0 => 0x2D10, + 0x10B1 => 0x2D11, + 0x10B2 => 0x2D12, + 0x10B3 => 0x2D13, + 0x10B4 => 0x2D14, + 0x10B5 => 0x2D15, + 0x10B6 => 0x2D16, + 0x10B7 => 0x2D17, + 0x10B8 => 0x2D18, + 0x10B9 => 0x2D19, + 0x10BA => 0x2D1A, + 0x10BB => 0x2D1B, + 0x10BC => 0x2D1C, + 0x10BD => 0x2D1D, + 0x10BE => 0x2D1E, + 0x10BF => 0x2D1F, + 0x10C0 => 0x2D20, + 0x10C1 => 0x2D21, + 0x10C2 => 0x2D22, + 0x10C3 => 0x2D23, + 0x10C4 => 0x2D24, + 0x10C5 => 0x2D25, + 0x10C7 => 0x2D27, + 0x10CD => 0x2D2D, + 0x13A0 => 0xAB70, + 0x13A1 => 0xAB71, + 0x13A2 => 0xAB72, + 0x13A3 => 0xAB73, + 0x13A4 => 0xAB74, + 0x13A5 => 0xAB75, + 0x13A6 => 0xAB76, + 0x13A7 => 0xAB77, + 0x13A8 => 0xAB78, + 0x13A9 => 0xAB79, + 0x13AA => 0xAB7A, + 0x13AB => 0xAB7B, + 0x13AC => 0xAB7C, + 0x13AD => 0xAB7D, + 0x13AE => 0xAB7E, + 0x13AF => 0xAB7F, + 0x13B0 => 0xAB80, + 0x13B1 => 0xAB81, + 0x13B2 => 0xAB82, + 0x13B3 => 0xAB83, + 0x13B4 => 0xAB84, + 0x13B5 => 0xAB85, + 0x13B6 => 0xAB86, + 0x13B7 => 0xAB87, + 0x13B8 => 0xAB88, + 0x13B9 => 0xAB89, + 0x13BA => 0xAB8A, + 0x13BB => 0xAB8B, + 0x13BC => 0xAB8C, + 0x13BD => 0xAB8D, + 0x13BE => 0xAB8E, + 0x13BF => 0xAB8F, + 0x13C0 => 0xAB90, + 0x13C1 => 0xAB91, + 0x13C2 => 0xAB92, + 0x13C3 => 0xAB93, + 0x13C4 => 0xAB94, + 0x13C5 => 0xAB95, + 0x13C6 => 0xAB96, + 0x13C7 => 0xAB97, + 0x13C8 => 0xAB98, + 0x13C9 => 0xAB99, + 0x13CA => 0xAB9A, + 0x13CB => 0xAB9B, + 0x13CC => 0xAB9C, + 0x13CD => 0xAB9D, + 0x13CE => 0xAB9E, + 0x13CF => 0xAB9F, + 0x13D0 => 0xABA0, + 0x13D1 => 0xABA1, + 0x13D2 => 0xABA2, + 0x13D3 => 0xABA3, + 0x13D4 => 0xABA4, + 0x13D5 => 0xABA5, + 0x13D6 => 0xABA6, + 0x13D7 => 0xABA7, + 0x13D8 => 0xABA8, + 0x13D9 => 0xABA9, + 0x13DA => 0xABAA, + 0x13DB => 0xABAB, + 0x13DC => 0xABAC, + 0x13DD => 0xABAD, + 0x13DE => 0xABAE, + 0x13DF => 0xABAF, + 0x13E0 => 0xABB0, + 0x13E1 => 0xABB1, + 0x13E2 => 0xABB2, + 0x13E3 => 0xABB3, + 0x13E4 => 0xABB4, + 0x13E5 => 0xABB5, + 0x13E6 => 0xABB6, + 0x13E7 => 0xABB7, + 0x13E8 => 0xABB8, + 0x13E9 => 0xABB9, + 0x13EA => 0xABBA, + 0x13EB => 0xABBB, + 0x13EC => 0xABBC, + 0x13ED => 0xABBD, + 0x13EE => 0xABBE, + 0x13EF => 0xABBF, + 0x13F0 => 0x13F8, + 0x13F1 => 0x13F9, + 0x13F2 => 0x13FA, + 0x13F3 => 0x13FB, + 0x13F4 => 0x13FC, + 0x13F5 => 0x13FD, + 0x1C90 => 0x10D0, + 0x1C91 => 0x10D1, + 0x1C92 => 0x10D2, + 0x1C93 => 0x10D3, + 0x1C94 => 0x10D4, + 0x1C95 => 0x10D5, + 0x1C96 => 0x10D6, + 0x1C97 => 0x10D7, + 0x1C98 => 0x10D8, + 0x1C99 => 0x10D9, + 0x1C9A => 0x10DA, + 0x1C9B => 0x10DB, + 0x1C9C => 0x10DC, + 0x1C9D => 0x10DD, + 0x1C9E => 0x10DE, + 0x1C9F => 0x10DF, + 0x1CA0 => 0x10E0, + 0x1CA1 => 0x10E1, + 0x1CA2 => 0x10E2, + 0x1CA3 => 0x10E3, + 0x1CA4 => 0x10E4, + 0x1CA5 => 0x10E5, + 0x1CA6 => 0x10E6, + 0x1CA7 => 0x10E7, + 0x1CA8 => 0x10E8, + 0x1CA9 => 0x10E9, + 0x1CAA => 0x10EA, + 0x1CAB => 0x10EB, + 0x1CAC => 0x10EC, + 0x1CAD => 0x10ED, + 0x1CAE => 0x10EE, + 0x1CAF => 0x10EF, + 0x1CB0 => 0x10F0, + 0x1CB1 => 0x10F1, + 0x1CB2 => 0x10F2, + 0x1CB3 => 0x10F3, + 0x1CB4 => 0x10F4, + 0x1CB5 => 0x10F5, + 0x1CB6 => 0x10F6, + 0x1CB7 => 0x10F7, + 0x1CB8 => 0x10F8, + 0x1CB9 => 0x10F9, + 0x1CBA => 0x10FA, + 0x1CBD => 0x10FD, + 0x1CBE => 0x10FE, + 0x1CBF => 0x10FF, + 0x1E00 => 0x1E01, + 0x1E02 => 0x1E03, + 0x1E04 => 0x1E05, + 0x1E06 => 0x1E07, + 0x1E08 => 0x1E09, + 0x1E0A => 0x1E0B, + 0x1E0C => 0x1E0D, + 0x1E0E => 0x1E0F, + 0x1E10 => 0x1E11, + 0x1E12 => 0x1E13, + 0x1E14 => 0x1E15, + 0x1E16 => 0x1E17, + 0x1E18 => 0x1E19, + 0x1E1A => 0x1E1B, + 0x1E1C => 0x1E1D, + 0x1E1E => 0x1E1F, + 0x1E20 => 0x1E21, + 0x1E22 => 0x1E23, + 0x1E24 => 0x1E25, + 0x1E26 => 0x1E27, + 0x1E28 => 0x1E29, + 0x1E2A => 0x1E2B, + 0x1E2C => 0x1E2D, + 0x1E2E => 0x1E2F, + 0x1E30 => 0x1E31, + 0x1E32 => 0x1E33, + 0x1E34 => 0x1E35, + 0x1E36 => 0x1E37, + 0x1E38 => 0x1E39, + 0x1E3A => 0x1E3B, + 0x1E3C => 0x1E3D, + 0x1E3E => 0x1E3F, + 0x1E40 => 0x1E41, + 0x1E42 => 0x1E43, + 0x1E44 => 0x1E45, + 0x1E46 => 0x1E47, + 0x1E48 => 0x1E49, + 0x1E4A => 0x1E4B, + 0x1E4C => 0x1E4D, + 0x1E4E => 0x1E4F, + 0x1E50 => 0x1E51, + 0x1E52 => 0x1E53, + 0x1E54 => 0x1E55, + 0x1E56 => 0x1E57, + 0x1E58 => 0x1E59, + 0x1E5A => 0x1E5B, + 0x1E5C => 0x1E5D, + 0x1E5E => 0x1E5F, + 0x1E60 => 0x1E61, + 0x1E62 => 0x1E63, + 0x1E64 => 0x1E65, + 0x1E66 => 0x1E67, + 0x1E68 => 0x1E69, + 0x1E6A => 0x1E6B, + 0x1E6C => 0x1E6D, + 0x1E6E => 0x1E6F, + 0x1E70 => 0x1E71, + 0x1E72 => 0x1E73, + 0x1E74 => 0x1E75, + 0x1E76 => 0x1E77, + 0x1E78 => 0x1E79, + 0x1E7A => 0x1E7B, + 0x1E7C => 0x1E7D, + 0x1E7E => 0x1E7F, + 0x1E80 => 0x1E81, + 0x1E82 => 0x1E83, + 0x1E84 => 0x1E85, + 0x1E86 => 0x1E87, + 0x1E88 => 0x1E89, + 0x1E8A => 0x1E8B, + 0x1E8C => 0x1E8D, + 0x1E8E => 0x1E8F, + 0x1E90 => 0x1E91, + 0x1E92 => 0x1E93, + 0x1E94 => 0x1E95, + 0x1E9E => 0xDF, + 0x1EA0 => 0x1EA1, + 0x1EA2 => 0x1EA3, + 0x1EA4 => 0x1EA5, + 0x1EA6 => 0x1EA7, + 0x1EA8 => 0x1EA9, + 0x1EAA => 0x1EAB, + 0x1EAC => 0x1EAD, + 0x1EAE => 0x1EAF, + 0x1EB0 => 0x1EB1, + 0x1EB2 => 0x1EB3, + 0x1EB4 => 0x1EB5, + 0x1EB6 => 0x1EB7, + 0x1EB8 => 0x1EB9, + 0x1EBA => 0x1EBB, + 0x1EBC => 0x1EBD, + 0x1EBE => 0x1EBF, + 0x1EC0 => 0x1EC1, + 0x1EC2 => 0x1EC3, + 0x1EC4 => 0x1EC5, + 0x1EC6 => 0x1EC7, + 0x1EC8 => 0x1EC9, + 0x1ECA => 0x1ECB, + 0x1ECC => 0x1ECD, + 0x1ECE => 0x1ECF, + 0x1ED0 => 0x1ED1, + 0x1ED2 => 0x1ED3, + 0x1ED4 => 0x1ED5, + 0x1ED6 => 0x1ED7, + 0x1ED8 => 0x1ED9, + 0x1EDA => 0x1EDB, + 0x1EDC => 0x1EDD, + 0x1EDE => 0x1EDF, + 0x1EE0 => 0x1EE1, + 0x1EE2 => 0x1EE3, + 0x1EE4 => 0x1EE5, + 0x1EE6 => 0x1EE7, + 0x1EE8 => 0x1EE9, + 0x1EEA => 0x1EEB, + 0x1EEC => 0x1EED, + 0x1EEE => 0x1EEF, + 0x1EF0 => 0x1EF1, + 0x1EF2 => 0x1EF3, + 0x1EF4 => 0x1EF5, + 0x1EF6 => 0x1EF7, + 0x1EF8 => 0x1EF9, + 0x1EFA => 0x1EFB, + 0x1EFC => 0x1EFD, + 0x1EFE => 0x1EFF, + 0x1F08 => 0x1F00, + 0x1F09 => 0x1F01, + 0x1F0A => 0x1F02, + 0x1F0B => 0x1F03, + 0x1F0C => 0x1F04, + 0x1F0D => 0x1F05, + 0x1F0E => 0x1F06, + 0x1F0F => 0x1F07, + 0x1F18 => 0x1F10, + 0x1F19 => 0x1F11, + 0x1F1A => 0x1F12, + 0x1F1B => 0x1F13, + 0x1F1C => 0x1F14, + 0x1F1D => 0x1F15, + 0x1F28 => 0x1F20, + 0x1F29 => 0x1F21, + 0x1F2A => 0x1F22, + 0x1F2B => 0x1F23, + 0x1F2C => 0x1F24, + 0x1F2D => 0x1F25, + 0x1F2E => 0x1F26, + 0x1F2F => 0x1F27, + 0x1F38 => 0x1F30, + 0x1F39 => 0x1F31, + 0x1F3A => 0x1F32, + 0x1F3B => 0x1F33, + 0x1F3C => 0x1F34, + 0x1F3D => 0x1F35, + 0x1F3E => 0x1F36, + 0x1F3F => 0x1F37, + 0x1F48 => 0x1F40, + 0x1F49 => 0x1F41, + 0x1F4A => 0x1F42, + 0x1F4B => 0x1F43, + 0x1F4C => 0x1F44, + 0x1F4D => 0x1F45, + 0x1F59 => 0x1F51, + 0x1F5B => 0x1F53, + 0x1F5D => 0x1F55, + 0x1F5F => 0x1F57, + 0x1F68 => 0x1F60, + 0x1F69 => 0x1F61, + 0x1F6A => 0x1F62, + 0x1F6B => 0x1F63, + 0x1F6C => 0x1F64, + 0x1F6D => 0x1F65, + 0x1F6E => 0x1F66, + 0x1F6F => 0x1F67, + 0x1F88 => 0x1F80, + 0x1F89 => 0x1F81, + 0x1F8A => 0x1F82, + 0x1F8B => 0x1F83, + 0x1F8C => 0x1F84, + 0x1F8D => 0x1F85, + 0x1F8E => 0x1F86, + 0x1F8F => 0x1F87, + 0x1F98 => 0x1F90, + 0x1F99 => 0x1F91, + 0x1F9A => 0x1F92, + 0x1F9B => 0x1F93, + 0x1F9C => 0x1F94, + 0x1F9D => 0x1F95, + 0x1F9E => 0x1F96, + 0x1F9F => 0x1F97, + 0x1FA8 => 0x1FA0, + 0x1FA9 => 0x1FA1, + 0x1FAA => 0x1FA2, + 0x1FAB => 0x1FA3, + 0x1FAC => 0x1FA4, + 0x1FAD => 0x1FA5, + 0x1FAE => 0x1FA6, + 0x1FAF => 0x1FA7, + 0x1FB8 => 0x1FB0, + 0x1FB9 => 0x1FB1, + 0x1FBA => 0x1F70, + 0x1FBB => 0x1F71, + 0x1FBC => 0x1FB3, + 0x1FC8 => 0x1F72, + 0x1FC9 => 0x1F73, + 0x1FCA => 0x1F74, + 0x1FCB => 0x1F75, + 0x1FCC => 0x1FC3, + 0x1FD8 => 0x1FD0, + 0x1FD9 => 0x1FD1, + 0x1FDA => 0x1F76, + 0x1FDB => 0x1F77, + 0x1FE8 => 0x1FE0, + 0x1FE9 => 0x1FE1, + 0x1FEA => 0x1F7A, + 0x1FEB => 0x1F7B, + 0x1FEC => 0x1FE5, + 0x1FF8 => 0x1F78, + 0x1FF9 => 0x1F79, + 0x1FFA => 0x1F7C, + 0x1FFB => 0x1F7D, + 0x1FFC => 0x1FF3, + 0x2126 => 0x3C9, + 0x212A => 0x6B, + 0x212B => 0xE5, + 0x2132 => 0x214E, + 0x2160 => 0x2170, + 0x2161 => 0x2171, + 0x2162 => 0x2172, + 0x2163 => 0x2173, + 0x2164 => 0x2174, + 0x2165 => 0x2175, + 0x2166 => 0x2176, + 0x2167 => 0x2177, + 0x2168 => 0x2178, + 0x2169 => 0x2179, + 0x216A => 0x217A, + 0x216B => 0x217B, + 0x216C => 0x217C, + 0x216D => 0x217D, + 0x216E => 0x217E, + 0x216F => 0x217F, + 0x2183 => 0x2184, + 0x24B6 => 0x24D0, + 0x24B7 => 0x24D1, + 0x24B8 => 0x24D2, + 0x24B9 => 0x24D3, + 0x24BA => 0x24D4, + 0x24BB => 0x24D5, + 0x24BC => 0x24D6, + 0x24BD => 0x24D7, + 0x24BE => 0x24D8, + 0x24BF => 0x24D9, + 0x24C0 => 0x24DA, + 0x24C1 => 0x24DB, + 0x24C2 => 0x24DC, + 0x24C3 => 0x24DD, + 0x24C4 => 0x24DE, + 0x24C5 => 0x24DF, + 0x24C6 => 0x24E0, + 0x24C7 => 0x24E1, + 0x24C8 => 0x24E2, + 0x24C9 => 0x24E3, + 0x24CA => 0x24E4, + 0x24CB => 0x24E5, + 0x24CC => 0x24E6, + 0x24CD => 0x24E7, + 0x24CE => 0x24E8, + 0x24CF => 0x24E9, + 0x2C00 => 0x2C30, + 0x2C01 => 0x2C31, + 0x2C02 => 0x2C32, + 0x2C03 => 0x2C33, + 0x2C04 => 0x2C34, + 0x2C05 => 0x2C35, + 0x2C06 => 0x2C36, + 0x2C07 => 0x2C37, + 0x2C08 => 0x2C38, + 0x2C09 => 0x2C39, + 0x2C0A => 0x2C3A, + 0x2C0B => 0x2C3B, + 0x2C0C => 0x2C3C, + 0x2C0D => 0x2C3D, + 0x2C0E => 0x2C3E, + 0x2C0F => 0x2C3F, + 0x2C10 => 0x2C40, + 0x2C11 => 0x2C41, + 0x2C12 => 0x2C42, + 0x2C13 => 0x2C43, + 0x2C14 => 0x2C44, + 0x2C15 => 0x2C45, + 0x2C16 => 0x2C46, + 0x2C17 => 0x2C47, + 0x2C18 => 0x2C48, + 0x2C19 => 0x2C49, + 0x2C1A => 0x2C4A, + 0x2C1B => 0x2C4B, + 0x2C1C => 0x2C4C, + 0x2C1D => 0x2C4D, + 0x2C1E => 0x2C4E, + 0x2C1F => 0x2C4F, + 0x2C20 => 0x2C50, + 0x2C21 => 0x2C51, + 0x2C22 => 0x2C52, + 0x2C23 => 0x2C53, + 0x2C24 => 0x2C54, + 0x2C25 => 0x2C55, + 0x2C26 => 0x2C56, + 0x2C27 => 0x2C57, + 0x2C28 => 0x2C58, + 0x2C29 => 0x2C59, + 0x2C2A => 0x2C5A, + 0x2C2B => 0x2C5B, + 0x2C2C => 0x2C5C, + 0x2C2D => 0x2C5D, + 0x2C2E => 0x2C5E, + 0x2C60 => 0x2C61, + 0x2C62 => 0x26B, + 0x2C63 => 0x1D7D, + 0x2C64 => 0x27D, + 0x2C67 => 0x2C68, + 0x2C69 => 0x2C6A, + 0x2C6B => 0x2C6C, + 0x2C6D => 0x251, + 0x2C6E => 0x271, + 0x2C6F => 0x250, + 0x2C70 => 0x252, + 0x2C72 => 0x2C73, + 0x2C75 => 0x2C76, + 0x2C7E => 0x23F, + 0x2C7F => 0x240, + 0x2C80 => 0x2C81, + 0x2C82 => 0x2C83, + 0x2C84 => 0x2C85, + 0x2C86 => 0x2C87, + 0x2C88 => 0x2C89, + 0x2C8A => 0x2C8B, + 0x2C8C => 0x2C8D, + 0x2C8E => 0x2C8F, + 0x2C90 => 0x2C91, + 0x2C92 => 0x2C93, + 0x2C94 => 0x2C95, + 0x2C96 => 0x2C97, + 0x2C98 => 0x2C99, + 0x2C9A => 0x2C9B, + 0x2C9C => 0x2C9D, + 0x2C9E => 0x2C9F, + 0x2CA0 => 0x2CA1, + 0x2CA2 => 0x2CA3, + 0x2CA4 => 0x2CA5, + 0x2CA6 => 0x2CA7, + 0x2CA8 => 0x2CA9, + 0x2CAA => 0x2CAB, + 0x2CAC => 0x2CAD, + 0x2CAE => 0x2CAF, + 0x2CB0 => 0x2CB1, + 0x2CB2 => 0x2CB3, + 0x2CB4 => 0x2CB5, + 0x2CB6 => 0x2CB7, + 0x2CB8 => 0x2CB9, + 0x2CBA => 0x2CBB, + 0x2CBC => 0x2CBD, + 0x2CBE => 0x2CBF, + 0x2CC0 => 0x2CC1, + 0x2CC2 => 0x2CC3, + 0x2CC4 => 0x2CC5, + 0x2CC6 => 0x2CC7, + 0x2CC8 => 0x2CC9, + 0x2CCA => 0x2CCB, + 0x2CCC => 0x2CCD, + 0x2CCE => 0x2CCF, + 0x2CD0 => 0x2CD1, + 0x2CD2 => 0x2CD3, + 0x2CD4 => 0x2CD5, + 0x2CD6 => 0x2CD7, + 0x2CD8 => 0x2CD9, + 0x2CDA => 0x2CDB, + 0x2CDC => 0x2CDD, + 0x2CDE => 0x2CDF, + 0x2CE0 => 0x2CE1, + 0x2CE2 => 0x2CE3, + 0x2CEB => 0x2CEC, + 0x2CED => 0x2CEE, + 0x2CF2 => 0x2CF3, + 0xA640 => 0xA641, + 0xA642 => 0xA643, + 0xA644 => 0xA645, + 0xA646 => 0xA647, + 0xA648 => 0xA649, + 0xA64A => 0xA64B, + 0xA64C => 0xA64D, + 0xA64E => 0xA64F, + 0xA650 => 0xA651, + 0xA652 => 0xA653, + 0xA654 => 0xA655, + 0xA656 => 0xA657, + 0xA658 => 0xA659, + 0xA65A => 0xA65B, + 0xA65C => 0xA65D, + 0xA65E => 0xA65F, + 0xA660 => 0xA661, + 0xA662 => 0xA663, + 0xA664 => 0xA665, + 0xA666 => 0xA667, + 0xA668 => 0xA669, + 0xA66A => 0xA66B, + 0xA66C => 0xA66D, + 0xA680 => 0xA681, + 0xA682 => 0xA683, + 0xA684 => 0xA685, + 0xA686 => 0xA687, + 0xA688 => 0xA689, + 0xA68A => 0xA68B, + 0xA68C => 0xA68D, + 0xA68E => 0xA68F, + 0xA690 => 0xA691, + 0xA692 => 0xA693, + 0xA694 => 0xA695, + 0xA696 => 0xA697, + 0xA698 => 0xA699, + 0xA69A => 0xA69B, + 0xA722 => 0xA723, + 0xA724 => 0xA725, + 0xA726 => 0xA727, + 0xA728 => 0xA729, + 0xA72A => 0xA72B, + 0xA72C => 0xA72D, + 0xA72E => 0xA72F, + 0xA732 => 0xA733, + 0xA734 => 0xA735, + 0xA736 => 0xA737, + 0xA738 => 0xA739, + 0xA73A => 0xA73B, + 0xA73C => 0xA73D, + 0xA73E => 0xA73F, + 0xA740 => 0xA741, + 0xA742 => 0xA743, + 0xA744 => 0xA745, + 0xA746 => 0xA747, + 0xA748 => 0xA749, + 0xA74A => 0xA74B, + 0xA74C => 0xA74D, + 0xA74E => 0xA74F, + 0xA750 => 0xA751, + 0xA752 => 0xA753, + 0xA754 => 0xA755, + 0xA756 => 0xA757, + 0xA758 => 0xA759, + 0xA75A => 0xA75B, + 0xA75C => 0xA75D, + 0xA75E => 0xA75F, + 0xA760 => 0xA761, + 0xA762 => 0xA763, + 0xA764 => 0xA765, + 0xA766 => 0xA767, + 0xA768 => 0xA769, + 0xA76A => 0xA76B, + 0xA76C => 0xA76D, + 0xA76E => 0xA76F, + 0xA779 => 0xA77A, + 0xA77B => 0xA77C, + 0xA77D => 0x1D79, + 0xA77E => 0xA77F, + 0xA780 => 0xA781, + 0xA782 => 0xA783, + 0xA784 => 0xA785, + 0xA786 => 0xA787, + 0xA78B => 0xA78C, + 0xA78D => 0x265, + 0xA790 => 0xA791, + 0xA792 => 0xA793, + 0xA796 => 0xA797, + 0xA798 => 0xA799, + 0xA79A => 0xA79B, + 0xA79C => 0xA79D, + 0xA79E => 0xA79F, + 0xA7A0 => 0xA7A1, + 0xA7A2 => 0xA7A3, + 0xA7A4 => 0xA7A5, + 0xA7A6 => 0xA7A7, + 0xA7A8 => 0xA7A9, + 0xA7AA => 0x266, + 0xA7AB => 0x25C, + 0xA7AC => 0x261, + 0xA7AD => 0x26C, + 0xA7AE => 0x26A, + 0xA7B0 => 0x29E, + 0xA7B1 => 0x287, + 0xA7B2 => 0x29D, + 0xA7B3 => 0xAB53, + 0xA7B4 => 0xA7B5, + 0xA7B6 => 0xA7B7, + 0xA7B8 => 0xA7B9, + 0xA7BA => 0xA7BB, + 0xA7BC => 0xA7BD, + 0xA7BE => 0xA7BF, + 0xA7C2 => 0xA7C3, + 0xA7C4 => 0xA794, + 0xA7C5 => 0x282, + 0xA7C6 => 0x1D8E, + 0xA7C7 => 0xA7C8, + 0xA7C9 => 0xA7CA, + 0xA7F5 => 0xA7F6, + 0xFF21 => 0xFF41, + 0xFF22 => 0xFF42, + 0xFF23 => 0xFF43, + 0xFF24 => 0xFF44, + 0xFF25 => 0xFF45, + 0xFF26 => 0xFF46, + 0xFF27 => 0xFF47, + 0xFF28 => 0xFF48, + 0xFF29 => 0xFF49, + 0xFF2A => 0xFF4A, + 0xFF2B => 0xFF4B, + 0xFF2C => 0xFF4C, + 0xFF2D => 0xFF4D, + 0xFF2E => 0xFF4E, + 0xFF2F => 0xFF4F, + 0xFF30 => 0xFF50, + 0xFF31 => 0xFF51, + 0xFF32 => 0xFF52, + 0xFF33 => 0xFF53, + 0xFF34 => 0xFF54, + 0xFF35 => 0xFF55, + 0xFF36 => 0xFF56, + 0xFF37 => 0xFF57, + 0xFF38 => 0xFF58, + 0xFF39 => 0xFF59, + 0xFF3A => 0xFF5A, + 0x10400 => 0x10428, + 0x10401 => 0x10429, + 0x10402 => 0x1042A, + 0x10403 => 0x1042B, + 0x10404 => 0x1042C, + 0x10405 => 0x1042D, + 0x10406 => 0x1042E, + 0x10407 => 0x1042F, + 0x10408 => 0x10430, + 0x10409 => 0x10431, + 0x1040A => 0x10432, + 0x1040B => 0x10433, + 0x1040C => 0x10434, + 0x1040D => 0x10435, + 0x1040E => 0x10436, + 0x1040F => 0x10437, + 0x10410 => 0x10438, + 0x10411 => 0x10439, + 0x10412 => 0x1043A, + 0x10413 => 0x1043B, + 0x10414 => 0x1043C, + 0x10415 => 0x1043D, + 0x10416 => 0x1043E, + 0x10417 => 0x1043F, + 0x10418 => 0x10440, + 0x10419 => 0x10441, + 0x1041A => 0x10442, + 0x1041B => 0x10443, + 0x1041C => 0x10444, + 0x1041D => 0x10445, + 0x1041E => 0x10446, + 0x1041F => 0x10447, + 0x10420 => 0x10448, + 0x10421 => 0x10449, + 0x10422 => 0x1044A, + 0x10423 => 0x1044B, + 0x10424 => 0x1044C, + 0x10425 => 0x1044D, + 0x10426 => 0x1044E, + 0x10427 => 0x1044F, + 0x104B0 => 0x104D8, + 0x104B1 => 0x104D9, + 0x104B2 => 0x104DA, + 0x104B3 => 0x104DB, + 0x104B4 => 0x104DC, + 0x104B5 => 0x104DD, + 0x104B6 => 0x104DE, + 0x104B7 => 0x104DF, + 0x104B8 => 0x104E0, + 0x104B9 => 0x104E1, + 0x104BA => 0x104E2, + 0x104BB => 0x104E3, + 0x104BC => 0x104E4, + 0x104BD => 0x104E5, + 0x104BE => 0x104E6, + 0x104BF => 0x104E7, + 0x104C0 => 0x104E8, + 0x104C1 => 0x104E9, + 0x104C2 => 0x104EA, + 0x104C3 => 0x104EB, + 0x104C4 => 0x104EC, + 0x104C5 => 0x104ED, + 0x104C6 => 0x104EE, + 0x104C7 => 0x104EF, + 0x104C8 => 0x104F0, + 0x104C9 => 0x104F1, + 0x104CA => 0x104F2, + 0x104CB => 0x104F3, + 0x104CC => 0x104F4, + 0x104CD => 0x104F5, + 0x104CE => 0x104F6, + 0x104CF => 0x104F7, + 0x104D0 => 0x104F8, + 0x104D1 => 0x104F9, + 0x104D2 => 0x104FA, + 0x104D3 => 0x104FB, + 0x10C80 => 0x10CC0, + 0x10C81 => 0x10CC1, + 0x10C82 => 0x10CC2, + 0x10C83 => 0x10CC3, + 0x10C84 => 0x10CC4, + 0x10C85 => 0x10CC5, + 0x10C86 => 0x10CC6, + 0x10C87 => 0x10CC7, + 0x10C88 => 0x10CC8, + 0x10C89 => 0x10CC9, + 0x10C8A => 0x10CCA, + 0x10C8B => 0x10CCB, + 0x10C8C => 0x10CCC, + 0x10C8D => 0x10CCD, + 0x10C8E => 0x10CCE, + 0x10C8F => 0x10CCF, + 0x10C90 => 0x10CD0, + 0x10C91 => 0x10CD1, + 0x10C92 => 0x10CD2, + 0x10C93 => 0x10CD3, + 0x10C94 => 0x10CD4, + 0x10C95 => 0x10CD5, + 0x10C96 => 0x10CD6, + 0x10C97 => 0x10CD7, + 0x10C98 => 0x10CD8, + 0x10C99 => 0x10CD9, + 0x10C9A => 0x10CDA, + 0x10C9B => 0x10CDB, + 0x10C9C => 0x10CDC, + 0x10C9D => 0x10CDD, + 0x10C9E => 0x10CDE, + 0x10C9F => 0x10CDF, + 0x10CA0 => 0x10CE0, + 0x10CA1 => 0x10CE1, + 0x10CA2 => 0x10CE2, + 0x10CA3 => 0x10CE3, + 0x10CA4 => 0x10CE4, + 0x10CA5 => 0x10CE5, + 0x10CA6 => 0x10CE6, + 0x10CA7 => 0x10CE7, + 0x10CA8 => 0x10CE8, + 0x10CA9 => 0x10CE9, + 0x10CAA => 0x10CEA, + 0x10CAB => 0x10CEB, + 0x10CAC => 0x10CEC, + 0x10CAD => 0x10CED, + 0x10CAE => 0x10CEE, + 0x10CAF => 0x10CEF, + 0x10CB0 => 0x10CF0, + 0x10CB1 => 0x10CF1, + 0x10CB2 => 0x10CF2, + 0x118A0 => 0x118C0, + 0x118A1 => 0x118C1, + 0x118A2 => 0x118C2, + 0x118A3 => 0x118C3, + 0x118A4 => 0x118C4, + 0x118A5 => 0x118C5, + 0x118A6 => 0x118C6, + 0x118A7 => 0x118C7, + 0x118A8 => 0x118C8, + 0x118A9 => 0x118C9, + 0x118AA => 0x118CA, + 0x118AB => 0x118CB, + 0x118AC => 0x118CC, + 0x118AD => 0x118CD, + 0x118AE => 0x118CE, + 0x118AF => 0x118CF, + 0x118B0 => 0x118D0, + 0x118B1 => 0x118D1, + 0x118B2 => 0x118D2, + 0x118B3 => 0x118D3, + 0x118B4 => 0x118D4, + 0x118B5 => 0x118D5, + 0x118B6 => 0x118D6, + 0x118B7 => 0x118D7, + 0x118B8 => 0x118D8, + 0x118B9 => 0x118D9, + 0x118BA => 0x118DA, + 0x118BB => 0x118DB, + 0x118BC => 0x118DC, + 0x118BD => 0x118DD, + 0x118BE => 0x118DE, + 0x118BF => 0x118DF, + 0x16E40 => 0x16E60, + 0x16E41 => 0x16E61, + 0x16E42 => 0x16E62, + 0x16E43 => 0x16E63, + 0x16E44 => 0x16E64, + 0x16E45 => 0x16E65, + 0x16E46 => 0x16E66, + 0x16E47 => 0x16E67, + 0x16E48 => 0x16E68, + 0x16E49 => 0x16E69, + 0x16E4A => 0x16E6A, + 0x16E4B => 0x16E6B, + 0x16E4C => 0x16E6C, + 0x16E4D => 0x16E6D, + 0x16E4E => 0x16E6E, + 0x16E4F => 0x16E6F, + 0x16E50 => 0x16E70, + 0x16E51 => 0x16E71, + 0x16E52 => 0x16E72, + 0x16E53 => 0x16E73, + 0x16E54 => 0x16E74, + 0x16E55 => 0x16E75, + 0x16E56 => 0x16E76, + 0x16E57 => 0x16E77, + 0x16E58 => 0x16E78, + 0x16E59 => 0x16E79, + 0x16E5A => 0x16E7A, + 0x16E5B => 0x16E7B, + 0x16E5C => 0x16E7C, + 0x16E5D => 0x16E7D, + 0x16E5E => 0x16E7E, + 0x16E5F => 0x16E7F, + 0x1E900 => 0x1E922, + 0x1E901 => 0x1E923, + 0x1E902 => 0x1E924, + 0x1E903 => 0x1E925, + 0x1E904 => 0x1E926, + 0x1E905 => 0x1E927, + 0x1E906 => 0x1E928, + 0x1E907 => 0x1E929, + 0x1E908 => 0x1E92A, + 0x1E909 => 0x1E92B, + 0x1E90A => 0x1E92C, + 0x1E90B => 0x1E92D, + 0x1E90C => 0x1E92E, + 0x1E90D => 0x1E92F, + 0x1E90E => 0x1E930, + 0x1E90F => 0x1E931, + 0x1E910 => 0x1E932, + 0x1E911 => 0x1E933, + 0x1E912 => 0x1E934, + 0x1E913 => 0x1E935, + 0x1E914 => 0x1E936, + 0x1E915 => 0x1E937, + 0x1E916 => 0x1E938, + 0x1E917 => 0x1E939, + 0x1E918 => 0x1E93A, + 0x1E919 => 0x1E93B, + 0x1E91A => 0x1E93C, + 0x1E91B => 0x1E93D, + 0x1E91C => 0x1E93E, + 0x1E91D => 0x1E93F, + 0x1E91E => 0x1E940, + 0x1E91F => 0x1E941, + 0x1E920 => 0x1E942, + 0x1E921 => 0x1E943, +); diff --git a/src/opis/string/res/upper.php b/src/opis/string/res/upper.php index 376ce6e6..549389a0 100644 --- a/src/opis/string/res/upper.php +++ b/src/opis/string/res/upper.php @@ -1,1413 +1,1413 @@ 0x41, -0x62 => 0x42, -0x63 => 0x43, -0x64 => 0x44, -0x65 => 0x45, -0x66 => 0x46, -0x67 => 0x47, -0x68 => 0x48, -0x69 => 0x49, -0x6A => 0x4A, -0x6B => 0x4B, -0x6C => 0x4C, -0x6D => 0x4D, -0x6E => 0x4E, -0x6F => 0x4F, -0x70 => 0x50, -0x71 => 0x51, -0x72 => 0x52, -0x73 => 0x53, -0x74 => 0x54, -0x75 => 0x55, -0x76 => 0x56, -0x77 => 0x57, -0x78 => 0x58, -0x79 => 0x59, -0x7A => 0x5A, -0xB5 => 0x39C, -0xE0 => 0xC0, -0xE1 => 0xC1, -0xE2 => 0xC2, -0xE3 => 0xC3, -0xE4 => 0xC4, -0xE5 => 0xC5, -0xE6 => 0xC6, -0xE7 => 0xC7, -0xE8 => 0xC8, -0xE9 => 0xC9, -0xEA => 0xCA, -0xEB => 0xCB, -0xEC => 0xCC, -0xED => 0xCD, -0xEE => 0xCE, -0xEF => 0xCF, -0xF0 => 0xD0, -0xF1 => 0xD1, -0xF2 => 0xD2, -0xF3 => 0xD3, -0xF4 => 0xD4, -0xF5 => 0xD5, -0xF6 => 0xD6, -0xF8 => 0xD8, -0xF9 => 0xD9, -0xFA => 0xDA, -0xFB => 0xDB, -0xFC => 0xDC, -0xFD => 0xDD, -0xFE => 0xDE, -0xFF => 0x178, -0x101 => 0x100, -0x103 => 0x102, -0x105 => 0x104, -0x107 => 0x106, -0x109 => 0x108, -0x10B => 0x10A, -0x10D => 0x10C, -0x10F => 0x10E, -0x111 => 0x110, -0x113 => 0x112, -0x115 => 0x114, -0x117 => 0x116, -0x119 => 0x118, -0x11B => 0x11A, -0x11D => 0x11C, -0x11F => 0x11E, -0x121 => 0x120, -0x123 => 0x122, -0x125 => 0x124, -0x127 => 0x126, -0x129 => 0x128, -0x12B => 0x12A, -0x12D => 0x12C, -0x12F => 0x12E, -0x131 => 0x49, -0x133 => 0x132, -0x135 => 0x134, -0x137 => 0x136, -0x13A => 0x139, -0x13C => 0x13B, -0x13E => 0x13D, -0x140 => 0x13F, -0x142 => 0x141, -0x144 => 0x143, -0x146 => 0x145, -0x148 => 0x147, -0x14B => 0x14A, -0x14D => 0x14C, -0x14F => 0x14E, -0x151 => 0x150, -0x153 => 0x152, -0x155 => 0x154, -0x157 => 0x156, -0x159 => 0x158, -0x15B => 0x15A, -0x15D => 0x15C, -0x15F => 0x15E, -0x161 => 0x160, -0x163 => 0x162, -0x165 => 0x164, -0x167 => 0x166, -0x169 => 0x168, -0x16B => 0x16A, -0x16D => 0x16C, -0x16F => 0x16E, -0x171 => 0x170, -0x173 => 0x172, -0x175 => 0x174, -0x177 => 0x176, -0x17A => 0x179, -0x17C => 0x17B, -0x17E => 0x17D, -0x17F => 0x53, -0x180 => 0x243, -0x183 => 0x182, -0x185 => 0x184, -0x188 => 0x187, -0x18C => 0x18B, -0x192 => 0x191, -0x195 => 0x1F6, -0x199 => 0x198, -0x19A => 0x23D, -0x19E => 0x220, -0x1A1 => 0x1A0, -0x1A3 => 0x1A2, -0x1A5 => 0x1A4, -0x1A8 => 0x1A7, -0x1AD => 0x1AC, -0x1B0 => 0x1AF, -0x1B4 => 0x1B3, -0x1B6 => 0x1B5, -0x1B9 => 0x1B8, -0x1BD => 0x1BC, -0x1BF => 0x1F7, -0x1C5 => 0x1C4, -0x1C6 => 0x1C4, -0x1C8 => 0x1C7, -0x1C9 => 0x1C7, -0x1CB => 0x1CA, -0x1CC => 0x1CA, -0x1CE => 0x1CD, -0x1D0 => 0x1CF, -0x1D2 => 0x1D1, -0x1D4 => 0x1D3, -0x1D6 => 0x1D5, -0x1D8 => 0x1D7, -0x1DA => 0x1D9, -0x1DC => 0x1DB, -0x1DD => 0x18E, -0x1DF => 0x1DE, -0x1E1 => 0x1E0, -0x1E3 => 0x1E2, -0x1E5 => 0x1E4, -0x1E7 => 0x1E6, -0x1E9 => 0x1E8, -0x1EB => 0x1EA, -0x1ED => 0x1EC, -0x1EF => 0x1EE, -0x1F2 => 0x1F1, -0x1F3 => 0x1F1, -0x1F5 => 0x1F4, -0x1F9 => 0x1F8, -0x1FB => 0x1FA, -0x1FD => 0x1FC, -0x1FF => 0x1FE, -0x201 => 0x200, -0x203 => 0x202, -0x205 => 0x204, -0x207 => 0x206, -0x209 => 0x208, -0x20B => 0x20A, -0x20D => 0x20C, -0x20F => 0x20E, -0x211 => 0x210, -0x213 => 0x212, -0x215 => 0x214, -0x217 => 0x216, -0x219 => 0x218, -0x21B => 0x21A, -0x21D => 0x21C, -0x21F => 0x21E, -0x223 => 0x222, -0x225 => 0x224, -0x227 => 0x226, -0x229 => 0x228, -0x22B => 0x22A, -0x22D => 0x22C, -0x22F => 0x22E, -0x231 => 0x230, -0x233 => 0x232, -0x23C => 0x23B, -0x23F => 0x2C7E, -0x240 => 0x2C7F, -0x242 => 0x241, -0x247 => 0x246, -0x249 => 0x248, -0x24B => 0x24A, -0x24D => 0x24C, -0x24F => 0x24E, -0x250 => 0x2C6F, -0x251 => 0x2C6D, -0x252 => 0x2C70, -0x253 => 0x181, -0x254 => 0x186, -0x256 => 0x189, -0x257 => 0x18A, -0x259 => 0x18F, -0x25B => 0x190, -0x25C => 0xA7AB, -0x260 => 0x193, -0x261 => 0xA7AC, -0x263 => 0x194, -0x265 => 0xA78D, -0x266 => 0xA7AA, -0x268 => 0x197, -0x269 => 0x196, -0x26A => 0xA7AE, -0x26B => 0x2C62, -0x26C => 0xA7AD, -0x26F => 0x19C, -0x271 => 0x2C6E, -0x272 => 0x19D, -0x275 => 0x19F, -0x27D => 0x2C64, -0x280 => 0x1A6, -0x282 => 0xA7C5, -0x283 => 0x1A9, -0x287 => 0xA7B1, -0x288 => 0x1AE, -0x289 => 0x244, -0x28A => 0x1B1, -0x28B => 0x1B2, -0x28C => 0x245, -0x292 => 0x1B7, -0x29D => 0xA7B2, -0x29E => 0xA7B0, -0x345 => 0x399, -0x371 => 0x370, -0x373 => 0x372, -0x377 => 0x376, -0x37B => 0x3FD, -0x37C => 0x3FE, -0x37D => 0x3FF, -0x3AC => 0x386, -0x3AD => 0x388, -0x3AE => 0x389, -0x3AF => 0x38A, -0x3B1 => 0x391, -0x3B2 => 0x392, -0x3B3 => 0x393, -0x3B4 => 0x394, -0x3B5 => 0x395, -0x3B6 => 0x396, -0x3B7 => 0x397, -0x3B8 => 0x398, -0x3B9 => 0x399, -0x3BA => 0x39A, -0x3BB => 0x39B, -0x3BC => 0x39C, -0x3BD => 0x39D, -0x3BE => 0x39E, -0x3BF => 0x39F, -0x3C0 => 0x3A0, -0x3C1 => 0x3A1, -0x3C2 => 0x3A3, -0x3C3 => 0x3A3, -0x3C4 => 0x3A4, -0x3C5 => 0x3A5, -0x3C6 => 0x3A6, -0x3C7 => 0x3A7, -0x3C8 => 0x3A8, -0x3C9 => 0x3A9, -0x3CA => 0x3AA, -0x3CB => 0x3AB, -0x3CC => 0x38C, -0x3CD => 0x38E, -0x3CE => 0x38F, -0x3D0 => 0x392, -0x3D1 => 0x398, -0x3D5 => 0x3A6, -0x3D6 => 0x3A0, -0x3D7 => 0x3CF, -0x3D9 => 0x3D8, -0x3DB => 0x3DA, -0x3DD => 0x3DC, -0x3DF => 0x3DE, -0x3E1 => 0x3E0, -0x3E3 => 0x3E2, -0x3E5 => 0x3E4, -0x3E7 => 0x3E6, -0x3E9 => 0x3E8, -0x3EB => 0x3EA, -0x3ED => 0x3EC, -0x3EF => 0x3EE, -0x3F0 => 0x39A, -0x3F1 => 0x3A1, -0x3F2 => 0x3F9, -0x3F3 => 0x37F, -0x3F5 => 0x395, -0x3F8 => 0x3F7, -0x3FB => 0x3FA, -0x430 => 0x410, -0x431 => 0x411, -0x432 => 0x412, -0x433 => 0x413, -0x434 => 0x414, -0x435 => 0x415, -0x436 => 0x416, -0x437 => 0x417, -0x438 => 0x418, -0x439 => 0x419, -0x43A => 0x41A, -0x43B => 0x41B, -0x43C => 0x41C, -0x43D => 0x41D, -0x43E => 0x41E, -0x43F => 0x41F, -0x440 => 0x420, -0x441 => 0x421, -0x442 => 0x422, -0x443 => 0x423, -0x444 => 0x424, -0x445 => 0x425, -0x446 => 0x426, -0x447 => 0x427, -0x448 => 0x428, -0x449 => 0x429, -0x44A => 0x42A, -0x44B => 0x42B, -0x44C => 0x42C, -0x44D => 0x42D, -0x44E => 0x42E, -0x44F => 0x42F, -0x450 => 0x400, -0x451 => 0x401, -0x452 => 0x402, -0x453 => 0x403, -0x454 => 0x404, -0x455 => 0x405, -0x456 => 0x406, -0x457 => 0x407, -0x458 => 0x408, -0x459 => 0x409, -0x45A => 0x40A, -0x45B => 0x40B, -0x45C => 0x40C, -0x45D => 0x40D, -0x45E => 0x40E, -0x45F => 0x40F, -0x461 => 0x460, -0x463 => 0x462, -0x465 => 0x464, -0x467 => 0x466, -0x469 => 0x468, -0x46B => 0x46A, -0x46D => 0x46C, -0x46F => 0x46E, -0x471 => 0x470, -0x473 => 0x472, -0x475 => 0x474, -0x477 => 0x476, -0x479 => 0x478, -0x47B => 0x47A, -0x47D => 0x47C, -0x47F => 0x47E, -0x481 => 0x480, -0x48B => 0x48A, -0x48D => 0x48C, -0x48F => 0x48E, -0x491 => 0x490, -0x493 => 0x492, -0x495 => 0x494, -0x497 => 0x496, -0x499 => 0x498, -0x49B => 0x49A, -0x49D => 0x49C, -0x49F => 0x49E, -0x4A1 => 0x4A0, -0x4A3 => 0x4A2, -0x4A5 => 0x4A4, -0x4A7 => 0x4A6, -0x4A9 => 0x4A8, -0x4AB => 0x4AA, -0x4AD => 0x4AC, -0x4AF => 0x4AE, -0x4B1 => 0x4B0, -0x4B3 => 0x4B2, -0x4B5 => 0x4B4, -0x4B7 => 0x4B6, -0x4B9 => 0x4B8, -0x4BB => 0x4BA, -0x4BD => 0x4BC, -0x4BF => 0x4BE, -0x4C2 => 0x4C1, -0x4C4 => 0x4C3, -0x4C6 => 0x4C5, -0x4C8 => 0x4C7, -0x4CA => 0x4C9, -0x4CC => 0x4CB, -0x4CE => 0x4CD, -0x4CF => 0x4C0, -0x4D1 => 0x4D0, -0x4D3 => 0x4D2, -0x4D5 => 0x4D4, -0x4D7 => 0x4D6, -0x4D9 => 0x4D8, -0x4DB => 0x4DA, -0x4DD => 0x4DC, -0x4DF => 0x4DE, -0x4E1 => 0x4E0, -0x4E3 => 0x4E2, -0x4E5 => 0x4E4, -0x4E7 => 0x4E6, -0x4E9 => 0x4E8, -0x4EB => 0x4EA, -0x4ED => 0x4EC, -0x4EF => 0x4EE, -0x4F1 => 0x4F0, -0x4F3 => 0x4F2, -0x4F5 => 0x4F4, -0x4F7 => 0x4F6, -0x4F9 => 0x4F8, -0x4FB => 0x4FA, -0x4FD => 0x4FC, -0x4FF => 0x4FE, -0x501 => 0x500, -0x503 => 0x502, -0x505 => 0x504, -0x507 => 0x506, -0x509 => 0x508, -0x50B => 0x50A, -0x50D => 0x50C, -0x50F => 0x50E, -0x511 => 0x510, -0x513 => 0x512, -0x515 => 0x514, -0x517 => 0x516, -0x519 => 0x518, -0x51B => 0x51A, -0x51D => 0x51C, -0x51F => 0x51E, -0x521 => 0x520, -0x523 => 0x522, -0x525 => 0x524, -0x527 => 0x526, -0x529 => 0x528, -0x52B => 0x52A, -0x52D => 0x52C, -0x52F => 0x52E, -0x561 => 0x531, -0x562 => 0x532, -0x563 => 0x533, -0x564 => 0x534, -0x565 => 0x535, -0x566 => 0x536, -0x567 => 0x537, -0x568 => 0x538, -0x569 => 0x539, -0x56A => 0x53A, -0x56B => 0x53B, -0x56C => 0x53C, -0x56D => 0x53D, -0x56E => 0x53E, -0x56F => 0x53F, -0x570 => 0x540, -0x571 => 0x541, -0x572 => 0x542, -0x573 => 0x543, -0x574 => 0x544, -0x575 => 0x545, -0x576 => 0x546, -0x577 => 0x547, -0x578 => 0x548, -0x579 => 0x549, -0x57A => 0x54A, -0x57B => 0x54B, -0x57C => 0x54C, -0x57D => 0x54D, -0x57E => 0x54E, -0x57F => 0x54F, -0x580 => 0x550, -0x581 => 0x551, -0x582 => 0x552, -0x583 => 0x553, -0x584 => 0x554, -0x585 => 0x555, -0x586 => 0x556, -0x10D0 => 0x1C90, -0x10D1 => 0x1C91, -0x10D2 => 0x1C92, -0x10D3 => 0x1C93, -0x10D4 => 0x1C94, -0x10D5 => 0x1C95, -0x10D6 => 0x1C96, -0x10D7 => 0x1C97, -0x10D8 => 0x1C98, -0x10D9 => 0x1C99, -0x10DA => 0x1C9A, -0x10DB => 0x1C9B, -0x10DC => 0x1C9C, -0x10DD => 0x1C9D, -0x10DE => 0x1C9E, -0x10DF => 0x1C9F, -0x10E0 => 0x1CA0, -0x10E1 => 0x1CA1, -0x10E2 => 0x1CA2, -0x10E3 => 0x1CA3, -0x10E4 => 0x1CA4, -0x10E5 => 0x1CA5, -0x10E6 => 0x1CA6, -0x10E7 => 0x1CA7, -0x10E8 => 0x1CA8, -0x10E9 => 0x1CA9, -0x10EA => 0x1CAA, -0x10EB => 0x1CAB, -0x10EC => 0x1CAC, -0x10ED => 0x1CAD, -0x10EE => 0x1CAE, -0x10EF => 0x1CAF, -0x10F0 => 0x1CB0, -0x10F1 => 0x1CB1, -0x10F2 => 0x1CB2, -0x10F3 => 0x1CB3, -0x10F4 => 0x1CB4, -0x10F5 => 0x1CB5, -0x10F6 => 0x1CB6, -0x10F7 => 0x1CB7, -0x10F8 => 0x1CB8, -0x10F9 => 0x1CB9, -0x10FA => 0x1CBA, -0x10FD => 0x1CBD, -0x10FE => 0x1CBE, -0x10FF => 0x1CBF, -0x13F8 => 0x13F0, -0x13F9 => 0x13F1, -0x13FA => 0x13F2, -0x13FB => 0x13F3, -0x13FC => 0x13F4, -0x13FD => 0x13F5, -0x1C80 => 0x412, -0x1C81 => 0x414, -0x1C82 => 0x41E, -0x1C83 => 0x421, -0x1C84 => 0x422, -0x1C85 => 0x422, -0x1C86 => 0x42A, -0x1C87 => 0x462, -0x1C88 => 0xA64A, -0x1D79 => 0xA77D, -0x1D7D => 0x2C63, -0x1D8E => 0xA7C6, -0x1E01 => 0x1E00, -0x1E03 => 0x1E02, -0x1E05 => 0x1E04, -0x1E07 => 0x1E06, -0x1E09 => 0x1E08, -0x1E0B => 0x1E0A, -0x1E0D => 0x1E0C, -0x1E0F => 0x1E0E, -0x1E11 => 0x1E10, -0x1E13 => 0x1E12, -0x1E15 => 0x1E14, -0x1E17 => 0x1E16, -0x1E19 => 0x1E18, -0x1E1B => 0x1E1A, -0x1E1D => 0x1E1C, -0x1E1F => 0x1E1E, -0x1E21 => 0x1E20, -0x1E23 => 0x1E22, -0x1E25 => 0x1E24, -0x1E27 => 0x1E26, -0x1E29 => 0x1E28, -0x1E2B => 0x1E2A, -0x1E2D => 0x1E2C, -0x1E2F => 0x1E2E, -0x1E31 => 0x1E30, -0x1E33 => 0x1E32, -0x1E35 => 0x1E34, -0x1E37 => 0x1E36, -0x1E39 => 0x1E38, -0x1E3B => 0x1E3A, -0x1E3D => 0x1E3C, -0x1E3F => 0x1E3E, -0x1E41 => 0x1E40, -0x1E43 => 0x1E42, -0x1E45 => 0x1E44, -0x1E47 => 0x1E46, -0x1E49 => 0x1E48, -0x1E4B => 0x1E4A, -0x1E4D => 0x1E4C, -0x1E4F => 0x1E4E, -0x1E51 => 0x1E50, -0x1E53 => 0x1E52, -0x1E55 => 0x1E54, -0x1E57 => 0x1E56, -0x1E59 => 0x1E58, -0x1E5B => 0x1E5A, -0x1E5D => 0x1E5C, -0x1E5F => 0x1E5E, -0x1E61 => 0x1E60, -0x1E63 => 0x1E62, -0x1E65 => 0x1E64, -0x1E67 => 0x1E66, -0x1E69 => 0x1E68, -0x1E6B => 0x1E6A, -0x1E6D => 0x1E6C, -0x1E6F => 0x1E6E, -0x1E71 => 0x1E70, -0x1E73 => 0x1E72, -0x1E75 => 0x1E74, -0x1E77 => 0x1E76, -0x1E79 => 0x1E78, -0x1E7B => 0x1E7A, -0x1E7D => 0x1E7C, -0x1E7F => 0x1E7E, -0x1E81 => 0x1E80, -0x1E83 => 0x1E82, -0x1E85 => 0x1E84, -0x1E87 => 0x1E86, -0x1E89 => 0x1E88, -0x1E8B => 0x1E8A, -0x1E8D => 0x1E8C, -0x1E8F => 0x1E8E, -0x1E91 => 0x1E90, -0x1E93 => 0x1E92, -0x1E95 => 0x1E94, -0x1E9B => 0x1E60, -0x1EA1 => 0x1EA0, -0x1EA3 => 0x1EA2, -0x1EA5 => 0x1EA4, -0x1EA7 => 0x1EA6, -0x1EA9 => 0x1EA8, -0x1EAB => 0x1EAA, -0x1EAD => 0x1EAC, -0x1EAF => 0x1EAE, -0x1EB1 => 0x1EB0, -0x1EB3 => 0x1EB2, -0x1EB5 => 0x1EB4, -0x1EB7 => 0x1EB6, -0x1EB9 => 0x1EB8, -0x1EBB => 0x1EBA, -0x1EBD => 0x1EBC, -0x1EBF => 0x1EBE, -0x1EC1 => 0x1EC0, -0x1EC3 => 0x1EC2, -0x1EC5 => 0x1EC4, -0x1EC7 => 0x1EC6, -0x1EC9 => 0x1EC8, -0x1ECB => 0x1ECA, -0x1ECD => 0x1ECC, -0x1ECF => 0x1ECE, -0x1ED1 => 0x1ED0, -0x1ED3 => 0x1ED2, -0x1ED5 => 0x1ED4, -0x1ED7 => 0x1ED6, -0x1ED9 => 0x1ED8, -0x1EDB => 0x1EDA, -0x1EDD => 0x1EDC, -0x1EDF => 0x1EDE, -0x1EE1 => 0x1EE0, -0x1EE3 => 0x1EE2, -0x1EE5 => 0x1EE4, -0x1EE7 => 0x1EE6, -0x1EE9 => 0x1EE8, -0x1EEB => 0x1EEA, -0x1EED => 0x1EEC, -0x1EEF => 0x1EEE, -0x1EF1 => 0x1EF0, -0x1EF3 => 0x1EF2, -0x1EF5 => 0x1EF4, -0x1EF7 => 0x1EF6, -0x1EF9 => 0x1EF8, -0x1EFB => 0x1EFA, -0x1EFD => 0x1EFC, -0x1EFF => 0x1EFE, -0x1F00 => 0x1F08, -0x1F01 => 0x1F09, -0x1F02 => 0x1F0A, -0x1F03 => 0x1F0B, -0x1F04 => 0x1F0C, -0x1F05 => 0x1F0D, -0x1F06 => 0x1F0E, -0x1F07 => 0x1F0F, -0x1F10 => 0x1F18, -0x1F11 => 0x1F19, -0x1F12 => 0x1F1A, -0x1F13 => 0x1F1B, -0x1F14 => 0x1F1C, -0x1F15 => 0x1F1D, -0x1F20 => 0x1F28, -0x1F21 => 0x1F29, -0x1F22 => 0x1F2A, -0x1F23 => 0x1F2B, -0x1F24 => 0x1F2C, -0x1F25 => 0x1F2D, -0x1F26 => 0x1F2E, -0x1F27 => 0x1F2F, -0x1F30 => 0x1F38, -0x1F31 => 0x1F39, -0x1F32 => 0x1F3A, -0x1F33 => 0x1F3B, -0x1F34 => 0x1F3C, -0x1F35 => 0x1F3D, -0x1F36 => 0x1F3E, -0x1F37 => 0x1F3F, -0x1F40 => 0x1F48, -0x1F41 => 0x1F49, -0x1F42 => 0x1F4A, -0x1F43 => 0x1F4B, -0x1F44 => 0x1F4C, -0x1F45 => 0x1F4D, -0x1F51 => 0x1F59, -0x1F53 => 0x1F5B, -0x1F55 => 0x1F5D, -0x1F57 => 0x1F5F, -0x1F60 => 0x1F68, -0x1F61 => 0x1F69, -0x1F62 => 0x1F6A, -0x1F63 => 0x1F6B, -0x1F64 => 0x1F6C, -0x1F65 => 0x1F6D, -0x1F66 => 0x1F6E, -0x1F67 => 0x1F6F, -0x1F70 => 0x1FBA, -0x1F71 => 0x1FBB, -0x1F72 => 0x1FC8, -0x1F73 => 0x1FC9, -0x1F74 => 0x1FCA, -0x1F75 => 0x1FCB, -0x1F76 => 0x1FDA, -0x1F77 => 0x1FDB, -0x1F78 => 0x1FF8, -0x1F79 => 0x1FF9, -0x1F7A => 0x1FEA, -0x1F7B => 0x1FEB, -0x1F7C => 0x1FFA, -0x1F7D => 0x1FFB, -0x1F80 => 0x1F88, -0x1F81 => 0x1F89, -0x1F82 => 0x1F8A, -0x1F83 => 0x1F8B, -0x1F84 => 0x1F8C, -0x1F85 => 0x1F8D, -0x1F86 => 0x1F8E, -0x1F87 => 0x1F8F, -0x1F90 => 0x1F98, -0x1F91 => 0x1F99, -0x1F92 => 0x1F9A, -0x1F93 => 0x1F9B, -0x1F94 => 0x1F9C, -0x1F95 => 0x1F9D, -0x1F96 => 0x1F9E, -0x1F97 => 0x1F9F, -0x1FA0 => 0x1FA8, -0x1FA1 => 0x1FA9, -0x1FA2 => 0x1FAA, -0x1FA3 => 0x1FAB, -0x1FA4 => 0x1FAC, -0x1FA5 => 0x1FAD, -0x1FA6 => 0x1FAE, -0x1FA7 => 0x1FAF, -0x1FB0 => 0x1FB8, -0x1FB1 => 0x1FB9, -0x1FB3 => 0x1FBC, -0x1FBE => 0x399, -0x1FC3 => 0x1FCC, -0x1FD0 => 0x1FD8, -0x1FD1 => 0x1FD9, -0x1FE0 => 0x1FE8, -0x1FE1 => 0x1FE9, -0x1FE5 => 0x1FEC, -0x1FF3 => 0x1FFC, -0x214E => 0x2132, -0x2170 => 0x2160, -0x2171 => 0x2161, -0x2172 => 0x2162, -0x2173 => 0x2163, -0x2174 => 0x2164, -0x2175 => 0x2165, -0x2176 => 0x2166, -0x2177 => 0x2167, -0x2178 => 0x2168, -0x2179 => 0x2169, -0x217A => 0x216A, -0x217B => 0x216B, -0x217C => 0x216C, -0x217D => 0x216D, -0x217E => 0x216E, -0x217F => 0x216F, -0x2184 => 0x2183, -0x24D0 => 0x24B6, -0x24D1 => 0x24B7, -0x24D2 => 0x24B8, -0x24D3 => 0x24B9, -0x24D4 => 0x24BA, -0x24D5 => 0x24BB, -0x24D6 => 0x24BC, -0x24D7 => 0x24BD, -0x24D8 => 0x24BE, -0x24D9 => 0x24BF, -0x24DA => 0x24C0, -0x24DB => 0x24C1, -0x24DC => 0x24C2, -0x24DD => 0x24C3, -0x24DE => 0x24C4, -0x24DF => 0x24C5, -0x24E0 => 0x24C6, -0x24E1 => 0x24C7, -0x24E2 => 0x24C8, -0x24E3 => 0x24C9, -0x24E4 => 0x24CA, -0x24E5 => 0x24CB, -0x24E6 => 0x24CC, -0x24E7 => 0x24CD, -0x24E8 => 0x24CE, -0x24E9 => 0x24CF, -0x2C30 => 0x2C00, -0x2C31 => 0x2C01, -0x2C32 => 0x2C02, -0x2C33 => 0x2C03, -0x2C34 => 0x2C04, -0x2C35 => 0x2C05, -0x2C36 => 0x2C06, -0x2C37 => 0x2C07, -0x2C38 => 0x2C08, -0x2C39 => 0x2C09, -0x2C3A => 0x2C0A, -0x2C3B => 0x2C0B, -0x2C3C => 0x2C0C, -0x2C3D => 0x2C0D, -0x2C3E => 0x2C0E, -0x2C3F => 0x2C0F, -0x2C40 => 0x2C10, -0x2C41 => 0x2C11, -0x2C42 => 0x2C12, -0x2C43 => 0x2C13, -0x2C44 => 0x2C14, -0x2C45 => 0x2C15, -0x2C46 => 0x2C16, -0x2C47 => 0x2C17, -0x2C48 => 0x2C18, -0x2C49 => 0x2C19, -0x2C4A => 0x2C1A, -0x2C4B => 0x2C1B, -0x2C4C => 0x2C1C, -0x2C4D => 0x2C1D, -0x2C4E => 0x2C1E, -0x2C4F => 0x2C1F, -0x2C50 => 0x2C20, -0x2C51 => 0x2C21, -0x2C52 => 0x2C22, -0x2C53 => 0x2C23, -0x2C54 => 0x2C24, -0x2C55 => 0x2C25, -0x2C56 => 0x2C26, -0x2C57 => 0x2C27, -0x2C58 => 0x2C28, -0x2C59 => 0x2C29, -0x2C5A => 0x2C2A, -0x2C5B => 0x2C2B, -0x2C5C => 0x2C2C, -0x2C5D => 0x2C2D, -0x2C5E => 0x2C2E, -0x2C61 => 0x2C60, -0x2C65 => 0x23A, -0x2C66 => 0x23E, -0x2C68 => 0x2C67, -0x2C6A => 0x2C69, -0x2C6C => 0x2C6B, -0x2C73 => 0x2C72, -0x2C76 => 0x2C75, -0x2C81 => 0x2C80, -0x2C83 => 0x2C82, -0x2C85 => 0x2C84, -0x2C87 => 0x2C86, -0x2C89 => 0x2C88, -0x2C8B => 0x2C8A, -0x2C8D => 0x2C8C, -0x2C8F => 0x2C8E, -0x2C91 => 0x2C90, -0x2C93 => 0x2C92, -0x2C95 => 0x2C94, -0x2C97 => 0x2C96, -0x2C99 => 0x2C98, -0x2C9B => 0x2C9A, -0x2C9D => 0x2C9C, -0x2C9F => 0x2C9E, -0x2CA1 => 0x2CA0, -0x2CA3 => 0x2CA2, -0x2CA5 => 0x2CA4, -0x2CA7 => 0x2CA6, -0x2CA9 => 0x2CA8, -0x2CAB => 0x2CAA, -0x2CAD => 0x2CAC, -0x2CAF => 0x2CAE, -0x2CB1 => 0x2CB0, -0x2CB3 => 0x2CB2, -0x2CB5 => 0x2CB4, -0x2CB7 => 0x2CB6, -0x2CB9 => 0x2CB8, -0x2CBB => 0x2CBA, -0x2CBD => 0x2CBC, -0x2CBF => 0x2CBE, -0x2CC1 => 0x2CC0, -0x2CC3 => 0x2CC2, -0x2CC5 => 0x2CC4, -0x2CC7 => 0x2CC6, -0x2CC9 => 0x2CC8, -0x2CCB => 0x2CCA, -0x2CCD => 0x2CCC, -0x2CCF => 0x2CCE, -0x2CD1 => 0x2CD0, -0x2CD3 => 0x2CD2, -0x2CD5 => 0x2CD4, -0x2CD7 => 0x2CD6, -0x2CD9 => 0x2CD8, -0x2CDB => 0x2CDA, -0x2CDD => 0x2CDC, -0x2CDF => 0x2CDE, -0x2CE1 => 0x2CE0, -0x2CE3 => 0x2CE2, -0x2CEC => 0x2CEB, -0x2CEE => 0x2CED, -0x2CF3 => 0x2CF2, -0x2D00 => 0x10A0, -0x2D01 => 0x10A1, -0x2D02 => 0x10A2, -0x2D03 => 0x10A3, -0x2D04 => 0x10A4, -0x2D05 => 0x10A5, -0x2D06 => 0x10A6, -0x2D07 => 0x10A7, -0x2D08 => 0x10A8, -0x2D09 => 0x10A9, -0x2D0A => 0x10AA, -0x2D0B => 0x10AB, -0x2D0C => 0x10AC, -0x2D0D => 0x10AD, -0x2D0E => 0x10AE, -0x2D0F => 0x10AF, -0x2D10 => 0x10B0, -0x2D11 => 0x10B1, -0x2D12 => 0x10B2, -0x2D13 => 0x10B3, -0x2D14 => 0x10B4, -0x2D15 => 0x10B5, -0x2D16 => 0x10B6, -0x2D17 => 0x10B7, -0x2D18 => 0x10B8, -0x2D19 => 0x10B9, -0x2D1A => 0x10BA, -0x2D1B => 0x10BB, -0x2D1C => 0x10BC, -0x2D1D => 0x10BD, -0x2D1E => 0x10BE, -0x2D1F => 0x10BF, -0x2D20 => 0x10C0, -0x2D21 => 0x10C1, -0x2D22 => 0x10C2, -0x2D23 => 0x10C3, -0x2D24 => 0x10C4, -0x2D25 => 0x10C5, -0x2D27 => 0x10C7, -0x2D2D => 0x10CD, -0xA641 => 0xA640, -0xA643 => 0xA642, -0xA645 => 0xA644, -0xA647 => 0xA646, -0xA649 => 0xA648, -0xA64B => 0xA64A, -0xA64D => 0xA64C, -0xA64F => 0xA64E, -0xA651 => 0xA650, -0xA653 => 0xA652, -0xA655 => 0xA654, -0xA657 => 0xA656, -0xA659 => 0xA658, -0xA65B => 0xA65A, -0xA65D => 0xA65C, -0xA65F => 0xA65E, -0xA661 => 0xA660, -0xA663 => 0xA662, -0xA665 => 0xA664, -0xA667 => 0xA666, -0xA669 => 0xA668, -0xA66B => 0xA66A, -0xA66D => 0xA66C, -0xA681 => 0xA680, -0xA683 => 0xA682, -0xA685 => 0xA684, -0xA687 => 0xA686, -0xA689 => 0xA688, -0xA68B => 0xA68A, -0xA68D => 0xA68C, -0xA68F => 0xA68E, -0xA691 => 0xA690, -0xA693 => 0xA692, -0xA695 => 0xA694, -0xA697 => 0xA696, -0xA699 => 0xA698, -0xA69B => 0xA69A, -0xA723 => 0xA722, -0xA725 => 0xA724, -0xA727 => 0xA726, -0xA729 => 0xA728, -0xA72B => 0xA72A, -0xA72D => 0xA72C, -0xA72F => 0xA72E, -0xA733 => 0xA732, -0xA735 => 0xA734, -0xA737 => 0xA736, -0xA739 => 0xA738, -0xA73B => 0xA73A, -0xA73D => 0xA73C, -0xA73F => 0xA73E, -0xA741 => 0xA740, -0xA743 => 0xA742, -0xA745 => 0xA744, -0xA747 => 0xA746, -0xA749 => 0xA748, -0xA74B => 0xA74A, -0xA74D => 0xA74C, -0xA74F => 0xA74E, -0xA751 => 0xA750, -0xA753 => 0xA752, -0xA755 => 0xA754, -0xA757 => 0xA756, -0xA759 => 0xA758, -0xA75B => 0xA75A, -0xA75D => 0xA75C, -0xA75F => 0xA75E, -0xA761 => 0xA760, -0xA763 => 0xA762, -0xA765 => 0xA764, -0xA767 => 0xA766, -0xA769 => 0xA768, -0xA76B => 0xA76A, -0xA76D => 0xA76C, -0xA76F => 0xA76E, -0xA77A => 0xA779, -0xA77C => 0xA77B, -0xA77F => 0xA77E, -0xA781 => 0xA780, -0xA783 => 0xA782, -0xA785 => 0xA784, -0xA787 => 0xA786, -0xA78C => 0xA78B, -0xA791 => 0xA790, -0xA793 => 0xA792, -0xA794 => 0xA7C4, -0xA797 => 0xA796, -0xA799 => 0xA798, -0xA79B => 0xA79A, -0xA79D => 0xA79C, -0xA79F => 0xA79E, -0xA7A1 => 0xA7A0, -0xA7A3 => 0xA7A2, -0xA7A5 => 0xA7A4, -0xA7A7 => 0xA7A6, -0xA7A9 => 0xA7A8, -0xA7B5 => 0xA7B4, -0xA7B7 => 0xA7B6, -0xA7B9 => 0xA7B8, -0xA7BB => 0xA7BA, -0xA7BD => 0xA7BC, -0xA7BF => 0xA7BE, -0xA7C3 => 0xA7C2, -0xA7C8 => 0xA7C7, -0xA7CA => 0xA7C9, -0xA7F6 => 0xA7F5, -0xAB53 => 0xA7B3, -0xAB70 => 0x13A0, -0xAB71 => 0x13A1, -0xAB72 => 0x13A2, -0xAB73 => 0x13A3, -0xAB74 => 0x13A4, -0xAB75 => 0x13A5, -0xAB76 => 0x13A6, -0xAB77 => 0x13A7, -0xAB78 => 0x13A8, -0xAB79 => 0x13A9, -0xAB7A => 0x13AA, -0xAB7B => 0x13AB, -0xAB7C => 0x13AC, -0xAB7D => 0x13AD, -0xAB7E => 0x13AE, -0xAB7F => 0x13AF, -0xAB80 => 0x13B0, -0xAB81 => 0x13B1, -0xAB82 => 0x13B2, -0xAB83 => 0x13B3, -0xAB84 => 0x13B4, -0xAB85 => 0x13B5, -0xAB86 => 0x13B6, -0xAB87 => 0x13B7, -0xAB88 => 0x13B8, -0xAB89 => 0x13B9, -0xAB8A => 0x13BA, -0xAB8B => 0x13BB, -0xAB8C => 0x13BC, -0xAB8D => 0x13BD, -0xAB8E => 0x13BE, -0xAB8F => 0x13BF, -0xAB90 => 0x13C0, -0xAB91 => 0x13C1, -0xAB92 => 0x13C2, -0xAB93 => 0x13C3, -0xAB94 => 0x13C4, -0xAB95 => 0x13C5, -0xAB96 => 0x13C6, -0xAB97 => 0x13C7, -0xAB98 => 0x13C8, -0xAB99 => 0x13C9, -0xAB9A => 0x13CA, -0xAB9B => 0x13CB, -0xAB9C => 0x13CC, -0xAB9D => 0x13CD, -0xAB9E => 0x13CE, -0xAB9F => 0x13CF, -0xABA0 => 0x13D0, -0xABA1 => 0x13D1, -0xABA2 => 0x13D2, -0xABA3 => 0x13D3, -0xABA4 => 0x13D4, -0xABA5 => 0x13D5, -0xABA6 => 0x13D6, -0xABA7 => 0x13D7, -0xABA8 => 0x13D8, -0xABA9 => 0x13D9, -0xABAA => 0x13DA, -0xABAB => 0x13DB, -0xABAC => 0x13DC, -0xABAD => 0x13DD, -0xABAE => 0x13DE, -0xABAF => 0x13DF, -0xABB0 => 0x13E0, -0xABB1 => 0x13E1, -0xABB2 => 0x13E2, -0xABB3 => 0x13E3, -0xABB4 => 0x13E4, -0xABB5 => 0x13E5, -0xABB6 => 0x13E6, -0xABB7 => 0x13E7, -0xABB8 => 0x13E8, -0xABB9 => 0x13E9, -0xABBA => 0x13EA, -0xABBB => 0x13EB, -0xABBC => 0x13EC, -0xABBD => 0x13ED, -0xABBE => 0x13EE, -0xABBF => 0x13EF, -0xFF41 => 0xFF21, -0xFF42 => 0xFF22, -0xFF43 => 0xFF23, -0xFF44 => 0xFF24, -0xFF45 => 0xFF25, -0xFF46 => 0xFF26, -0xFF47 => 0xFF27, -0xFF48 => 0xFF28, -0xFF49 => 0xFF29, -0xFF4A => 0xFF2A, -0xFF4B => 0xFF2B, -0xFF4C => 0xFF2C, -0xFF4D => 0xFF2D, -0xFF4E => 0xFF2E, -0xFF4F => 0xFF2F, -0xFF50 => 0xFF30, -0xFF51 => 0xFF31, -0xFF52 => 0xFF32, -0xFF53 => 0xFF33, -0xFF54 => 0xFF34, -0xFF55 => 0xFF35, -0xFF56 => 0xFF36, -0xFF57 => 0xFF37, -0xFF58 => 0xFF38, -0xFF59 => 0xFF39, -0xFF5A => 0xFF3A, -0x10428 => 0x10400, -0x10429 => 0x10401, -0x1042A => 0x10402, -0x1042B => 0x10403, -0x1042C => 0x10404, -0x1042D => 0x10405, -0x1042E => 0x10406, -0x1042F => 0x10407, -0x10430 => 0x10408, -0x10431 => 0x10409, -0x10432 => 0x1040A, -0x10433 => 0x1040B, -0x10434 => 0x1040C, -0x10435 => 0x1040D, -0x10436 => 0x1040E, -0x10437 => 0x1040F, -0x10438 => 0x10410, -0x10439 => 0x10411, -0x1043A => 0x10412, -0x1043B => 0x10413, -0x1043C => 0x10414, -0x1043D => 0x10415, -0x1043E => 0x10416, -0x1043F => 0x10417, -0x10440 => 0x10418, -0x10441 => 0x10419, -0x10442 => 0x1041A, -0x10443 => 0x1041B, -0x10444 => 0x1041C, -0x10445 => 0x1041D, -0x10446 => 0x1041E, -0x10447 => 0x1041F, -0x10448 => 0x10420, -0x10449 => 0x10421, -0x1044A => 0x10422, -0x1044B => 0x10423, -0x1044C => 0x10424, -0x1044D => 0x10425, -0x1044E => 0x10426, -0x1044F => 0x10427, -0x104D8 => 0x104B0, -0x104D9 => 0x104B1, -0x104DA => 0x104B2, -0x104DB => 0x104B3, -0x104DC => 0x104B4, -0x104DD => 0x104B5, -0x104DE => 0x104B6, -0x104DF => 0x104B7, -0x104E0 => 0x104B8, -0x104E1 => 0x104B9, -0x104E2 => 0x104BA, -0x104E3 => 0x104BB, -0x104E4 => 0x104BC, -0x104E5 => 0x104BD, -0x104E6 => 0x104BE, -0x104E7 => 0x104BF, -0x104E8 => 0x104C0, -0x104E9 => 0x104C1, -0x104EA => 0x104C2, -0x104EB => 0x104C3, -0x104EC => 0x104C4, -0x104ED => 0x104C5, -0x104EE => 0x104C6, -0x104EF => 0x104C7, -0x104F0 => 0x104C8, -0x104F1 => 0x104C9, -0x104F2 => 0x104CA, -0x104F3 => 0x104CB, -0x104F4 => 0x104CC, -0x104F5 => 0x104CD, -0x104F6 => 0x104CE, -0x104F7 => 0x104CF, -0x104F8 => 0x104D0, -0x104F9 => 0x104D1, -0x104FA => 0x104D2, -0x104FB => 0x104D3, -0x10CC0 => 0x10C80, -0x10CC1 => 0x10C81, -0x10CC2 => 0x10C82, -0x10CC3 => 0x10C83, -0x10CC4 => 0x10C84, -0x10CC5 => 0x10C85, -0x10CC6 => 0x10C86, -0x10CC7 => 0x10C87, -0x10CC8 => 0x10C88, -0x10CC9 => 0x10C89, -0x10CCA => 0x10C8A, -0x10CCB => 0x10C8B, -0x10CCC => 0x10C8C, -0x10CCD => 0x10C8D, -0x10CCE => 0x10C8E, -0x10CCF => 0x10C8F, -0x10CD0 => 0x10C90, -0x10CD1 => 0x10C91, -0x10CD2 => 0x10C92, -0x10CD3 => 0x10C93, -0x10CD4 => 0x10C94, -0x10CD5 => 0x10C95, -0x10CD6 => 0x10C96, -0x10CD7 => 0x10C97, -0x10CD8 => 0x10C98, -0x10CD9 => 0x10C99, -0x10CDA => 0x10C9A, -0x10CDB => 0x10C9B, -0x10CDC => 0x10C9C, -0x10CDD => 0x10C9D, -0x10CDE => 0x10C9E, -0x10CDF => 0x10C9F, -0x10CE0 => 0x10CA0, -0x10CE1 => 0x10CA1, -0x10CE2 => 0x10CA2, -0x10CE3 => 0x10CA3, -0x10CE4 => 0x10CA4, -0x10CE5 => 0x10CA5, -0x10CE6 => 0x10CA6, -0x10CE7 => 0x10CA7, -0x10CE8 => 0x10CA8, -0x10CE9 => 0x10CA9, -0x10CEA => 0x10CAA, -0x10CEB => 0x10CAB, -0x10CEC => 0x10CAC, -0x10CED => 0x10CAD, -0x10CEE => 0x10CAE, -0x10CEF => 0x10CAF, -0x10CF0 => 0x10CB0, -0x10CF1 => 0x10CB1, -0x10CF2 => 0x10CB2, -0x118C0 => 0x118A0, -0x118C1 => 0x118A1, -0x118C2 => 0x118A2, -0x118C3 => 0x118A3, -0x118C4 => 0x118A4, -0x118C5 => 0x118A5, -0x118C6 => 0x118A6, -0x118C7 => 0x118A7, -0x118C8 => 0x118A8, -0x118C9 => 0x118A9, -0x118CA => 0x118AA, -0x118CB => 0x118AB, -0x118CC => 0x118AC, -0x118CD => 0x118AD, -0x118CE => 0x118AE, -0x118CF => 0x118AF, -0x118D0 => 0x118B0, -0x118D1 => 0x118B1, -0x118D2 => 0x118B2, -0x118D3 => 0x118B3, -0x118D4 => 0x118B4, -0x118D5 => 0x118B5, -0x118D6 => 0x118B6, -0x118D7 => 0x118B7, -0x118D8 => 0x118B8, -0x118D9 => 0x118B9, -0x118DA => 0x118BA, -0x118DB => 0x118BB, -0x118DC => 0x118BC, -0x118DD => 0x118BD, -0x118DE => 0x118BE, -0x118DF => 0x118BF, -0x16E60 => 0x16E40, -0x16E61 => 0x16E41, -0x16E62 => 0x16E42, -0x16E63 => 0x16E43, -0x16E64 => 0x16E44, -0x16E65 => 0x16E45, -0x16E66 => 0x16E46, -0x16E67 => 0x16E47, -0x16E68 => 0x16E48, -0x16E69 => 0x16E49, -0x16E6A => 0x16E4A, -0x16E6B => 0x16E4B, -0x16E6C => 0x16E4C, -0x16E6D => 0x16E4D, -0x16E6E => 0x16E4E, -0x16E6F => 0x16E4F, -0x16E70 => 0x16E50, -0x16E71 => 0x16E51, -0x16E72 => 0x16E52, -0x16E73 => 0x16E53, -0x16E74 => 0x16E54, -0x16E75 => 0x16E55, -0x16E76 => 0x16E56, -0x16E77 => 0x16E57, -0x16E78 => 0x16E58, -0x16E79 => 0x16E59, -0x16E7A => 0x16E5A, -0x16E7B => 0x16E5B, -0x16E7C => 0x16E5C, -0x16E7D => 0x16E5D, -0x16E7E => 0x16E5E, -0x16E7F => 0x16E5F, -0x1E922 => 0x1E900, -0x1E923 => 0x1E901, -0x1E924 => 0x1E902, -0x1E925 => 0x1E903, -0x1E926 => 0x1E904, -0x1E927 => 0x1E905, -0x1E928 => 0x1E906, -0x1E929 => 0x1E907, -0x1E92A => 0x1E908, -0x1E92B => 0x1E909, -0x1E92C => 0x1E90A, -0x1E92D => 0x1E90B, -0x1E92E => 0x1E90C, -0x1E92F => 0x1E90D, -0x1E930 => 0x1E90E, -0x1E931 => 0x1E90F, -0x1E932 => 0x1E910, -0x1E933 => 0x1E911, -0x1E934 => 0x1E912, -0x1E935 => 0x1E913, -0x1E936 => 0x1E914, -0x1E937 => 0x1E915, -0x1E938 => 0x1E916, -0x1E939 => 0x1E917, -0x1E93A => 0x1E918, -0x1E93B => 0x1E919, -0x1E93C => 0x1E91A, -0x1E93D => 0x1E91B, -0x1E93E => 0x1E91C, -0x1E93F => 0x1E91D, -0x1E940 => 0x1E91E, -0x1E941 => 0x1E91F, -0x1E942 => 0x1E920, -0x1E943 => 0x1E921, -]; +return array( + 0x61 => 0x41, + 0x62 => 0x42, + 0x63 => 0x43, + 0x64 => 0x44, + 0x65 => 0x45, + 0x66 => 0x46, + 0x67 => 0x47, + 0x68 => 0x48, + 0x69 => 0x49, + 0x6A => 0x4A, + 0x6B => 0x4B, + 0x6C => 0x4C, + 0x6D => 0x4D, + 0x6E => 0x4E, + 0x6F => 0x4F, + 0x70 => 0x50, + 0x71 => 0x51, + 0x72 => 0x52, + 0x73 => 0x53, + 0x74 => 0x54, + 0x75 => 0x55, + 0x76 => 0x56, + 0x77 => 0x57, + 0x78 => 0x58, + 0x79 => 0x59, + 0x7A => 0x5A, + 0xB5 => 0x39C, + 0xE0 => 0xC0, + 0xE1 => 0xC1, + 0xE2 => 0xC2, + 0xE3 => 0xC3, + 0xE4 => 0xC4, + 0xE5 => 0xC5, + 0xE6 => 0xC6, + 0xE7 => 0xC7, + 0xE8 => 0xC8, + 0xE9 => 0xC9, + 0xEA => 0xCA, + 0xEB => 0xCB, + 0xEC => 0xCC, + 0xED => 0xCD, + 0xEE => 0xCE, + 0xEF => 0xCF, + 0xF0 => 0xD0, + 0xF1 => 0xD1, + 0xF2 => 0xD2, + 0xF3 => 0xD3, + 0xF4 => 0xD4, + 0xF5 => 0xD5, + 0xF6 => 0xD6, + 0xF8 => 0xD8, + 0xF9 => 0xD9, + 0xFA => 0xDA, + 0xFB => 0xDB, + 0xFC => 0xDC, + 0xFD => 0xDD, + 0xFE => 0xDE, + 0xFF => 0x178, + 0x101 => 0x100, + 0x103 => 0x102, + 0x105 => 0x104, + 0x107 => 0x106, + 0x109 => 0x108, + 0x10B => 0x10A, + 0x10D => 0x10C, + 0x10F => 0x10E, + 0x111 => 0x110, + 0x113 => 0x112, + 0x115 => 0x114, + 0x117 => 0x116, + 0x119 => 0x118, + 0x11B => 0x11A, + 0x11D => 0x11C, + 0x11F => 0x11E, + 0x121 => 0x120, + 0x123 => 0x122, + 0x125 => 0x124, + 0x127 => 0x126, + 0x129 => 0x128, + 0x12B => 0x12A, + 0x12D => 0x12C, + 0x12F => 0x12E, + 0x131 => 0x49, + 0x133 => 0x132, + 0x135 => 0x134, + 0x137 => 0x136, + 0x13A => 0x139, + 0x13C => 0x13B, + 0x13E => 0x13D, + 0x140 => 0x13F, + 0x142 => 0x141, + 0x144 => 0x143, + 0x146 => 0x145, + 0x148 => 0x147, + 0x14B => 0x14A, + 0x14D => 0x14C, + 0x14F => 0x14E, + 0x151 => 0x150, + 0x153 => 0x152, + 0x155 => 0x154, + 0x157 => 0x156, + 0x159 => 0x158, + 0x15B => 0x15A, + 0x15D => 0x15C, + 0x15F => 0x15E, + 0x161 => 0x160, + 0x163 => 0x162, + 0x165 => 0x164, + 0x167 => 0x166, + 0x169 => 0x168, + 0x16B => 0x16A, + 0x16D => 0x16C, + 0x16F => 0x16E, + 0x171 => 0x170, + 0x173 => 0x172, + 0x175 => 0x174, + 0x177 => 0x176, + 0x17A => 0x179, + 0x17C => 0x17B, + 0x17E => 0x17D, + 0x17F => 0x53, + 0x180 => 0x243, + 0x183 => 0x182, + 0x185 => 0x184, + 0x188 => 0x187, + 0x18C => 0x18B, + 0x192 => 0x191, + 0x195 => 0x1F6, + 0x199 => 0x198, + 0x19A => 0x23D, + 0x19E => 0x220, + 0x1A1 => 0x1A0, + 0x1A3 => 0x1A2, + 0x1A5 => 0x1A4, + 0x1A8 => 0x1A7, + 0x1AD => 0x1AC, + 0x1B0 => 0x1AF, + 0x1B4 => 0x1B3, + 0x1B6 => 0x1B5, + 0x1B9 => 0x1B8, + 0x1BD => 0x1BC, + 0x1BF => 0x1F7, + 0x1C5 => 0x1C4, + 0x1C6 => 0x1C4, + 0x1C8 => 0x1C7, + 0x1C9 => 0x1C7, + 0x1CB => 0x1CA, + 0x1CC => 0x1CA, + 0x1CE => 0x1CD, + 0x1D0 => 0x1CF, + 0x1D2 => 0x1D1, + 0x1D4 => 0x1D3, + 0x1D6 => 0x1D5, + 0x1D8 => 0x1D7, + 0x1DA => 0x1D9, + 0x1DC => 0x1DB, + 0x1DD => 0x18E, + 0x1DF => 0x1DE, + 0x1E1 => 0x1E0, + 0x1E3 => 0x1E2, + 0x1E5 => 0x1E4, + 0x1E7 => 0x1E6, + 0x1E9 => 0x1E8, + 0x1EB => 0x1EA, + 0x1ED => 0x1EC, + 0x1EF => 0x1EE, + 0x1F2 => 0x1F1, + 0x1F3 => 0x1F1, + 0x1F5 => 0x1F4, + 0x1F9 => 0x1F8, + 0x1FB => 0x1FA, + 0x1FD => 0x1FC, + 0x1FF => 0x1FE, + 0x201 => 0x200, + 0x203 => 0x202, + 0x205 => 0x204, + 0x207 => 0x206, + 0x209 => 0x208, + 0x20B => 0x20A, + 0x20D => 0x20C, + 0x20F => 0x20E, + 0x211 => 0x210, + 0x213 => 0x212, + 0x215 => 0x214, + 0x217 => 0x216, + 0x219 => 0x218, + 0x21B => 0x21A, + 0x21D => 0x21C, + 0x21F => 0x21E, + 0x223 => 0x222, + 0x225 => 0x224, + 0x227 => 0x226, + 0x229 => 0x228, + 0x22B => 0x22A, + 0x22D => 0x22C, + 0x22F => 0x22E, + 0x231 => 0x230, + 0x233 => 0x232, + 0x23C => 0x23B, + 0x23F => 0x2C7E, + 0x240 => 0x2C7F, + 0x242 => 0x241, + 0x247 => 0x246, + 0x249 => 0x248, + 0x24B => 0x24A, + 0x24D => 0x24C, + 0x24F => 0x24E, + 0x250 => 0x2C6F, + 0x251 => 0x2C6D, + 0x252 => 0x2C70, + 0x253 => 0x181, + 0x254 => 0x186, + 0x256 => 0x189, + 0x257 => 0x18A, + 0x259 => 0x18F, + 0x25B => 0x190, + 0x25C => 0xA7AB, + 0x260 => 0x193, + 0x261 => 0xA7AC, + 0x263 => 0x194, + 0x265 => 0xA78D, + 0x266 => 0xA7AA, + 0x268 => 0x197, + 0x269 => 0x196, + 0x26A => 0xA7AE, + 0x26B => 0x2C62, + 0x26C => 0xA7AD, + 0x26F => 0x19C, + 0x271 => 0x2C6E, + 0x272 => 0x19D, + 0x275 => 0x19F, + 0x27D => 0x2C64, + 0x280 => 0x1A6, + 0x282 => 0xA7C5, + 0x283 => 0x1A9, + 0x287 => 0xA7B1, + 0x288 => 0x1AE, + 0x289 => 0x244, + 0x28A => 0x1B1, + 0x28B => 0x1B2, + 0x28C => 0x245, + 0x292 => 0x1B7, + 0x29D => 0xA7B2, + 0x29E => 0xA7B0, + 0x345 => 0x399, + 0x371 => 0x370, + 0x373 => 0x372, + 0x377 => 0x376, + 0x37B => 0x3FD, + 0x37C => 0x3FE, + 0x37D => 0x3FF, + 0x3AC => 0x386, + 0x3AD => 0x388, + 0x3AE => 0x389, + 0x3AF => 0x38A, + 0x3B1 => 0x391, + 0x3B2 => 0x392, + 0x3B3 => 0x393, + 0x3B4 => 0x394, + 0x3B5 => 0x395, + 0x3B6 => 0x396, + 0x3B7 => 0x397, + 0x3B8 => 0x398, + 0x3B9 => 0x399, + 0x3BA => 0x39A, + 0x3BB => 0x39B, + 0x3BC => 0x39C, + 0x3BD => 0x39D, + 0x3BE => 0x39E, + 0x3BF => 0x39F, + 0x3C0 => 0x3A0, + 0x3C1 => 0x3A1, + 0x3C2 => 0x3A3, + 0x3C3 => 0x3A3, + 0x3C4 => 0x3A4, + 0x3C5 => 0x3A5, + 0x3C6 => 0x3A6, + 0x3C7 => 0x3A7, + 0x3C8 => 0x3A8, + 0x3C9 => 0x3A9, + 0x3CA => 0x3AA, + 0x3CB => 0x3AB, + 0x3CC => 0x38C, + 0x3CD => 0x38E, + 0x3CE => 0x38F, + 0x3D0 => 0x392, + 0x3D1 => 0x398, + 0x3D5 => 0x3A6, + 0x3D6 => 0x3A0, + 0x3D7 => 0x3CF, + 0x3D9 => 0x3D8, + 0x3DB => 0x3DA, + 0x3DD => 0x3DC, + 0x3DF => 0x3DE, + 0x3E1 => 0x3E0, + 0x3E3 => 0x3E2, + 0x3E5 => 0x3E4, + 0x3E7 => 0x3E6, + 0x3E9 => 0x3E8, + 0x3EB => 0x3EA, + 0x3ED => 0x3EC, + 0x3EF => 0x3EE, + 0x3F0 => 0x39A, + 0x3F1 => 0x3A1, + 0x3F2 => 0x3F9, + 0x3F3 => 0x37F, + 0x3F5 => 0x395, + 0x3F8 => 0x3F7, + 0x3FB => 0x3FA, + 0x430 => 0x410, + 0x431 => 0x411, + 0x432 => 0x412, + 0x433 => 0x413, + 0x434 => 0x414, + 0x435 => 0x415, + 0x436 => 0x416, + 0x437 => 0x417, + 0x438 => 0x418, + 0x439 => 0x419, + 0x43A => 0x41A, + 0x43B => 0x41B, + 0x43C => 0x41C, + 0x43D => 0x41D, + 0x43E => 0x41E, + 0x43F => 0x41F, + 0x440 => 0x420, + 0x441 => 0x421, + 0x442 => 0x422, + 0x443 => 0x423, + 0x444 => 0x424, + 0x445 => 0x425, + 0x446 => 0x426, + 0x447 => 0x427, + 0x448 => 0x428, + 0x449 => 0x429, + 0x44A => 0x42A, + 0x44B => 0x42B, + 0x44C => 0x42C, + 0x44D => 0x42D, + 0x44E => 0x42E, + 0x44F => 0x42F, + 0x450 => 0x400, + 0x451 => 0x401, + 0x452 => 0x402, + 0x453 => 0x403, + 0x454 => 0x404, + 0x455 => 0x405, + 0x456 => 0x406, + 0x457 => 0x407, + 0x458 => 0x408, + 0x459 => 0x409, + 0x45A => 0x40A, + 0x45B => 0x40B, + 0x45C => 0x40C, + 0x45D => 0x40D, + 0x45E => 0x40E, + 0x45F => 0x40F, + 0x461 => 0x460, + 0x463 => 0x462, + 0x465 => 0x464, + 0x467 => 0x466, + 0x469 => 0x468, + 0x46B => 0x46A, + 0x46D => 0x46C, + 0x46F => 0x46E, + 0x471 => 0x470, + 0x473 => 0x472, + 0x475 => 0x474, + 0x477 => 0x476, + 0x479 => 0x478, + 0x47B => 0x47A, + 0x47D => 0x47C, + 0x47F => 0x47E, + 0x481 => 0x480, + 0x48B => 0x48A, + 0x48D => 0x48C, + 0x48F => 0x48E, + 0x491 => 0x490, + 0x493 => 0x492, + 0x495 => 0x494, + 0x497 => 0x496, + 0x499 => 0x498, + 0x49B => 0x49A, + 0x49D => 0x49C, + 0x49F => 0x49E, + 0x4A1 => 0x4A0, + 0x4A3 => 0x4A2, + 0x4A5 => 0x4A4, + 0x4A7 => 0x4A6, + 0x4A9 => 0x4A8, + 0x4AB => 0x4AA, + 0x4AD => 0x4AC, + 0x4AF => 0x4AE, + 0x4B1 => 0x4B0, + 0x4B3 => 0x4B2, + 0x4B5 => 0x4B4, + 0x4B7 => 0x4B6, + 0x4B9 => 0x4B8, + 0x4BB => 0x4BA, + 0x4BD => 0x4BC, + 0x4BF => 0x4BE, + 0x4C2 => 0x4C1, + 0x4C4 => 0x4C3, + 0x4C6 => 0x4C5, + 0x4C8 => 0x4C7, + 0x4CA => 0x4C9, + 0x4CC => 0x4CB, + 0x4CE => 0x4CD, + 0x4CF => 0x4C0, + 0x4D1 => 0x4D0, + 0x4D3 => 0x4D2, + 0x4D5 => 0x4D4, + 0x4D7 => 0x4D6, + 0x4D9 => 0x4D8, + 0x4DB => 0x4DA, + 0x4DD => 0x4DC, + 0x4DF => 0x4DE, + 0x4E1 => 0x4E0, + 0x4E3 => 0x4E2, + 0x4E5 => 0x4E4, + 0x4E7 => 0x4E6, + 0x4E9 => 0x4E8, + 0x4EB => 0x4EA, + 0x4ED => 0x4EC, + 0x4EF => 0x4EE, + 0x4F1 => 0x4F0, + 0x4F3 => 0x4F2, + 0x4F5 => 0x4F4, + 0x4F7 => 0x4F6, + 0x4F9 => 0x4F8, + 0x4FB => 0x4FA, + 0x4FD => 0x4FC, + 0x4FF => 0x4FE, + 0x501 => 0x500, + 0x503 => 0x502, + 0x505 => 0x504, + 0x507 => 0x506, + 0x509 => 0x508, + 0x50B => 0x50A, + 0x50D => 0x50C, + 0x50F => 0x50E, + 0x511 => 0x510, + 0x513 => 0x512, + 0x515 => 0x514, + 0x517 => 0x516, + 0x519 => 0x518, + 0x51B => 0x51A, + 0x51D => 0x51C, + 0x51F => 0x51E, + 0x521 => 0x520, + 0x523 => 0x522, + 0x525 => 0x524, + 0x527 => 0x526, + 0x529 => 0x528, + 0x52B => 0x52A, + 0x52D => 0x52C, + 0x52F => 0x52E, + 0x561 => 0x531, + 0x562 => 0x532, + 0x563 => 0x533, + 0x564 => 0x534, + 0x565 => 0x535, + 0x566 => 0x536, + 0x567 => 0x537, + 0x568 => 0x538, + 0x569 => 0x539, + 0x56A => 0x53A, + 0x56B => 0x53B, + 0x56C => 0x53C, + 0x56D => 0x53D, + 0x56E => 0x53E, + 0x56F => 0x53F, + 0x570 => 0x540, + 0x571 => 0x541, + 0x572 => 0x542, + 0x573 => 0x543, + 0x574 => 0x544, + 0x575 => 0x545, + 0x576 => 0x546, + 0x577 => 0x547, + 0x578 => 0x548, + 0x579 => 0x549, + 0x57A => 0x54A, + 0x57B => 0x54B, + 0x57C => 0x54C, + 0x57D => 0x54D, + 0x57E => 0x54E, + 0x57F => 0x54F, + 0x580 => 0x550, + 0x581 => 0x551, + 0x582 => 0x552, + 0x583 => 0x553, + 0x584 => 0x554, + 0x585 => 0x555, + 0x586 => 0x556, + 0x10D0 => 0x1C90, + 0x10D1 => 0x1C91, + 0x10D2 => 0x1C92, + 0x10D3 => 0x1C93, + 0x10D4 => 0x1C94, + 0x10D5 => 0x1C95, + 0x10D6 => 0x1C96, + 0x10D7 => 0x1C97, + 0x10D8 => 0x1C98, + 0x10D9 => 0x1C99, + 0x10DA => 0x1C9A, + 0x10DB => 0x1C9B, + 0x10DC => 0x1C9C, + 0x10DD => 0x1C9D, + 0x10DE => 0x1C9E, + 0x10DF => 0x1C9F, + 0x10E0 => 0x1CA0, + 0x10E1 => 0x1CA1, + 0x10E2 => 0x1CA2, + 0x10E3 => 0x1CA3, + 0x10E4 => 0x1CA4, + 0x10E5 => 0x1CA5, + 0x10E6 => 0x1CA6, + 0x10E7 => 0x1CA7, + 0x10E8 => 0x1CA8, + 0x10E9 => 0x1CA9, + 0x10EA => 0x1CAA, + 0x10EB => 0x1CAB, + 0x10EC => 0x1CAC, + 0x10ED => 0x1CAD, + 0x10EE => 0x1CAE, + 0x10EF => 0x1CAF, + 0x10F0 => 0x1CB0, + 0x10F1 => 0x1CB1, + 0x10F2 => 0x1CB2, + 0x10F3 => 0x1CB3, + 0x10F4 => 0x1CB4, + 0x10F5 => 0x1CB5, + 0x10F6 => 0x1CB6, + 0x10F7 => 0x1CB7, + 0x10F8 => 0x1CB8, + 0x10F9 => 0x1CB9, + 0x10FA => 0x1CBA, + 0x10FD => 0x1CBD, + 0x10FE => 0x1CBE, + 0x10FF => 0x1CBF, + 0x13F8 => 0x13F0, + 0x13F9 => 0x13F1, + 0x13FA => 0x13F2, + 0x13FB => 0x13F3, + 0x13FC => 0x13F4, + 0x13FD => 0x13F5, + 0x1C80 => 0x412, + 0x1C81 => 0x414, + 0x1C82 => 0x41E, + 0x1C83 => 0x421, + 0x1C84 => 0x422, + 0x1C85 => 0x422, + 0x1C86 => 0x42A, + 0x1C87 => 0x462, + 0x1C88 => 0xA64A, + 0x1D79 => 0xA77D, + 0x1D7D => 0x2C63, + 0x1D8E => 0xA7C6, + 0x1E01 => 0x1E00, + 0x1E03 => 0x1E02, + 0x1E05 => 0x1E04, + 0x1E07 => 0x1E06, + 0x1E09 => 0x1E08, + 0x1E0B => 0x1E0A, + 0x1E0D => 0x1E0C, + 0x1E0F => 0x1E0E, + 0x1E11 => 0x1E10, + 0x1E13 => 0x1E12, + 0x1E15 => 0x1E14, + 0x1E17 => 0x1E16, + 0x1E19 => 0x1E18, + 0x1E1B => 0x1E1A, + 0x1E1D => 0x1E1C, + 0x1E1F => 0x1E1E, + 0x1E21 => 0x1E20, + 0x1E23 => 0x1E22, + 0x1E25 => 0x1E24, + 0x1E27 => 0x1E26, + 0x1E29 => 0x1E28, + 0x1E2B => 0x1E2A, + 0x1E2D => 0x1E2C, + 0x1E2F => 0x1E2E, + 0x1E31 => 0x1E30, + 0x1E33 => 0x1E32, + 0x1E35 => 0x1E34, + 0x1E37 => 0x1E36, + 0x1E39 => 0x1E38, + 0x1E3B => 0x1E3A, + 0x1E3D => 0x1E3C, + 0x1E3F => 0x1E3E, + 0x1E41 => 0x1E40, + 0x1E43 => 0x1E42, + 0x1E45 => 0x1E44, + 0x1E47 => 0x1E46, + 0x1E49 => 0x1E48, + 0x1E4B => 0x1E4A, + 0x1E4D => 0x1E4C, + 0x1E4F => 0x1E4E, + 0x1E51 => 0x1E50, + 0x1E53 => 0x1E52, + 0x1E55 => 0x1E54, + 0x1E57 => 0x1E56, + 0x1E59 => 0x1E58, + 0x1E5B => 0x1E5A, + 0x1E5D => 0x1E5C, + 0x1E5F => 0x1E5E, + 0x1E61 => 0x1E60, + 0x1E63 => 0x1E62, + 0x1E65 => 0x1E64, + 0x1E67 => 0x1E66, + 0x1E69 => 0x1E68, + 0x1E6B => 0x1E6A, + 0x1E6D => 0x1E6C, + 0x1E6F => 0x1E6E, + 0x1E71 => 0x1E70, + 0x1E73 => 0x1E72, + 0x1E75 => 0x1E74, + 0x1E77 => 0x1E76, + 0x1E79 => 0x1E78, + 0x1E7B => 0x1E7A, + 0x1E7D => 0x1E7C, + 0x1E7F => 0x1E7E, + 0x1E81 => 0x1E80, + 0x1E83 => 0x1E82, + 0x1E85 => 0x1E84, + 0x1E87 => 0x1E86, + 0x1E89 => 0x1E88, + 0x1E8B => 0x1E8A, + 0x1E8D => 0x1E8C, + 0x1E8F => 0x1E8E, + 0x1E91 => 0x1E90, + 0x1E93 => 0x1E92, + 0x1E95 => 0x1E94, + 0x1E9B => 0x1E60, + 0x1EA1 => 0x1EA0, + 0x1EA3 => 0x1EA2, + 0x1EA5 => 0x1EA4, + 0x1EA7 => 0x1EA6, + 0x1EA9 => 0x1EA8, + 0x1EAB => 0x1EAA, + 0x1EAD => 0x1EAC, + 0x1EAF => 0x1EAE, + 0x1EB1 => 0x1EB0, + 0x1EB3 => 0x1EB2, + 0x1EB5 => 0x1EB4, + 0x1EB7 => 0x1EB6, + 0x1EB9 => 0x1EB8, + 0x1EBB => 0x1EBA, + 0x1EBD => 0x1EBC, + 0x1EBF => 0x1EBE, + 0x1EC1 => 0x1EC0, + 0x1EC3 => 0x1EC2, + 0x1EC5 => 0x1EC4, + 0x1EC7 => 0x1EC6, + 0x1EC9 => 0x1EC8, + 0x1ECB => 0x1ECA, + 0x1ECD => 0x1ECC, + 0x1ECF => 0x1ECE, + 0x1ED1 => 0x1ED0, + 0x1ED3 => 0x1ED2, + 0x1ED5 => 0x1ED4, + 0x1ED7 => 0x1ED6, + 0x1ED9 => 0x1ED8, + 0x1EDB => 0x1EDA, + 0x1EDD => 0x1EDC, + 0x1EDF => 0x1EDE, + 0x1EE1 => 0x1EE0, + 0x1EE3 => 0x1EE2, + 0x1EE5 => 0x1EE4, + 0x1EE7 => 0x1EE6, + 0x1EE9 => 0x1EE8, + 0x1EEB => 0x1EEA, + 0x1EED => 0x1EEC, + 0x1EEF => 0x1EEE, + 0x1EF1 => 0x1EF0, + 0x1EF3 => 0x1EF2, + 0x1EF5 => 0x1EF4, + 0x1EF7 => 0x1EF6, + 0x1EF9 => 0x1EF8, + 0x1EFB => 0x1EFA, + 0x1EFD => 0x1EFC, + 0x1EFF => 0x1EFE, + 0x1F00 => 0x1F08, + 0x1F01 => 0x1F09, + 0x1F02 => 0x1F0A, + 0x1F03 => 0x1F0B, + 0x1F04 => 0x1F0C, + 0x1F05 => 0x1F0D, + 0x1F06 => 0x1F0E, + 0x1F07 => 0x1F0F, + 0x1F10 => 0x1F18, + 0x1F11 => 0x1F19, + 0x1F12 => 0x1F1A, + 0x1F13 => 0x1F1B, + 0x1F14 => 0x1F1C, + 0x1F15 => 0x1F1D, + 0x1F20 => 0x1F28, + 0x1F21 => 0x1F29, + 0x1F22 => 0x1F2A, + 0x1F23 => 0x1F2B, + 0x1F24 => 0x1F2C, + 0x1F25 => 0x1F2D, + 0x1F26 => 0x1F2E, + 0x1F27 => 0x1F2F, + 0x1F30 => 0x1F38, + 0x1F31 => 0x1F39, + 0x1F32 => 0x1F3A, + 0x1F33 => 0x1F3B, + 0x1F34 => 0x1F3C, + 0x1F35 => 0x1F3D, + 0x1F36 => 0x1F3E, + 0x1F37 => 0x1F3F, + 0x1F40 => 0x1F48, + 0x1F41 => 0x1F49, + 0x1F42 => 0x1F4A, + 0x1F43 => 0x1F4B, + 0x1F44 => 0x1F4C, + 0x1F45 => 0x1F4D, + 0x1F51 => 0x1F59, + 0x1F53 => 0x1F5B, + 0x1F55 => 0x1F5D, + 0x1F57 => 0x1F5F, + 0x1F60 => 0x1F68, + 0x1F61 => 0x1F69, + 0x1F62 => 0x1F6A, + 0x1F63 => 0x1F6B, + 0x1F64 => 0x1F6C, + 0x1F65 => 0x1F6D, + 0x1F66 => 0x1F6E, + 0x1F67 => 0x1F6F, + 0x1F70 => 0x1FBA, + 0x1F71 => 0x1FBB, + 0x1F72 => 0x1FC8, + 0x1F73 => 0x1FC9, + 0x1F74 => 0x1FCA, + 0x1F75 => 0x1FCB, + 0x1F76 => 0x1FDA, + 0x1F77 => 0x1FDB, + 0x1F78 => 0x1FF8, + 0x1F79 => 0x1FF9, + 0x1F7A => 0x1FEA, + 0x1F7B => 0x1FEB, + 0x1F7C => 0x1FFA, + 0x1F7D => 0x1FFB, + 0x1F80 => 0x1F88, + 0x1F81 => 0x1F89, + 0x1F82 => 0x1F8A, + 0x1F83 => 0x1F8B, + 0x1F84 => 0x1F8C, + 0x1F85 => 0x1F8D, + 0x1F86 => 0x1F8E, + 0x1F87 => 0x1F8F, + 0x1F90 => 0x1F98, + 0x1F91 => 0x1F99, + 0x1F92 => 0x1F9A, + 0x1F93 => 0x1F9B, + 0x1F94 => 0x1F9C, + 0x1F95 => 0x1F9D, + 0x1F96 => 0x1F9E, + 0x1F97 => 0x1F9F, + 0x1FA0 => 0x1FA8, + 0x1FA1 => 0x1FA9, + 0x1FA2 => 0x1FAA, + 0x1FA3 => 0x1FAB, + 0x1FA4 => 0x1FAC, + 0x1FA5 => 0x1FAD, + 0x1FA6 => 0x1FAE, + 0x1FA7 => 0x1FAF, + 0x1FB0 => 0x1FB8, + 0x1FB1 => 0x1FB9, + 0x1FB3 => 0x1FBC, + 0x1FBE => 0x399, + 0x1FC3 => 0x1FCC, + 0x1FD0 => 0x1FD8, + 0x1FD1 => 0x1FD9, + 0x1FE0 => 0x1FE8, + 0x1FE1 => 0x1FE9, + 0x1FE5 => 0x1FEC, + 0x1FF3 => 0x1FFC, + 0x214E => 0x2132, + 0x2170 => 0x2160, + 0x2171 => 0x2161, + 0x2172 => 0x2162, + 0x2173 => 0x2163, + 0x2174 => 0x2164, + 0x2175 => 0x2165, + 0x2176 => 0x2166, + 0x2177 => 0x2167, + 0x2178 => 0x2168, + 0x2179 => 0x2169, + 0x217A => 0x216A, + 0x217B => 0x216B, + 0x217C => 0x216C, + 0x217D => 0x216D, + 0x217E => 0x216E, + 0x217F => 0x216F, + 0x2184 => 0x2183, + 0x24D0 => 0x24B6, + 0x24D1 => 0x24B7, + 0x24D2 => 0x24B8, + 0x24D3 => 0x24B9, + 0x24D4 => 0x24BA, + 0x24D5 => 0x24BB, + 0x24D6 => 0x24BC, + 0x24D7 => 0x24BD, + 0x24D8 => 0x24BE, + 0x24D9 => 0x24BF, + 0x24DA => 0x24C0, + 0x24DB => 0x24C1, + 0x24DC => 0x24C2, + 0x24DD => 0x24C3, + 0x24DE => 0x24C4, + 0x24DF => 0x24C5, + 0x24E0 => 0x24C6, + 0x24E1 => 0x24C7, + 0x24E2 => 0x24C8, + 0x24E3 => 0x24C9, + 0x24E4 => 0x24CA, + 0x24E5 => 0x24CB, + 0x24E6 => 0x24CC, + 0x24E7 => 0x24CD, + 0x24E8 => 0x24CE, + 0x24E9 => 0x24CF, + 0x2C30 => 0x2C00, + 0x2C31 => 0x2C01, + 0x2C32 => 0x2C02, + 0x2C33 => 0x2C03, + 0x2C34 => 0x2C04, + 0x2C35 => 0x2C05, + 0x2C36 => 0x2C06, + 0x2C37 => 0x2C07, + 0x2C38 => 0x2C08, + 0x2C39 => 0x2C09, + 0x2C3A => 0x2C0A, + 0x2C3B => 0x2C0B, + 0x2C3C => 0x2C0C, + 0x2C3D => 0x2C0D, + 0x2C3E => 0x2C0E, + 0x2C3F => 0x2C0F, + 0x2C40 => 0x2C10, + 0x2C41 => 0x2C11, + 0x2C42 => 0x2C12, + 0x2C43 => 0x2C13, + 0x2C44 => 0x2C14, + 0x2C45 => 0x2C15, + 0x2C46 => 0x2C16, + 0x2C47 => 0x2C17, + 0x2C48 => 0x2C18, + 0x2C49 => 0x2C19, + 0x2C4A => 0x2C1A, + 0x2C4B => 0x2C1B, + 0x2C4C => 0x2C1C, + 0x2C4D => 0x2C1D, + 0x2C4E => 0x2C1E, + 0x2C4F => 0x2C1F, + 0x2C50 => 0x2C20, + 0x2C51 => 0x2C21, + 0x2C52 => 0x2C22, + 0x2C53 => 0x2C23, + 0x2C54 => 0x2C24, + 0x2C55 => 0x2C25, + 0x2C56 => 0x2C26, + 0x2C57 => 0x2C27, + 0x2C58 => 0x2C28, + 0x2C59 => 0x2C29, + 0x2C5A => 0x2C2A, + 0x2C5B => 0x2C2B, + 0x2C5C => 0x2C2C, + 0x2C5D => 0x2C2D, + 0x2C5E => 0x2C2E, + 0x2C61 => 0x2C60, + 0x2C65 => 0x23A, + 0x2C66 => 0x23E, + 0x2C68 => 0x2C67, + 0x2C6A => 0x2C69, + 0x2C6C => 0x2C6B, + 0x2C73 => 0x2C72, + 0x2C76 => 0x2C75, + 0x2C81 => 0x2C80, + 0x2C83 => 0x2C82, + 0x2C85 => 0x2C84, + 0x2C87 => 0x2C86, + 0x2C89 => 0x2C88, + 0x2C8B => 0x2C8A, + 0x2C8D => 0x2C8C, + 0x2C8F => 0x2C8E, + 0x2C91 => 0x2C90, + 0x2C93 => 0x2C92, + 0x2C95 => 0x2C94, + 0x2C97 => 0x2C96, + 0x2C99 => 0x2C98, + 0x2C9B => 0x2C9A, + 0x2C9D => 0x2C9C, + 0x2C9F => 0x2C9E, + 0x2CA1 => 0x2CA0, + 0x2CA3 => 0x2CA2, + 0x2CA5 => 0x2CA4, + 0x2CA7 => 0x2CA6, + 0x2CA9 => 0x2CA8, + 0x2CAB => 0x2CAA, + 0x2CAD => 0x2CAC, + 0x2CAF => 0x2CAE, + 0x2CB1 => 0x2CB0, + 0x2CB3 => 0x2CB2, + 0x2CB5 => 0x2CB4, + 0x2CB7 => 0x2CB6, + 0x2CB9 => 0x2CB8, + 0x2CBB => 0x2CBA, + 0x2CBD => 0x2CBC, + 0x2CBF => 0x2CBE, + 0x2CC1 => 0x2CC0, + 0x2CC3 => 0x2CC2, + 0x2CC5 => 0x2CC4, + 0x2CC7 => 0x2CC6, + 0x2CC9 => 0x2CC8, + 0x2CCB => 0x2CCA, + 0x2CCD => 0x2CCC, + 0x2CCF => 0x2CCE, + 0x2CD1 => 0x2CD0, + 0x2CD3 => 0x2CD2, + 0x2CD5 => 0x2CD4, + 0x2CD7 => 0x2CD6, + 0x2CD9 => 0x2CD8, + 0x2CDB => 0x2CDA, + 0x2CDD => 0x2CDC, + 0x2CDF => 0x2CDE, + 0x2CE1 => 0x2CE0, + 0x2CE3 => 0x2CE2, + 0x2CEC => 0x2CEB, + 0x2CEE => 0x2CED, + 0x2CF3 => 0x2CF2, + 0x2D00 => 0x10A0, + 0x2D01 => 0x10A1, + 0x2D02 => 0x10A2, + 0x2D03 => 0x10A3, + 0x2D04 => 0x10A4, + 0x2D05 => 0x10A5, + 0x2D06 => 0x10A6, + 0x2D07 => 0x10A7, + 0x2D08 => 0x10A8, + 0x2D09 => 0x10A9, + 0x2D0A => 0x10AA, + 0x2D0B => 0x10AB, + 0x2D0C => 0x10AC, + 0x2D0D => 0x10AD, + 0x2D0E => 0x10AE, + 0x2D0F => 0x10AF, + 0x2D10 => 0x10B0, + 0x2D11 => 0x10B1, + 0x2D12 => 0x10B2, + 0x2D13 => 0x10B3, + 0x2D14 => 0x10B4, + 0x2D15 => 0x10B5, + 0x2D16 => 0x10B6, + 0x2D17 => 0x10B7, + 0x2D18 => 0x10B8, + 0x2D19 => 0x10B9, + 0x2D1A => 0x10BA, + 0x2D1B => 0x10BB, + 0x2D1C => 0x10BC, + 0x2D1D => 0x10BD, + 0x2D1E => 0x10BE, + 0x2D1F => 0x10BF, + 0x2D20 => 0x10C0, + 0x2D21 => 0x10C1, + 0x2D22 => 0x10C2, + 0x2D23 => 0x10C3, + 0x2D24 => 0x10C4, + 0x2D25 => 0x10C5, + 0x2D27 => 0x10C7, + 0x2D2D => 0x10CD, + 0xA641 => 0xA640, + 0xA643 => 0xA642, + 0xA645 => 0xA644, + 0xA647 => 0xA646, + 0xA649 => 0xA648, + 0xA64B => 0xA64A, + 0xA64D => 0xA64C, + 0xA64F => 0xA64E, + 0xA651 => 0xA650, + 0xA653 => 0xA652, + 0xA655 => 0xA654, + 0xA657 => 0xA656, + 0xA659 => 0xA658, + 0xA65B => 0xA65A, + 0xA65D => 0xA65C, + 0xA65F => 0xA65E, + 0xA661 => 0xA660, + 0xA663 => 0xA662, + 0xA665 => 0xA664, + 0xA667 => 0xA666, + 0xA669 => 0xA668, + 0xA66B => 0xA66A, + 0xA66D => 0xA66C, + 0xA681 => 0xA680, + 0xA683 => 0xA682, + 0xA685 => 0xA684, + 0xA687 => 0xA686, + 0xA689 => 0xA688, + 0xA68B => 0xA68A, + 0xA68D => 0xA68C, + 0xA68F => 0xA68E, + 0xA691 => 0xA690, + 0xA693 => 0xA692, + 0xA695 => 0xA694, + 0xA697 => 0xA696, + 0xA699 => 0xA698, + 0xA69B => 0xA69A, + 0xA723 => 0xA722, + 0xA725 => 0xA724, + 0xA727 => 0xA726, + 0xA729 => 0xA728, + 0xA72B => 0xA72A, + 0xA72D => 0xA72C, + 0xA72F => 0xA72E, + 0xA733 => 0xA732, + 0xA735 => 0xA734, + 0xA737 => 0xA736, + 0xA739 => 0xA738, + 0xA73B => 0xA73A, + 0xA73D => 0xA73C, + 0xA73F => 0xA73E, + 0xA741 => 0xA740, + 0xA743 => 0xA742, + 0xA745 => 0xA744, + 0xA747 => 0xA746, + 0xA749 => 0xA748, + 0xA74B => 0xA74A, + 0xA74D => 0xA74C, + 0xA74F => 0xA74E, + 0xA751 => 0xA750, + 0xA753 => 0xA752, + 0xA755 => 0xA754, + 0xA757 => 0xA756, + 0xA759 => 0xA758, + 0xA75B => 0xA75A, + 0xA75D => 0xA75C, + 0xA75F => 0xA75E, + 0xA761 => 0xA760, + 0xA763 => 0xA762, + 0xA765 => 0xA764, + 0xA767 => 0xA766, + 0xA769 => 0xA768, + 0xA76B => 0xA76A, + 0xA76D => 0xA76C, + 0xA76F => 0xA76E, + 0xA77A => 0xA779, + 0xA77C => 0xA77B, + 0xA77F => 0xA77E, + 0xA781 => 0xA780, + 0xA783 => 0xA782, + 0xA785 => 0xA784, + 0xA787 => 0xA786, + 0xA78C => 0xA78B, + 0xA791 => 0xA790, + 0xA793 => 0xA792, + 0xA794 => 0xA7C4, + 0xA797 => 0xA796, + 0xA799 => 0xA798, + 0xA79B => 0xA79A, + 0xA79D => 0xA79C, + 0xA79F => 0xA79E, + 0xA7A1 => 0xA7A0, + 0xA7A3 => 0xA7A2, + 0xA7A5 => 0xA7A4, + 0xA7A7 => 0xA7A6, + 0xA7A9 => 0xA7A8, + 0xA7B5 => 0xA7B4, + 0xA7B7 => 0xA7B6, + 0xA7B9 => 0xA7B8, + 0xA7BB => 0xA7BA, + 0xA7BD => 0xA7BC, + 0xA7BF => 0xA7BE, + 0xA7C3 => 0xA7C2, + 0xA7C8 => 0xA7C7, + 0xA7CA => 0xA7C9, + 0xA7F6 => 0xA7F5, + 0xAB53 => 0xA7B3, + 0xAB70 => 0x13A0, + 0xAB71 => 0x13A1, + 0xAB72 => 0x13A2, + 0xAB73 => 0x13A3, + 0xAB74 => 0x13A4, + 0xAB75 => 0x13A5, + 0xAB76 => 0x13A6, + 0xAB77 => 0x13A7, + 0xAB78 => 0x13A8, + 0xAB79 => 0x13A9, + 0xAB7A => 0x13AA, + 0xAB7B => 0x13AB, + 0xAB7C => 0x13AC, + 0xAB7D => 0x13AD, + 0xAB7E => 0x13AE, + 0xAB7F => 0x13AF, + 0xAB80 => 0x13B0, + 0xAB81 => 0x13B1, + 0xAB82 => 0x13B2, + 0xAB83 => 0x13B3, + 0xAB84 => 0x13B4, + 0xAB85 => 0x13B5, + 0xAB86 => 0x13B6, + 0xAB87 => 0x13B7, + 0xAB88 => 0x13B8, + 0xAB89 => 0x13B9, + 0xAB8A => 0x13BA, + 0xAB8B => 0x13BB, + 0xAB8C => 0x13BC, + 0xAB8D => 0x13BD, + 0xAB8E => 0x13BE, + 0xAB8F => 0x13BF, + 0xAB90 => 0x13C0, + 0xAB91 => 0x13C1, + 0xAB92 => 0x13C2, + 0xAB93 => 0x13C3, + 0xAB94 => 0x13C4, + 0xAB95 => 0x13C5, + 0xAB96 => 0x13C6, + 0xAB97 => 0x13C7, + 0xAB98 => 0x13C8, + 0xAB99 => 0x13C9, + 0xAB9A => 0x13CA, + 0xAB9B => 0x13CB, + 0xAB9C => 0x13CC, + 0xAB9D => 0x13CD, + 0xAB9E => 0x13CE, + 0xAB9F => 0x13CF, + 0xABA0 => 0x13D0, + 0xABA1 => 0x13D1, + 0xABA2 => 0x13D2, + 0xABA3 => 0x13D3, + 0xABA4 => 0x13D4, + 0xABA5 => 0x13D5, + 0xABA6 => 0x13D6, + 0xABA7 => 0x13D7, + 0xABA8 => 0x13D8, + 0xABA9 => 0x13D9, + 0xABAA => 0x13DA, + 0xABAB => 0x13DB, + 0xABAC => 0x13DC, + 0xABAD => 0x13DD, + 0xABAE => 0x13DE, + 0xABAF => 0x13DF, + 0xABB0 => 0x13E0, + 0xABB1 => 0x13E1, + 0xABB2 => 0x13E2, + 0xABB3 => 0x13E3, + 0xABB4 => 0x13E4, + 0xABB5 => 0x13E5, + 0xABB6 => 0x13E6, + 0xABB7 => 0x13E7, + 0xABB8 => 0x13E8, + 0xABB9 => 0x13E9, + 0xABBA => 0x13EA, + 0xABBB => 0x13EB, + 0xABBC => 0x13EC, + 0xABBD => 0x13ED, + 0xABBE => 0x13EE, + 0xABBF => 0x13EF, + 0xFF41 => 0xFF21, + 0xFF42 => 0xFF22, + 0xFF43 => 0xFF23, + 0xFF44 => 0xFF24, + 0xFF45 => 0xFF25, + 0xFF46 => 0xFF26, + 0xFF47 => 0xFF27, + 0xFF48 => 0xFF28, + 0xFF49 => 0xFF29, + 0xFF4A => 0xFF2A, + 0xFF4B => 0xFF2B, + 0xFF4C => 0xFF2C, + 0xFF4D => 0xFF2D, + 0xFF4E => 0xFF2E, + 0xFF4F => 0xFF2F, + 0xFF50 => 0xFF30, + 0xFF51 => 0xFF31, + 0xFF52 => 0xFF32, + 0xFF53 => 0xFF33, + 0xFF54 => 0xFF34, + 0xFF55 => 0xFF35, + 0xFF56 => 0xFF36, + 0xFF57 => 0xFF37, + 0xFF58 => 0xFF38, + 0xFF59 => 0xFF39, + 0xFF5A => 0xFF3A, + 0x10428 => 0x10400, + 0x10429 => 0x10401, + 0x1042A => 0x10402, + 0x1042B => 0x10403, + 0x1042C => 0x10404, + 0x1042D => 0x10405, + 0x1042E => 0x10406, + 0x1042F => 0x10407, + 0x10430 => 0x10408, + 0x10431 => 0x10409, + 0x10432 => 0x1040A, + 0x10433 => 0x1040B, + 0x10434 => 0x1040C, + 0x10435 => 0x1040D, + 0x10436 => 0x1040E, + 0x10437 => 0x1040F, + 0x10438 => 0x10410, + 0x10439 => 0x10411, + 0x1043A => 0x10412, + 0x1043B => 0x10413, + 0x1043C => 0x10414, + 0x1043D => 0x10415, + 0x1043E => 0x10416, + 0x1043F => 0x10417, + 0x10440 => 0x10418, + 0x10441 => 0x10419, + 0x10442 => 0x1041A, + 0x10443 => 0x1041B, + 0x10444 => 0x1041C, + 0x10445 => 0x1041D, + 0x10446 => 0x1041E, + 0x10447 => 0x1041F, + 0x10448 => 0x10420, + 0x10449 => 0x10421, + 0x1044A => 0x10422, + 0x1044B => 0x10423, + 0x1044C => 0x10424, + 0x1044D => 0x10425, + 0x1044E => 0x10426, + 0x1044F => 0x10427, + 0x104D8 => 0x104B0, + 0x104D9 => 0x104B1, + 0x104DA => 0x104B2, + 0x104DB => 0x104B3, + 0x104DC => 0x104B4, + 0x104DD => 0x104B5, + 0x104DE => 0x104B6, + 0x104DF => 0x104B7, + 0x104E0 => 0x104B8, + 0x104E1 => 0x104B9, + 0x104E2 => 0x104BA, + 0x104E3 => 0x104BB, + 0x104E4 => 0x104BC, + 0x104E5 => 0x104BD, + 0x104E6 => 0x104BE, + 0x104E7 => 0x104BF, + 0x104E8 => 0x104C0, + 0x104E9 => 0x104C1, + 0x104EA => 0x104C2, + 0x104EB => 0x104C3, + 0x104EC => 0x104C4, + 0x104ED => 0x104C5, + 0x104EE => 0x104C6, + 0x104EF => 0x104C7, + 0x104F0 => 0x104C8, + 0x104F1 => 0x104C9, + 0x104F2 => 0x104CA, + 0x104F3 => 0x104CB, + 0x104F4 => 0x104CC, + 0x104F5 => 0x104CD, + 0x104F6 => 0x104CE, + 0x104F7 => 0x104CF, + 0x104F8 => 0x104D0, + 0x104F9 => 0x104D1, + 0x104FA => 0x104D2, + 0x104FB => 0x104D3, + 0x10CC0 => 0x10C80, + 0x10CC1 => 0x10C81, + 0x10CC2 => 0x10C82, + 0x10CC3 => 0x10C83, + 0x10CC4 => 0x10C84, + 0x10CC5 => 0x10C85, + 0x10CC6 => 0x10C86, + 0x10CC7 => 0x10C87, + 0x10CC8 => 0x10C88, + 0x10CC9 => 0x10C89, + 0x10CCA => 0x10C8A, + 0x10CCB => 0x10C8B, + 0x10CCC => 0x10C8C, + 0x10CCD => 0x10C8D, + 0x10CCE => 0x10C8E, + 0x10CCF => 0x10C8F, + 0x10CD0 => 0x10C90, + 0x10CD1 => 0x10C91, + 0x10CD2 => 0x10C92, + 0x10CD3 => 0x10C93, + 0x10CD4 => 0x10C94, + 0x10CD5 => 0x10C95, + 0x10CD6 => 0x10C96, + 0x10CD7 => 0x10C97, + 0x10CD8 => 0x10C98, + 0x10CD9 => 0x10C99, + 0x10CDA => 0x10C9A, + 0x10CDB => 0x10C9B, + 0x10CDC => 0x10C9C, + 0x10CDD => 0x10C9D, + 0x10CDE => 0x10C9E, + 0x10CDF => 0x10C9F, + 0x10CE0 => 0x10CA0, + 0x10CE1 => 0x10CA1, + 0x10CE2 => 0x10CA2, + 0x10CE3 => 0x10CA3, + 0x10CE4 => 0x10CA4, + 0x10CE5 => 0x10CA5, + 0x10CE6 => 0x10CA6, + 0x10CE7 => 0x10CA7, + 0x10CE8 => 0x10CA8, + 0x10CE9 => 0x10CA9, + 0x10CEA => 0x10CAA, + 0x10CEB => 0x10CAB, + 0x10CEC => 0x10CAC, + 0x10CED => 0x10CAD, + 0x10CEE => 0x10CAE, + 0x10CEF => 0x10CAF, + 0x10CF0 => 0x10CB0, + 0x10CF1 => 0x10CB1, + 0x10CF2 => 0x10CB2, + 0x118C0 => 0x118A0, + 0x118C1 => 0x118A1, + 0x118C2 => 0x118A2, + 0x118C3 => 0x118A3, + 0x118C4 => 0x118A4, + 0x118C5 => 0x118A5, + 0x118C6 => 0x118A6, + 0x118C7 => 0x118A7, + 0x118C8 => 0x118A8, + 0x118C9 => 0x118A9, + 0x118CA => 0x118AA, + 0x118CB => 0x118AB, + 0x118CC => 0x118AC, + 0x118CD => 0x118AD, + 0x118CE => 0x118AE, + 0x118CF => 0x118AF, + 0x118D0 => 0x118B0, + 0x118D1 => 0x118B1, + 0x118D2 => 0x118B2, + 0x118D3 => 0x118B3, + 0x118D4 => 0x118B4, + 0x118D5 => 0x118B5, + 0x118D6 => 0x118B6, + 0x118D7 => 0x118B7, + 0x118D8 => 0x118B8, + 0x118D9 => 0x118B9, + 0x118DA => 0x118BA, + 0x118DB => 0x118BB, + 0x118DC => 0x118BC, + 0x118DD => 0x118BD, + 0x118DE => 0x118BE, + 0x118DF => 0x118BF, + 0x16E60 => 0x16E40, + 0x16E61 => 0x16E41, + 0x16E62 => 0x16E42, + 0x16E63 => 0x16E43, + 0x16E64 => 0x16E44, + 0x16E65 => 0x16E45, + 0x16E66 => 0x16E46, + 0x16E67 => 0x16E47, + 0x16E68 => 0x16E48, + 0x16E69 => 0x16E49, + 0x16E6A => 0x16E4A, + 0x16E6B => 0x16E4B, + 0x16E6C => 0x16E4C, + 0x16E6D => 0x16E4D, + 0x16E6E => 0x16E4E, + 0x16E6F => 0x16E4F, + 0x16E70 => 0x16E50, + 0x16E71 => 0x16E51, + 0x16E72 => 0x16E52, + 0x16E73 => 0x16E53, + 0x16E74 => 0x16E54, + 0x16E75 => 0x16E55, + 0x16E76 => 0x16E56, + 0x16E77 => 0x16E57, + 0x16E78 => 0x16E58, + 0x16E79 => 0x16E59, + 0x16E7A => 0x16E5A, + 0x16E7B => 0x16E5B, + 0x16E7C => 0x16E5C, + 0x16E7D => 0x16E5D, + 0x16E7E => 0x16E5E, + 0x16E7F => 0x16E5F, + 0x1E922 => 0x1E900, + 0x1E923 => 0x1E901, + 0x1E924 => 0x1E902, + 0x1E925 => 0x1E903, + 0x1E926 => 0x1E904, + 0x1E927 => 0x1E905, + 0x1E928 => 0x1E906, + 0x1E929 => 0x1E907, + 0x1E92A => 0x1E908, + 0x1E92B => 0x1E909, + 0x1E92C => 0x1E90A, + 0x1E92D => 0x1E90B, + 0x1E92E => 0x1E90C, + 0x1E92F => 0x1E90D, + 0x1E930 => 0x1E90E, + 0x1E931 => 0x1E90F, + 0x1E932 => 0x1E910, + 0x1E933 => 0x1E911, + 0x1E934 => 0x1E912, + 0x1E935 => 0x1E913, + 0x1E936 => 0x1E914, + 0x1E937 => 0x1E915, + 0x1E938 => 0x1E916, + 0x1E939 => 0x1E917, + 0x1E93A => 0x1E918, + 0x1E93B => 0x1E919, + 0x1E93C => 0x1E91A, + 0x1E93D => 0x1E91B, + 0x1E93E => 0x1E91C, + 0x1E93F => 0x1E91D, + 0x1E940 => 0x1E91E, + 0x1E941 => 0x1E91F, + 0x1E942 => 0x1E920, + 0x1E943 => 0x1E921, +); diff --git a/src/opis/string/src/Exception/InvalidCodePointException.php b/src/opis/string/src/Exception/InvalidCodePointException.php index f1a3fe96..f93dc6b9 100644 --- a/src/opis/string/src/Exception/InvalidCodePointException.php +++ b/src/opis/string/src/Exception/InvalidCodePointException.php @@ -1,5 +1,6 @@ codePoint = $codePoint; - } + /** + * @var mixed + */ + protected $codePoint; - /** - * @return mixed - */ - public function codePoint() - { - return$this->codePoint; - } + /** + * @param $codePoint + * @param Throwable|null $previous + */ + public function __construct( $codePoint, Throwable $previous = null ) { + parent::__construct( 'Invalid code point', 0, $previous ); + $this->codePoint = $codePoint; + } + + /** + * @return mixed + */ + public function codePoint() { + return $this->codePoint; + } } diff --git a/src/opis/string/src/Exception/InvalidStringException.php b/src/opis/string/src/Exception/InvalidStringException.php index ede9d682..c5731f9e 100644 --- a/src/opis/string/src/Exception/InvalidStringException.php +++ b/src/opis/string/src/Exception/InvalidStringException.php @@ -1,5 +1,6 @@ string = $string; - $this->offset = $offset; - } - - /** - * @return string - */ - public function string(): string - { - return $this->string; - } - - /** - * @return int - */ - public function offset(): int - { - return $this->offset; - } +class InvalidStringException extends UnicodeException { + + /** + * @var string + */ + protected $string; + + /** + * @var int + */ + protected $offset; + + /** + * @param string $string + * @param int $offset + * @param Throwable|null $previous + */ + public function __construct( string $string, int $offset = -1, Throwable $previous = null ) { + parent::__construct( "Invalid UTF-8 string at offset {$offset}", 0, $previous ); + $this->string = $string; + $this->offset = $offset; + } + + /** + * @return string + */ + public function string(): string { + return $this->string; + } + + /** + * @return int + */ + public function offset(): int { + return $this->offset; + } } diff --git a/src/opis/string/src/Exception/UnicodeException.php b/src/opis/string/src/Exception/UnicodeException.php index 22e6d2fa..88993be3 100644 --- a/src/opis/string/src/Exception/UnicodeException.php +++ b/src/opis/string/src/Exception/UnicodeException.php @@ -1,5 +1,6 @@ codes = $codes; - $this->length = count($codes); - } - - /** - * @return int[] - */ - public function codePoints(): array - { - return $this->codes; - } - - /** - * @return string[] - */ - public function chars(): array - { - if ($this->chars === null) { - $this->chars = self::getCharsFromCodePoints($this->codes); - } - return $this->chars; - } - - /** - * @return int - */ - public function length(): int - { - return $this->length; - } - - /** - * @return bool - */ - public function isEmpty(): bool - { - return $this->length === 0; - } - - /** - * @param string|self|int[]|string[] $text - * @param bool $ignoreCase - * @return bool - */ - public function equals($text, $ignoreCase = false): bool - { - return $this->compareTo($text, $ignoreCase) === 0; - } - - /** - * @param string|self|int[]|string[] $text - * @param bool $ignoreCase - * @return int - */ - public function compareTo($text, $ignoreCase = false): int - { - $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; - - $text = self::resolveCodePoints($text, $mode); - - $length = count($text); - - if ($length !== $this->length) { - return $this->length <=> $length; - } - - return $this->getMappedCodes($mode) <=> $text; - } - - /** - * @param string|self|int[]|string[] $text - * @param bool $ignoreCase - * @return bool - */ - public function contains($text, $ignoreCase = false): bool - { - return $this->indexOf($text, 0, $ignoreCase) !== -1; - } - - /** - * @param string|self|int[]|string[] $text - * @param bool $ignoreCase - * @return bool - */ - public function startsWith($text, $ignoreCase = false): bool - { - $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; - - $text = self::resolveCodePoints($text, $mode); - - $len = count($text); - - if ($len === 0 || $len > $this->length) { - return false; - } - - return array_slice($this->getMappedCodes($mode), 0, $len) === $text; - } - - /** - * @param string|self|int[]|string[] $text - * @param bool $ignoreCase - * @return bool - */ - public function endsWith($text, $ignoreCase = false): bool - { - $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; - - $text = self::resolveCodePoints($text, $mode); - - if (empty($text)) { - return false; - } - - $codes = $this->getMappedCodes($mode); - - $offset = $this->length - count($text); - - if ($offset < 0) { - return false; - } - - return array_slice($codes, $offset) === $text; - } - - /** - * @param string|self|int[]|string[] $text - * @param int $offset - * @param bool $ignoreCase - * @return int - */ - public function indexOf($text, $offset = 0, $ignoreCase = false): int - { - if ($offset < 0) { - $offset += $this->length; - } - if ($offset < 0 || $offset >= $this->length) { - return -1; - } - - $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; - - $text = self::resolveCodePoints($text, $mode); - - $len = count($text); - - if ($len === 0 || $offset + $len > $this->length) { - return -1; - } - - return $this->doIndexOf($this->getMappedCodes($mode), $text, $offset); - } - - /** - * @param string|self|int[]|string[] $text - * @param int $offset - * @param bool $ignoreCase - * @return int - */ - public function lastIndexOf($text, $offset = 0, $ignoreCase = false): int - { - if ($offset < 0) { - $start = $this->length + $offset; - if ($start < 0) { - return -1; - } - $last = 0; - } else { - if ($offset >= $this->length) { - return -1; - } - $start = $this->length - 1; - $last = $offset; - } - - $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; - - $text = self::resolveCodePoints($text, $mode); - - $len = count($text); - - if ($len === 0) { - return -1; - } - - if ($offset < 0) { - if ($len > $this->length) { - return -1; - } - $start = min($start, $this->length - $len); - } elseif ($offset + $len > $this->length) { - return -1; - } - - $codes = $this->getMappedCodes($mode); - - for ($i = $start; $i >= $last; $i--) { - $match = true; - - for ($j = 0; $j < $len; $j++) { - if ($codes[$i + $j] !== $text[$j]) { - $match = false; - break; - } - } - - if ($match) { - return $i; - } - } - - return -1; - } - - /** - * @param string|self|int[]|string[] $text - * @param bool $ignoreCase - * @param bool $allowPrefixOnly If true the result can contain only the prefix - * @return $this - */ - public function ensurePrefix($text, $ignoreCase = false, $allowPrefixOnly = true): self - { - $text = self::resolveCodePoints($text); - - $len = count($text); - - if ($len === 0) { - return clone $this; - } - - if ($this->length === 0) { - return new static($text); - } - - if ($ignoreCase) { - $prefix = self::getMappedCodePoints($text, self::FOLD_CASE); - } else { - $prefix = &$text; - } - - if ($this->length === $len) { - $part = $this->getMappedCodes($ignoreCase ? self::FOLD_CASE : self::KEEP_CASE); - if ($allowPrefixOnly && $part === $prefix) { - return clone $this; - } - // Remove last element to avoid double check - array_pop($part); - } elseif ($this->length < $len) { - $part = $this->getMappedCodes($ignoreCase ? self::FOLD_CASE : self::KEEP_CASE); - // Checks if this can be a suffix - if ($allowPrefixOnly && (array_slice($prefix, 0, $this->length) === $part)) { - $text = array_slice($text, $this->length); - return new static(array_merge($this->codes, $text)); - } - } else { - $part = array_slice($this->codes, 0, $len); - if ($ignoreCase) { - $part = self::getMappedCodePoints($part, self::FOLD_CASE); - } - if ($part === $prefix) { - return clone $this; - } - // Remove last element to avoid double check - array_pop($part); - } - - $copy = $len; - - $part_len = count($part); - - while ($part_len) { - if ($part === array_slice($prefix, -$part_len)) { - $copy = $len - $part_len; - break; - } - array_pop($part); - $part_len--; - } - - if ($copy === 0) { - return clone $this; - } - - if ($copy < $len) { - $text = array_slice($text, 0, $copy); - } - - return new static(array_merge($text, $this->codes)); - } - - /** - * @param string|self|int[]|string[] $text - * @param bool $ignoreCase - * @param bool $allowSuffixOnly If true the result can contain only the suffix - * @return static - */ - public function ensureSuffix($text, $ignoreCase = false, $allowSuffixOnly = true): self - { - $text = self::resolveCodePoints($text); - - $len = count($text); - - if ($len === 0) { - return clone $this; - } - - if ($this->length === 0) { - return new static($text); - } - - if ($ignoreCase) { - $suffix = self::getMappedCodePoints($text, self::FOLD_CASE); - } else { - $suffix = &$text; - } - - if ($this->length === $len) { - $part = $this->getMappedCodes($ignoreCase ? self::FOLD_CASE : self::KEEP_CASE); - if ($allowSuffixOnly && $part === $suffix) { - return clone $this; - } - // Remove first element to avoid double check - array_shift($part); - } elseif ($this->length < $len) { - $part = $this->getMappedCodes($ignoreCase ? self::FOLD_CASE : self::KEEP_CASE); - // Checks if this can be a prefix - if ($allowSuffixOnly && (array_slice($suffix, -$this->length) === $part)) { - $text = array_slice($text, 0, $len - $this->length); - return new static(array_merge($text, $this->codes)); - } - } else { - $part = array_slice($this->codes, -$len); - if ($ignoreCase) { - $part = self::getMappedCodePoints($part, self::FOLD_CASE); - } - if ($part === $suffix) { - return clone $this; - } - // Remove first element to avoid double check - array_shift($part); - } - - $skip = 0; - - $part_len = count($part); - - while ($part_len) { - if ($part === array_slice($suffix, 0, $part_len)) { - $skip = $part_len; - break; - } - array_shift($part); - $part_len--; - } - - if ($skip === $len) { - return clone $this; - } - - if ($skip) { - array_splice($text, 0, $skip); - } - - return new static(array_merge($this->codes, $text)); - } - - /** - * @param string|self|int[]|string[] $text - * @param int $mode - * @return static - */ - public function append($text, $mode = self::KEEP_CASE): self - { - return new static(array_merge($this->codes, self::resolveCodePoints($text, $mode))); - } - - /** - * @param string|self|int[]|string[] $text - * @param int $mode - * @return static - */ - public function prepend($text, $mode = self::KEEP_CASE): self - { - return new static(array_merge(self::resolveCodePoints($text, $mode), $this->codes)); - } - - /** - * @param string|self|int[]|string[] $text - * @param int $offset - * @param int $mode - * @return static - */ - public function insert($text, $offset, $mode = self::KEEP_CASE): self - { - $codes = $this->codes; - - array_splice($codes, $offset, 0, self::resolveCodePoints($text, $mode)); - - return new static($codes); - } - - /** - * @param int $offset - * @param int|null $length - * @return static - */ - public function remove($offset, $length = null): self - { - $codes = $this->codes; - - if ($length === null) { - array_splice($codes, $offset); - } else { - array_splice($codes, $offset, $length); - } - - return new static($codes); - } - - /** - * @param string|self|int[]|string[] $mask - * @return static - */ - public function trim($mask = " \t\n\r\0\x0B"): self - { - return $this->doTrim($mask, true, true); - } - - /** - * @param string|self|int[]|string[] $mask - * @return static - */ - public function trimLeft($mask = " \t\n\r\0\x0B"): self - { - return $this->doTrim($mask, true, false); - } - - /** - * @param string|self|int[]|string[] $mask - * @return static - */ - public function trimRight($mask = " \t\n\r\0\x0B"): self - { - return $this->doTrim($mask, false, true); - } - - /** - * @return static - */ - public function reverse(): self - { - return new static(array_reverse($this->codes)); - } - - /** - * @param int $times - * @return static - */ - public function repeat($times = 1): self - { - if ($times <= 1) { - return clone $this; - } - - $codes = []; - - while ($times--) { - $codes = array_merge($codes, $this->codes); - } - - return new static($codes); - } - - /** - * @param string|self|int[]|string[] $subject - * @param string|self|int[]|string[] $replace - * @param int $offset - * @param bool $ignoreCase - * @return static - */ - public function replace($subject, $replace, $offset = 0, $ignoreCase = false): self - { - if ($offset < 0) { - $offset += $this->length; - } - if ($offset < 0 || $offset >= $this->length) { - return clone $this; - } - - $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; - - $subject = self::resolveCodePoints($subject, $mode); - - $len = count($subject); - - if ($len === 0 || $offset + $len > $this->length) { - return clone $this; - } - - $offset = $this->doIndexOf($this->getMappedCodes($mode), $subject, $offset); - - if ($offset === -1) { - return clone $this; - } - - $codes = $this->codes; - - array_splice($codes, $offset, count($subject), self::resolveCodePoints($replace)); - - return new static($codes); - } - - /** - * @param string|self|int[]|string[] $subject - * @param string|self|int[]|string[] $replace - * @param bool $ignoreCase - * @param int $offset - * @return static - */ - public function replaceAll($subject, $replace, $offset = 0, $ignoreCase = false): self - { - if ($offset < 0) { - $offset += $this->length; - } - if ($offset < 0 || $offset >= $this->length) { - return clone $this; - } - - $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; - - $subject = self::resolveCodePoints($subject, $mode); - - $len = count($subject); - - if ($len === 0 || $offset + $len > $this->length) { - return clone $this; - } - - $replace = self::resolveCodePoints($replace); - - $codes = $this->getMappedCodes($mode); - - $copy = $this->codes; - - $fix = count($replace) - $len; - - $t = 0; - - while (($pos = $this->doIndexOf($codes, $subject, $offset)) >= 0) { - array_splice($copy, $pos + $t * $fix, $len, $replace); - $offset = $pos + $len; - $t++; - } - - return new static($copy); - } - - /** - * @param string|self|int[]|string[] $delimiter - * @param bool $ignoreCase - * @return array - */ - public function split($delimiter = '', $ignoreCase = false): array - { - $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; - $delimiter = self::resolveCodePoints($delimiter, $mode); - $len = count($delimiter); - - $ret = []; - - if ($len === 0) { - foreach ($this->codes as $code) { - $ret[] = new static([$code]); - } - } else { - $codes = $this->getMappedCodes($mode); - - $offset = 0; - - while (($pos = $this->doIndexOf($codes, $delimiter, $offset)) >= 0) { - $ret[] = new static(array_slice($this->codes, $offset, $pos - $offset)); - $offset = $pos + $len; - } - - $ret[] = new static(array_slice($this->codes, $offset)); - } - - return $ret; - } - - /** - * @param int $start - * @param int|null $length - * @return static - */ - public function substring($start, $length = null): self - { - return new static(array_slice($this->codes, $start, $length)); - } - - /** - * @param int $size If negative then pad left otherwise pad right - * @param self|string|int $char A char or a code point - * @return static - */ - public function pad($size, $char = 0x20): self - { - return new static(array_pad($this->codes, $size, self::resolveFirstCodePoint($char, 0x20))); - } - - /** - * @param int $size - * @param self|string|int $char - * @return static - */ - public function padLeft($size, $char = 0x20): self - { - if ($size > 0) { - $size = -$size; - } - - return $this->pad($size, $char); - } - - /** - * @param int $size - * @param self|string|int $char - * @return static - */ - public function padRight($size, $char = 0x20): self - { - if ($size < 0) { - $size = -$size; - } - - return $this->pad($size, $char); - } - - /** - * @return bool - */ - public function isLowerCase(): bool - { - return $this->isCase(self::LOWER_CASE); - } - - /** - * @return bool - */ - public function isUpperCase(): bool - { - return $this->isCase(self::UPPER_CASE); - } - - /** - * @return bool - */ - public function isAscii(): bool - { - $key = 'i' . self::ASCII_CONV; - - if (!isset($this->cache[$key])) { - $ok = true; - - foreach ($this->codes as $code) { - if ($code >= 0x80) { - $ok = false; - break; - } - } - - $this->cache[$key] = $ok; - } - - return $this->cache[$key]; - } - - /** - * Convert all chars to lower case (where possible) - * @return static - */ - public function toLower(): self - { - if ($this->cache['i' . self::LOWER_CASE] ?? false) { - return clone $this; - } - return new static($this->getMappedCodes(self::LOWER_CASE)); - } - - /** - * Convert all chars to upper case (where possible) - * @return static - */ - public function toUpper(): self - { - if ($this->cache['i' . self::UPPER_CASE] ?? false) { - return clone $this; - } - return new static($this->getMappedCodes(self::UPPER_CASE)); - } - - /** - * Converts all chars to their ASCII equivalent (if any) - * @return static - */ - public function toAscii(): self - { - if ($this->cache['i' . self::ASCII_CONV] ?? false) { - return clone $this; - } - return new static($this->getMappedCodes(self::ASCII_CONV)); - } - - /** - * @param int $index - * @return string - */ - public function charAt($index): string - { - // Allow negative index - if ($index < 0 && $index + $this->length >= 0) { - $index += $this->length; - } - - if ($index < 0 || $index >= $this->length) { - return ''; - } - - return $this->chars()[$index]; - } - - /** - * @param int $index - * @return int - */ - public function codePointAt($index): int - { - // Allow negative index - if ($index < 0 && $index + $this->length >= 0) { - $index += $this->length; - } - - if ($index < 0 || $index >= $this->length) { - return -1; - } - - return $this->codes[$index]; - } - - /** - * @param int $offset - * @return int - */ - public function __invoke(int $offset): int - { - if ($offset < 0) { - if ($offset + $this->length < 0) { - throw new OutOfBoundsException("Undefined offset: {$offset}"); - } - $offset += $this->length; - } elseif ($offset >= $this->length) { - throw new OutOfBoundsException("Undefined offset: {$offset}"); - } - - return $this->codes[$offset]; - } - - /** - * @inheritDoc - */ - public function offsetExists($offset): bool - { - // Allow negative index - if ($offset < 0) { - $offset += $this->length; - } - - return isset($this->codes[$offset]); - } - - /** - * @inheritDoc - */ - public function offsetGet($offset): string - { - if ($offset < 0) { - if ($offset + $this->length < 0) { - throw new OutOfBoundsException("Undefined offset: {$offset}"); - } - $offset += $this->length; - } elseif ($offset >= $this->length) { - throw new OutOfBoundsException("Undefined offset: {$offset}"); - } - - return $this->chars()[$offset]; - } - - /** - * @inheritDoc - */ - #[\ReturnTypeWillChange] - public function offsetSet($offset, $value) - { - // Allow negative index - if ($offset < 0) { - $offset += $this->length; - } - - if (!isset($this->codes[$offset])) { - return; - } - - - $value = self::resolveFirstCodePoint($value); - if ($value === -1) { - return; - } - - if ($value === $this->codes[$offset]) { - // Same value, nothing to do - return; - } - - $this->codes[$offset] = $value; - - // Clear cache - $this->str = null; - $this->cache = null; - if ($this->chars) { - $this->chars[$offset] = self::getCharFromCodePoint($value); - } - } - - /** - * @inheritDoc - */ - #[\ReturnTypeWillChange] - public function offsetUnset($offset) - { - throw new RuntimeException("Invalid operation"); - } - - /** - * @inheritDoc - */ - public function count(): int - { - return $this->length; - } - - /** - * @return string - */ - public function __toString(): string - { - if ($this->str === null) { - $this->str = self::getStringFromCodePoints($this->codes); - } - - return $this->str; - } - - /** - * @inheritDoc - */ - public function jsonSerialize(): string - { - return $this->__toString(); - } - - public function __serialize(): array - { - return [ - 'value' => $this->__toString(), - ]; - } - - public function __unserialize(array $data) - { - $this->str = $data['value']; - $this->codes = self::getCodePointsFromString($this->str); - $this->length = count($this->codes); - } - - /** - * Creates an unicode string instance from raw string - * @param string $string - * @param string|null $encoding Defaults to UTF-8 - * @param int $mode - * @return static - * @throws InvalidStringException - */ - public static function from($string, $encoding = null, $mode = self::KEEP_CASE): self - { - if ($encoding !== null && strcasecmp($encoding, 'UTF-8') !== 0) { - if (false === $string = @iconv($encoding, 'UTF-8', $string)) { - throw new UnicodeException("Could not convert string from '$encoding' encoding to UTF-8 encoding"); - } - } - - $instance = new static(self::getCodePointsFromString($string, $mode)); - if ($mode === self::KEEP_CASE) { - $instance->str = $string; - } - return $instance; - } - - /** - * Creates an unicode string instance from code points - * @param int[] $codes - * @param int $mode - * @return static - * @throws InvalidCodePointException - */ - public static function fromCodePoints($codes, $mode = self::KEEP_CASE): self - { - $map = self::getMapByMode($mode); - - foreach ($codes as &$code) { - if (!is_int($codes) || !self::isValidCodePoint($code)) { - throw new InvalidCodePointException($code); - } else { - $code = $map[$code] ?? $code; - } - } - - return new static(array_values($codes)); - } - - /** - * Converts the code point to corresponding char - * @param int $code - * @return string The char or an empty string if code point is invalid - */ - public static function getCharFromCodePoint($code): string - { - if ($code < 0) { - return ''; - } - - if ($code < 0x80) { - return chr($code); - } - - if ($code < 0x800) { - return chr(($code >> 6) + 0xC0) . chr(($code & 0x3F) + 0x80); - } - - if ($code >= 0xD800 && $code <= 0xDFFF) { - /* - The definition of UTF-8 prohibits encoding character numbers between - U+D800 and U+DFFF, which are reserved for use with the UTF-16 - encoding form (as surrogate pairs) and do not directly represent characters. - */ - return ''; - } - - if ($code <= 0xFFFF) { - return - chr(($code >> 12) + 0xE0) . - chr((($code >> 6) & 0x3F) + 0x80) . - chr(($code & 0x3F) + 0x80); - } - - if ($code <= 0x10FFFF) { - return - chr(($code >> 18) + 0xF0) . - chr((($code >> 12) & 0x3F) + 0x80) . - chr((($code >> 6) & 0x3F) + 0x80) . - chr(($code & 0x3F) + 0x80); - } - - /* - Restricted the range of characters to 0000-10FFFF (the UTF-16 accessible range). - */ - - return ''; - } - - /** - * Convert a string to a code point array - * @param string $str - * @param int $mode - * @return array - * @throws InvalidStringException - */ - public static function getCodePointsFromString($str, $mode = self::KEEP_CASE): array - { - // 0x00-0x7F - // 0xC2-0xDF 0x80-0xBF - // 0xE0-0xE0 0xA0-0xBF 0x80-0xBF - // 0xE1-0xEC 0x80-0xBF 0x80-0xBF - // 0xED-0xED 0x80-0x9F 0x80-0xBF - // 0xEE-0xEF 0x80-0xBF 0x80-0xBF - // 0xF0-0xF0 0x90-0xBF 0x80-0xBF 0x80-0xBF - // 0xF1-0xF3 0x80-0xBF 0x80-0xBF 0x80-0xBF - // 0xF4-0xF4 0x80-0x8F 0x80-0xBF 0x80-0xBF - - $codes = []; - $length = strlen($str); - $mode = self::getMapByMode($mode); - - $i = 0; - while ($i < $length) { - $ord0 = ord($str[$i++]); - - if ($ord0 < 0x80) { - $codes[] = $mode[$ord0] ?? $ord0; - continue; - } - - if ($i === $length || $ord0 < 0xC2 || $ord0 > 0xF4) { - throw new InvalidStringException($str, $i - 1); - } - - $ord1 = ord($str[$i++]); - - if ($ord0 < 0xE0) { - if ($ord1 < 0x80 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 1); - } - - $ord1 = ($ord0 - 0xC0) * 64 + $ord1 - 0x80; - $codes[] = $mode[$ord1] ?? $ord1; - - continue; - } - - if ($i === $length) { - throw new InvalidStringException($str, $i - 1); - } - - $ord2 = ord($str[$i++]); - - if ($ord0 < 0xF0) { - if ($ord0 === 0xE0) { - if ($ord1 < 0xA0 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 2); - } - } elseif ($ord0 === 0xED) { - if ($ord1 < 0x80 || $ord1 >= 0xA0) { - throw new InvalidStringException($str, $i - 2); - } - } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 2); - } - - if ($ord2 < 0x80 || $ord2 >= 0xC0) { - throw new InvalidStringException($str, $i - 1); - } - - $ord2 = ($ord0 - 0xE0) * 0x1000 + ($ord1 - 0x80) * 64 + $ord2 - 0x80; - $codes[] = $mode[$ord2] ?? $ord2; - - continue; - } - - if ($i === $length) { - throw new InvalidStringException($str, $i - 1); - } - - $ord3 = ord($str[$i++]); - - if ($ord0 < 0xF5) { - if ($ord0 === 0xF0) { - if ($ord1 < 0x90 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 3); - } - } elseif ($ord0 === 0xF4) { - if ($ord1 < 0x80 || $ord1 >= 0x90) { - throw new InvalidStringException($str, $i - 3); - } - } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 3); - } - - if ($ord2 < 0x80 || $ord2 >= 0xC0) { - throw new InvalidStringException($str, $i - 2); - } - - if ($ord3 < 0x80 || $ord3 >= 0xC0) { - throw new InvalidStringException($str, $i - 1); - } - - $ord3 = ($ord0 - 0xF0) * 0x40000 + ($ord1 - 0x80) * 0x1000 + ($ord2 - 0x80) * 64 + $ord3 - 0x80; - $codes[] = $mode[$ord3] ?? $ord3; - - continue; - } - - throw new InvalidStringException($str, $i - 1); - } - - return $codes; - } - - /** - * @param string $str - * @return iterable - * - * The key represents the current char index - * Value is a two element array - * - first element is an integer representing the code point - * - second element is an array of integers (length 1 to 4) representing bytes - */ - public static function walkString($str) - { - $i = 0; - $length = strlen($str); - - while ($i < $length) { - $index = $i; - - $ord0 = ord($str[$i++]); - - if ($ord0 < 0x80) { - yield $index => [ - $ord0, - [$ord0] - ]; - continue; - } - - if ($i === $length || $ord0 < 0xC2 || $ord0 > 0xF4) { - throw new InvalidStringException($str, $i - 1); - } - - $ord1 = ord($str[$i++]); - - if ($ord0 < 0xE0) { - if ($ord1 < 0x80 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 1); - } - - yield $index => [ - ($ord0 - 0xC0) * 64 + $ord1 - 0x80, - [$ord0, $ord1] - ]; - - continue; - } - - if ($i === $length) { - throw new InvalidStringException($str, $i - 1); - } - - $ord2 = ord($str[$i++]); - - if ($ord0 < 0xF0) { - if ($ord0 === 0xE0) { - if ($ord1 < 0xA0 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 2); - } - } elseif ($ord0 === 0xED) { - if ($ord1 < 0x80 || $ord1 >= 0xA0) { - throw new InvalidStringException($str, $i - 2); - } - } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 2); - } - - if ($ord2 < 0x80 || $ord2 >= 0xC0) { - throw new InvalidStringException($str, $i - 1); - } - - yield $index => [ - ($ord0 - 0xE0) * 0x1000 + ($ord1 - 0x80) * 64 + $ord2 - 0x80, - [$ord0, $ord1, $ord2] - ]; - - continue; - } - - if ($i === $length) { - throw new InvalidStringException($str, $i - 1); - } - - $ord3 = ord($str[$i++]); - - if ($ord0 < 0xF5) { - if ($ord0 === 0xF0) { - if ($ord1 < 0x90 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 3); - } - } elseif ($ord0 === 0xF4) { - if ($ord1 < 0x80 || $ord1 >= 0x90) { - throw new InvalidStringException($str, $i - 3); - } - } elseif ($ord1 < 0x80 || $ord1 >= 0xC0) { - throw new InvalidStringException($str, $i - 3); - } - - if ($ord2 < 0x80 || $ord2 >= 0xC0) { - throw new InvalidStringException($str, $i - 2); - } - - if ($ord3 < 0x80 || $ord3 >= 0xC0) { - throw new InvalidStringException($str, $i - 1); - } - - yield $index => [ - ($ord0 - 0xF0) * 0x40000 + ($ord1 - 0x80) * 0x1000 + ($ord2 - 0x80) * 64 + $ord3 - 0x80, - [$ord0, $ord1, $ord2, $ord3] - ]; - - continue; - } - - throw new InvalidStringException($str, $i - 1); - } - } - - /** - * Converts each code point to a char - * @param array $codes - * @param int $mode - * @return array - * @throws InvalidCodePointException - */ - public static function getCharsFromCodePoints($codes, $mode = self::KEEP_CASE): array - { - $mode = self::getMapByMode($mode); - - foreach ($codes as &$code) { - $char = self::getCharFromCodePoint($mode[$code] ?? $code); - if ($char === '') { - throw new InvalidCodePointException($code); - } else { - $code = $char; - } - } - - return $codes; - } - - /** - * @param string $str - * @param int $mode - * @return string[] - */ - public static function getCharsFromString($str, $mode = self::KEEP_CASE): array - { - return self::getCharsFromCodePoints(self::getCodePointsFromString($str), $mode); - } - - /** - * Converts all code points to chars and returns the string - * Invalid code points are ignored - * @param array $codes - * @param int $mode - * @return string - */ - public static function getStringFromCodePoints($codes, $mode = self::KEEP_CASE): string - { - $str = ''; - - $mode = self::getMapByMode($mode); - - foreach ($codes as $code) { - if (isset($mode[$code])) { - $code = $mode[$code]; - } - - if ($code < 0x80) { - $str .= chr($code); - continue; - } - - if ($code < 0x800) { - $str .= chr(($code >> 6) + 0xC0) . chr(($code & 0x3F) + 0x80); - continue; - } - - if ($code >= 0xD800 && $code <= 0xDFFF) { - continue; - } - - if ($code <= 0xFFFF) { - $str .= - chr(($code >> 12) + 0xE0) . - chr((($code >> 6) & 0x3F) + 0x80) . - chr(($code & 0x3F) + 0x80); - continue; - } - - if ($code <= 0x10FFFF) { - $str .= - chr(($code >> 18) + 0xF0) . - chr((($code >> 12) & 0x3F) + 0x80) . - chr((($code >> 6) & 0x3F) + 0x80) . - chr(($code & 0x3F) + 0x80); - } - } - - return $str; - } - - /** - * @param array $codes - * @param int $mode - * @return array - */ - public static function getMappedCodePoints($codes, $mode): array - { - if ($mode === self::KEEP_CASE) { - return $codes; - } - - $mode = self::getMapByMode($mode); - - if (empty($mode)) { - return $codes; - } - - foreach ($codes as &$code) { - $code = $mode[$code] ?? $code; - } - - return $codes; - } - - /** - * Checks if a code point is valid - * @param int $code - * @return bool - */ - public static function isValidCodePoint($code): bool - { - if ($code < 0 || $code > 0x10FFFF) { - return false; - } - - return $code < 0xD800 || $code > 0xDFFF; - } - - /** - * @param int $mode - * @return int[] - */ - private function getMappedCodes(int $mode): array - { - if ($mode === self::KEEP_CASE || ($this->cache['i' . $mode] ?? false)) { - return $this->codes; - } - - $key = 'm' . $mode; - - if (!isset($this->cache[$key])) { - $this->cache[$key] = self::getMappedCodePoints($this->codes, $mode); - } - - return $this->cache[$key]; - } - - /** - * @param int $mode - * @return bool - */ - private function isCase(int $mode): bool - { - $key = 'i' . $mode; - - if (!isset($this->cache[$key])) { - $list = self::getMapByMode($mode); - foreach ($this->codes as $code) { - if (isset($list[$code])) { - return $this->cache[$key] = false; - } - } - - return $this->cache[$key] = true; - } - - return $this->cache[$key]; - } - - /** - * @param int[] $codes - * @param int[] $text - * @param int $offset - * @return int - */ - private function doIndexOf(array $codes, array $text, int $offset = 0): int - { - $len = count($text); - - for ($i = $offset, $last = count($codes) - $len; $i <= $last; $i++) { - $match = true; - - for ($j = 0; $j < $len; $j++) { - if ($codes[$i + $j] !== $text[$j]) { - $match = false; - break; - } - } - - if ($match) { - return $i; - } - } - - return -1; - } - - /** - * @param string|self|int[]|string[] $mask - * @param bool $left - * @param bool $right - * @return static - */ - private function doTrim($mask, bool $left, bool $right): self - { - if ($this->length === 0) { - return clone $this; - } - - $mask = self::resolveCodePoints($mask); - - if (empty($mask)) { - return clone $this; - } - - $codes = $this->codes; - - if ($left) { - while (in_array($codes[0], $mask, true)) { - array_shift($codes); - if (empty($codes)) { - return new static(); - } - } - } - - if ($right) { - $last = count($codes) - 1; - while (in_array($codes[$last], $mask, true)) { - array_pop($codes); - if (--$last < 0) { - return new static(); - } - } - } - - return new static($codes); - } - - - /** - * @param string|self|int[]|string[] $text - * @param int $mode - * @return array - */ - private static function resolveCodePoints($text, int $mode = self::KEEP_CASE): array - { - if ($text instanceof self) { - return $text->getMappedCodes($mode); - } - - if (is_string($text)) { - return self::getCodePointsFromString($text, $mode); - } - - if ($text && is_array($text) && is_int($text[0])) { - // assume code point array - return self::getMappedCodePoints($text, $mode); - } - - return []; - } - - /** - * @param self|string|int|string[]|int[] $text - * @param int $invalid - * @return int - */ - private static function resolveFirstCodePoint($text, int $invalid = -1): int - { - if ($text instanceof self) { - return $text->length === 0 ? $invalid : $text->codes[0]; - } - - if (is_array($text)) { - if (empty($text)) { - return $invalid; - } - $text = reset($text); - } - - if (is_string($text)) { - if (isset($text[4])) { - $text = substr($text, 0, 4); - } - return self::getCodePointsFromString($text)[0] ?? $invalid; - } - - if (is_int($text)) { - return self::isValidCodePoint($text) ? $text : $invalid; - } - - return $invalid; - } - - /** - * @param int $mode - * @return int[] - */ - private static function getMapByMode(int $mode): array - { - if (isset(self::$maps[$mode])) { - return self::$maps[$mode]; - } - - switch ($mode) { - case self::LOWER_CASE: - $file = 'lower'; - break; - case self::UPPER_CASE: - $file = 'upper'; - break; - case self::ASCII_CONV: - $file = 'ascii'; - break; - case self::FOLD_CASE: - $file = 'fold'; - break; - default: - return []; - } - - /** @noinspection PhpIncludeInspection */ - return self::$maps[$mode] = include(__DIR__ . "/../res/{$file}.php"); - } +class UnicodeString implements Countable, ArrayAccess, JsonSerializable { + + const KEEP_CASE = 0; + + const LOWER_CASE = 1; + + const UPPER_CASE = 2; + + const FOLD_CASE = 3; + + const ASCII_CONV = 4; + + /** + * @var int[] + */ + private $codes; + + /** + * @var string[]|null + */ + private $chars; + /** + * @var int + */ + private $length; + /** + * @var string|null + */ + private $str; + /** + * @var mixed[]|null + */ + private $cache; + + /** + * @var int[][] + */ + private static $maps = array(); + + /** + * @param int[] $codes + */ + private function __construct( array $codes = array() ) { + $this->codes = $codes; + $this->length = count( $codes ); + } + + /** + * @return int[] + */ + public function codePoints(): array { + return $this->codes; + } + + /** + * @return string[] + */ + public function chars(): array { + if ( $this->chars === null ) { + $this->chars = self::getCharsFromCodePoints( $this->codes ); + } + return $this->chars; + } + + /** + * @return int + */ + public function length(): int { + return $this->length; + } + + /** + * @return bool + */ + public function isEmpty(): bool { + return $this->length === 0; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return bool + */ + public function equals( $text, $ignoreCase = false ): bool { + return $this->compareTo( $text, $ignoreCase ) === 0; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return int + */ + public function compareTo( $text, $ignoreCase = false ): int { + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints( $text, $mode ); + + $length = count( $text ); + + if ( $length !== $this->length ) { + return $this->length <=> $length; + } + + return $this->getMappedCodes( $mode ) <=> $text; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return bool + */ + public function contains( $text, $ignoreCase = false ): bool { + return $this->indexOf( $text, 0, $ignoreCase ) !== -1; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return bool + */ + public function startsWith( $text, $ignoreCase = false ): bool { + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints( $text, $mode ); + + $len = count( $text ); + + if ( $len === 0 || $len > $this->length ) { + return false; + } + + return array_slice( $this->getMappedCodes( $mode ), 0, $len ) === $text; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @return bool + */ + public function endsWith( $text, $ignoreCase = false ): bool { + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints( $text, $mode ); + + if ( empty( $text ) ) { + return false; + } + + $codes = $this->getMappedCodes( $mode ); + + $offset = $this->length - count( $text ); + + if ( $offset < 0 ) { + return false; + } + + return array_slice( $codes, $offset ) === $text; + } + + /** + * @param string|self|int[]|string[] $text + * @param int $offset + * @param bool $ignoreCase + * @return int + */ + public function indexOf( $text, $offset = 0, $ignoreCase = false ): int { + if ( $offset < 0 ) { + $offset += $this->length; + } + if ( $offset < 0 || $offset >= $this->length ) { + return -1; + } + + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints( $text, $mode ); + + $len = count( $text ); + + if ( $len === 0 || $offset + $len > $this->length ) { + return -1; + } + + return $this->doIndexOf( $this->getMappedCodes( $mode ), $text, $offset ); + } + + /** + * @param string|self|int[]|string[] $text + * @param int $offset + * @param bool $ignoreCase + * @return int + */ + public function lastIndexOf( $text, $offset = 0, $ignoreCase = false ): int { + if ( $offset < 0 ) { + $start = $this->length + $offset; + if ( $start < 0 ) { + return -1; + } + $last = 0; + } else { + if ( $offset >= $this->length ) { + return -1; + } + $start = $this->length - 1; + $last = $offset; + } + + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $text = self::resolveCodePoints( $text, $mode ); + + $len = count( $text ); + + if ( $len === 0 ) { + return -1; + } + + if ( $offset < 0 ) { + if ( $len > $this->length ) { + return -1; + } + $start = min( $start, $this->length - $len ); + } elseif ( $offset + $len > $this->length ) { + return -1; + } + + $codes = $this->getMappedCodes( $mode ); + + for ( $i = $start; $i >= $last; $i-- ) { + $match = true; + + for ( $j = 0; $j < $len; $j++ ) { + if ( $codes[ $i + $j ] !== $text[ $j ] ) { + $match = false; + break; + } + } + + if ( $match ) { + return $i; + } + } + + return -1; + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @param bool $allowPrefixOnly If true the result can contain only the prefix + * @return $this + */ + public function ensurePrefix( $text, $ignoreCase = false, $allowPrefixOnly = true ): self { + $text = self::resolveCodePoints( $text ); + + $len = count( $text ); + + if ( $len === 0 ) { + return clone $this; + } + + if ( $this->length === 0 ) { + return new static( $text ); + } + + if ( $ignoreCase ) { + $prefix = self::getMappedCodePoints( $text, self::FOLD_CASE ); + } else { + $prefix = &$text; + } + + if ( $this->length === $len ) { + $part = $this->getMappedCodes( $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE ); + if ( $allowPrefixOnly && $part === $prefix ) { + return clone $this; + } + // Remove last element to avoid double check + array_pop( $part ); + } elseif ( $this->length < $len ) { + $part = $this->getMappedCodes( $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE ); + // Checks if this can be a suffix + if ( $allowPrefixOnly && ( array_slice( $prefix, 0, $this->length ) === $part ) ) { + $text = array_slice( $text, $this->length ); + return new static( array_merge( $this->codes, $text ) ); + } + } else { + $part = array_slice( $this->codes, 0, $len ); + if ( $ignoreCase ) { + $part = self::getMappedCodePoints( $part, self::FOLD_CASE ); + } + if ( $part === $prefix ) { + return clone $this; + } + // Remove last element to avoid double check + array_pop( $part ); + } + + $copy = $len; + + $part_len = count( $part ); + + while ( $part_len ) { + if ( $part === array_slice( $prefix, -$part_len ) ) { + $copy = $len - $part_len; + break; + } + array_pop( $part ); + --$part_len; + } + + if ( $copy === 0 ) { + return clone $this; + } + + if ( $copy < $len ) { + $text = array_slice( $text, 0, $copy ); + } + + return new static( array_merge( $text, $this->codes ) ); + } + + /** + * @param string|self|int[]|string[] $text + * @param bool $ignoreCase + * @param bool $allowSuffixOnly If true the result can contain only the suffix + * @return static + */ + public function ensureSuffix( $text, $ignoreCase = false, $allowSuffixOnly = true ): self { + $text = self::resolveCodePoints( $text ); + + $len = count( $text ); + + if ( $len === 0 ) { + return clone $this; + } + + if ( $this->length === 0 ) { + return new static( $text ); + } + + if ( $ignoreCase ) { + $suffix = self::getMappedCodePoints( $text, self::FOLD_CASE ); + } else { + $suffix = &$text; + } + + if ( $this->length === $len ) { + $part = $this->getMappedCodes( $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE ); + if ( $allowSuffixOnly && $part === $suffix ) { + return clone $this; + } + // Remove first element to avoid double check + array_shift( $part ); + } elseif ( $this->length < $len ) { + $part = $this->getMappedCodes( $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE ); + // Checks if this can be a prefix + if ( $allowSuffixOnly && ( array_slice( $suffix, -$this->length ) === $part ) ) { + $text = array_slice( $text, 0, $len - $this->length ); + return new static( array_merge( $text, $this->codes ) ); + } + } else { + $part = array_slice( $this->codes, -$len ); + if ( $ignoreCase ) { + $part = self::getMappedCodePoints( $part, self::FOLD_CASE ); + } + if ( $part === $suffix ) { + return clone $this; + } + // Remove first element to avoid double check + array_shift( $part ); + } + + $skip = 0; + + $part_len = count( $part ); + + while ( $part_len ) { + if ( $part === array_slice( $suffix, 0, $part_len ) ) { + $skip = $part_len; + break; + } + array_shift( $part ); + --$part_len; + } + + if ( $skip === $len ) { + return clone $this; + } + + if ( $skip ) { + array_splice( $text, 0, $skip ); + } + + return new static( array_merge( $this->codes, $text ) ); + } + + /** + * @param string|self|int[]|string[] $text + * @param int $mode + * @return static + */ + public function append( $text, $mode = self::KEEP_CASE ): self { + return new static( array_merge( $this->codes, self::resolveCodePoints( $text, $mode ) ) ); + } + + /** + * @param string|self|int[]|string[] $text + * @param int $mode + * @return static + */ + public function prepend( $text, $mode = self::KEEP_CASE ): self { + return new static( array_merge( self::resolveCodePoints( $text, $mode ), $this->codes ) ); + } + + /** + * @param string|self|int[]|string[] $text + * @param int $offset + * @param int $mode + * @return static + */ + public function insert( $text, $offset, $mode = self::KEEP_CASE ): self { + $codes = $this->codes; + + array_splice( $codes, $offset, 0, self::resolveCodePoints( $text, $mode ) ); + + return new static( $codes ); + } + + /** + * @param int $offset + * @param int|null $length + * @return static + */ + public function remove( $offset, $length = null ): self { + $codes = $this->codes; + + if ( $length === null ) { + array_splice( $codes, $offset ); + } else { + array_splice( $codes, $offset, $length ); + } + + return new static( $codes ); + } + + /** + * @param string|self|int[]|string[] $mask + * @return static + */ + public function trim( $mask = " \t\n\r\0\x0B" ): self { + return $this->doTrim( $mask, true, true ); + } + + /** + * @param string|self|int[]|string[] $mask + * @return static + */ + public function trimLeft( $mask = " \t\n\r\0\x0B" ): self { + return $this->doTrim( $mask, true, false ); + } + + /** + * @param string|self|int[]|string[] $mask + * @return static + */ + public function trimRight( $mask = " \t\n\r\0\x0B" ): self { + return $this->doTrim( $mask, false, true ); + } + + /** + * @return static + */ + public function reverse(): self { + return new static( array_reverse( $this->codes ) ); + } + + /** + * @param int $times + * @return static + */ + public function repeat( $times = 1 ): self { + if ( $times <= 1 ) { + return clone $this; + } + + $codes = array(); + + while ( $times-- ) { + $codes = array_merge( $codes, $this->codes ); + } + + return new static( $codes ); + } + + /** + * @param string|self|int[]|string[] $subject + * @param string|self|int[]|string[] $replace + * @param int $offset + * @param bool $ignoreCase + * @return static + */ + public function replace( $subject, $replace, $offset = 0, $ignoreCase = false ): self { + if ( $offset < 0 ) { + $offset += $this->length; + } + if ( $offset < 0 || $offset >= $this->length ) { + return clone $this; + } + + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $subject = self::resolveCodePoints( $subject, $mode ); + + $len = count( $subject ); + + if ( $len === 0 || $offset + $len > $this->length ) { + return clone $this; + } + + $offset = $this->doIndexOf( $this->getMappedCodes( $mode ), $subject, $offset ); + + if ( $offset === -1 ) { + return clone $this; + } + + $codes = $this->codes; + + array_splice( $codes, $offset, count( $subject ), self::resolveCodePoints( $replace ) ); + + return new static( $codes ); + } + + /** + * @param string|self|int[]|string[] $subject + * @param string|self|int[]|string[] $replace + * @param bool $ignoreCase + * @param int $offset + * @return static + */ + public function replaceAll( $subject, $replace, $offset = 0, $ignoreCase = false ): self { + if ( $offset < 0 ) { + $offset += $this->length; + } + if ( $offset < 0 || $offset >= $this->length ) { + return clone $this; + } + + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + + $subject = self::resolveCodePoints( $subject, $mode ); + + $len = count( $subject ); + + if ( $len === 0 || $offset + $len > $this->length ) { + return clone $this; + } + + $replace = self::resolveCodePoints( $replace ); + + $codes = $this->getMappedCodes( $mode ); + + $copy = $this->codes; + + $fix = count( $replace ) - $len; + + $t = 0; + + while ( ( $pos = $this->doIndexOf( $codes, $subject, $offset ) ) >= 0 ) { + array_splice( $copy, $pos + $t * $fix, $len, $replace ); + $offset = $pos + $len; + ++$t; + } + + return new static( $copy ); + } + + /** + * @param string|self|int[]|string[] $delimiter + * @param bool $ignoreCase + * @return array + */ + public function split( $delimiter = '', $ignoreCase = false ): array { + $mode = $ignoreCase ? self::FOLD_CASE : self::KEEP_CASE; + $delimiter = self::resolveCodePoints( $delimiter, $mode ); + $len = count( $delimiter ); + + $ret = array(); + + if ( $len === 0 ) { + foreach ( $this->codes as $code ) { + $ret[] = new static( array( $code ) ); + } + } else { + $codes = $this->getMappedCodes( $mode ); + + $offset = 0; + + while ( ( $pos = $this->doIndexOf( $codes, $delimiter, $offset ) ) >= 0 ) { + $ret[] = new static( array_slice( $this->codes, $offset, $pos - $offset ) ); + $offset = $pos + $len; + } + + $ret[] = new static( array_slice( $this->codes, $offset ) ); + } + + return $ret; + } + + /** + * @param int $start + * @param int|null $length + * @return static + */ + public function substring( $start, $length = null ): self { + return new static( array_slice( $this->codes, $start, $length ) ); + } + + /** + * @param int $size If negative then pad left otherwise pad right + * @param self|string|int $char A char or a code point + * @return static + */ + public function pad( $size, $char = 0x20 ): self { + return new static( array_pad( $this->codes, $size, self::resolveFirstCodePoint( $char, 0x20 ) ) ); + } + + /** + * @param int $size + * @param self|string|int $char + * @return static + */ + public function padLeft( $size, $char = 0x20 ): self { + if ( $size > 0 ) { + $size = -$size; + } + + return $this->pad( $size, $char ); + } + + /** + * @param int $size + * @param self|string|int $char + * @return static + */ + public function padRight( $size, $char = 0x20 ): self { + if ( $size < 0 ) { + $size = -$size; + } + + return $this->pad( $size, $char ); + } + + /** + * @return bool + */ + public function isLowerCase(): bool { + return $this->isCase( self::LOWER_CASE ); + } + + /** + * @return bool + */ + public function isUpperCase(): bool { + return $this->isCase( self::UPPER_CASE ); + } + + /** + * @return bool + */ + public function isAscii(): bool { + $key = 'i' . self::ASCII_CONV; + + if ( ! isset( $this->cache[ $key ] ) ) { + $ok = true; + + foreach ( $this->codes as $code ) { + if ( $code >= 0x80 ) { + $ok = false; + break; + } + } + + $this->cache[ $key ] = $ok; + } + + return $this->cache[ $key ]; + } + + /** + * Convert all chars to lower case (where possible) + * + * @return static + */ + public function toLower(): self { + if ( $this->cache[ 'i' . self::LOWER_CASE ] ?? false ) { + return clone $this; + } + return new static( $this->getMappedCodes( self::LOWER_CASE ) ); + } + + /** + * Convert all chars to upper case (where possible) + * + * @return static + */ + public function toUpper(): self { + if ( $this->cache[ 'i' . self::UPPER_CASE ] ?? false ) { + return clone $this; + } + return new static( $this->getMappedCodes( self::UPPER_CASE ) ); + } + + /** + * Converts all chars to their ASCII equivalent (if any) + * + * @return static + */ + public function toAscii(): self { + if ( $this->cache[ 'i' . self::ASCII_CONV ] ?? false ) { + return clone $this; + } + return new static( $this->getMappedCodes( self::ASCII_CONV ) ); + } + + /** + * @param int $index + * @return string + */ + public function charAt( $index ): string { + // Allow negative index + if ( $index < 0 && $index + $this->length >= 0 ) { + $index += $this->length; + } + + if ( $index < 0 || $index >= $this->length ) { + return ''; + } + + return $this->chars()[ $index ]; + } + + /** + * @param int $index + * @return int + */ + public function codePointAt( $index ): int { + // Allow negative index + if ( $index < 0 && $index + $this->length >= 0 ) { + $index += $this->length; + } + + if ( $index < 0 || $index >= $this->length ) { + return -1; + } + + return $this->codes[ $index ]; + } + + /** + * @param int $offset + * @return int + */ + public function __invoke( int $offset ): int { + if ( $offset < 0 ) { + if ( $offset + $this->length < 0 ) { + throw new OutOfBoundsException( "Undefined offset: {$offset}" ); + } + $offset += $this->length; + } elseif ( $offset >= $this->length ) { + throw new OutOfBoundsException( "Undefined offset: {$offset}" ); + } + + return $this->codes[ $offset ]; + } + + /** + * @inheritDoc + */ + public function offsetExists( $offset ): bool { + // Allow negative index + if ( $offset < 0 ) { + $offset += $this->length; + } + + return isset( $this->codes[ $offset ] ); + } + + /** + * @inheritDoc + */ + public function offsetGet( $offset ): string { + if ( $offset < 0 ) { + if ( $offset + $this->length < 0 ) { + throw new OutOfBoundsException( "Undefined offset: {$offset}" ); + } + $offset += $this->length; + } elseif ( $offset >= $this->length ) { + throw new OutOfBoundsException( "Undefined offset: {$offset}" ); + } + + return $this->chars()[ $offset ]; + } + + /** + * @inheritDoc + */ + #[\ReturnTypeWillChange] + public function offsetSet( $offset, $value ) { + // Allow negative index + if ( $offset < 0 ) { + $offset += $this->length; + } + + if ( ! isset( $this->codes[ $offset ] ) ) { + return; + } + + $value = self::resolveFirstCodePoint( $value ); + if ( $value === -1 ) { + return; + } + + if ( $value === $this->codes[ $offset ] ) { + // Same value, nothing to do + return; + } + + $this->codes[ $offset ] = $value; + + // Clear cache + $this->str = null; + $this->cache = null; + if ( $this->chars ) { + $this->chars[ $offset ] = self::getCharFromCodePoint( $value ); + } + } + + /** + * @inheritDoc + */ + #[\ReturnTypeWillChange] + public function offsetUnset( $offset ) { + throw new RuntimeException( 'Invalid operation' ); + } + + /** + * @inheritDoc + */ + public function count(): int { + return $this->length; + } + + /** + * @return string + */ + public function __toString(): string { + if ( $this->str === null ) { + $this->str = self::getStringFromCodePoints( $this->codes ); + } + + return $this->str; + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): string { + return $this->__toString(); + } + + public function __serialize(): array { + return array( + 'value' => $this->__toString(), + ); + } + + public function __unserialize( array $data ) { + $this->str = $data['value']; + $this->codes = self::getCodePointsFromString( $this->str ); + $this->length = count( $this->codes ); + } + + /** + * Creates an unicode string instance from raw string + * + * @param string $string + * @param string|null $encoding Defaults to UTF-8 + * @param int $mode + * @return static + * @throws InvalidStringException + */ + public static function from( $string, $encoding = null, $mode = self::KEEP_CASE ): self { + if ( $encoding !== null && strcasecmp( $encoding, 'UTF-8' ) !== 0 ) { + if ( false === $string = @iconv( $encoding, 'UTF-8', $string ) ) { + throw new UnicodeException( "Could not convert string from '$encoding' encoding to UTF-8 encoding" ); + } + } + + $instance = new static( self::getCodePointsFromString( $string, $mode ) ); + if ( $mode === self::KEEP_CASE ) { + $instance->str = $string; + } + return $instance; + } + + /** + * Creates an unicode string instance from code points + * + * @param int[] $codes + * @param int $mode + * @return static + * @throws InvalidCodePointException + */ + public static function fromCodePoints( $codes, $mode = self::KEEP_CASE ): self { + $map = self::getMapByMode( $mode ); + + foreach ( $codes as &$code ) { + if ( ! is_int( $codes ) || ! self::isValidCodePoint( $code ) ) { + throw new InvalidCodePointException( $code ); + } else { + $code = $map[ $code ] ?? $code; + } + } + + return new static( array_values( $codes ) ); + } + + /** + * Converts the code point to corresponding char + * + * @param int $code + * @return string The char or an empty string if code point is invalid + */ + public static function getCharFromCodePoint( $code ): string { + if ( $code < 0 ) { + return ''; + } + + if ( $code < 0x80 ) { + return chr( $code ); + } + + if ( $code < 0x800 ) { + return chr( ( $code >> 6 ) + 0xC0 ) . chr( ( $code & 0x3F ) + 0x80 ); + } + + if ( $code >= 0xD800 && $code <= 0xDFFF ) { + /* + The definition of UTF-8 prohibits encoding character numbers between + U+D800 and U+DFFF, which are reserved for use with the UTF-16 + encoding form (as surrogate pairs) and do not directly represent characters. + */ + return ''; + } + + if ( $code <= 0xFFFF ) { + return chr( ( $code >> 12 ) + 0xE0 ) . + chr( ( ( $code >> 6 ) & 0x3F ) + 0x80 ) . + chr( ( $code & 0x3F ) + 0x80 ); + } + + if ( $code <= 0x10FFFF ) { + return chr( ( $code >> 18 ) + 0xF0 ) . + chr( ( ( $code >> 12 ) & 0x3F ) + 0x80 ) . + chr( ( ( $code >> 6 ) & 0x3F ) + 0x80 ) . + chr( ( $code & 0x3F ) + 0x80 ); + } + + /* + Restricted the range of characters to 0000-10FFFF (the UTF-16 accessible range). + */ + + return ''; + } + + /** + * Convert a string to a code point array + * + * @param string $str + * @param int $mode + * @return array + * @throws InvalidStringException + */ + public static function getCodePointsFromString( $str, $mode = self::KEEP_CASE ): array { + // 0x00-0x7F + // 0xC2-0xDF 0x80-0xBF + // 0xE0-0xE0 0xA0-0xBF 0x80-0xBF + // 0xE1-0xEC 0x80-0xBF 0x80-0xBF + // 0xED-0xED 0x80-0x9F 0x80-0xBF + // 0xEE-0xEF 0x80-0xBF 0x80-0xBF + // 0xF0-0xF0 0x90-0xBF 0x80-0xBF 0x80-0xBF + // 0xF1-0xF3 0x80-0xBF 0x80-0xBF 0x80-0xBF + // 0xF4-0xF4 0x80-0x8F 0x80-0xBF 0x80-0xBF + + $codes = array(); + $length = strlen( $str ); + $mode = self::getMapByMode( $mode ); + + $i = 0; + while ( $i < $length ) { + $ord0 = ord( $str[ $i++ ] ); + + if ( $ord0 < 0x80 ) { + $codes[] = $mode[ $ord0 ] ?? $ord0; + continue; + } + + if ( $i === $length || $ord0 < 0xC2 || $ord0 > 0xF4 ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord1 = ord( $str[ $i++ ] ); + + if ( $ord0 < 0xE0 ) { + if ( $ord1 < 0x80 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord1 = ( $ord0 - 0xC0 ) * 64 + $ord1 - 0x80; + $codes[] = $mode[ $ord1 ] ?? $ord1; + + continue; + } + + if ( $i === $length ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord2 = ord( $str[ $i++ ] ); + + if ( $ord0 < 0xF0 ) { + if ( $ord0 === 0xE0 ) { + if ( $ord1 < 0xA0 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 2 ); + } + } elseif ( $ord0 === 0xED ) { + if ( $ord1 < 0x80 || $ord1 >= 0xA0 ) { + throw new InvalidStringException( $str, $i - 2 ); + } + } elseif ( $ord1 < 0x80 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 2 ); + } + + if ( $ord2 < 0x80 || $ord2 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord2 = ( $ord0 - 0xE0 ) * 0x1000 + ( $ord1 - 0x80 ) * 64 + $ord2 - 0x80; + $codes[] = $mode[ $ord2 ] ?? $ord2; + + continue; + } + + if ( $i === $length ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord3 = ord( $str[ $i++ ] ); + + if ( $ord0 < 0xF5 ) { + if ( $ord0 === 0xF0 ) { + if ( $ord1 < 0x90 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 3 ); + } + } elseif ( $ord0 === 0xF4 ) { + if ( $ord1 < 0x80 || $ord1 >= 0x90 ) { + throw new InvalidStringException( $str, $i - 3 ); + } + } elseif ( $ord1 < 0x80 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 3 ); + } + + if ( $ord2 < 0x80 || $ord2 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 2 ); + } + + if ( $ord3 < 0x80 || $ord3 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord3 = ( $ord0 - 0xF0 ) * 0x40000 + ( $ord1 - 0x80 ) * 0x1000 + ( $ord2 - 0x80 ) * 64 + $ord3 - 0x80; + $codes[] = $mode[ $ord3 ] ?? $ord3; + + continue; + } + + throw new InvalidStringException( $str, $i - 1 ); + } + + return $codes; + } + + /** + * @param string $str + * @return iterable + * + * The key represents the current char index + * Value is a two element array + * - first element is an integer representing the code point + * - second element is an array of integers (length 1 to 4) representing bytes + */ + public static function walkString( $str ) { + $i = 0; + $length = strlen( $str ); + + while ( $i < $length ) { + $index = $i; + + $ord0 = ord( $str[ $i++ ] ); + + if ( $ord0 < 0x80 ) { + yield $index => array( + $ord0, + array( $ord0 ), + ); + continue; + } + + if ( $i === $length || $ord0 < 0xC2 || $ord0 > 0xF4 ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord1 = ord( $str[ $i++ ] ); + + if ( $ord0 < 0xE0 ) { + if ( $ord1 < 0x80 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + yield $index => array( + ( $ord0 - 0xC0 ) * 64 + $ord1 - 0x80, + array( $ord0, $ord1 ), + ); + + continue; + } + + if ( $i === $length ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord2 = ord( $str[ $i++ ] ); + + if ( $ord0 < 0xF0 ) { + if ( $ord0 === 0xE0 ) { + if ( $ord1 < 0xA0 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 2 ); + } + } elseif ( $ord0 === 0xED ) { + if ( $ord1 < 0x80 || $ord1 >= 0xA0 ) { + throw new InvalidStringException( $str, $i - 2 ); + } + } elseif ( $ord1 < 0x80 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 2 ); + } + + if ( $ord2 < 0x80 || $ord2 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + yield $index => array( + ( $ord0 - 0xE0 ) * 0x1000 + ( $ord1 - 0x80 ) * 64 + $ord2 - 0x80, + array( $ord0, $ord1, $ord2 ), + ); + + continue; + } + + if ( $i === $length ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + $ord3 = ord( $str[ $i++ ] ); + + if ( $ord0 < 0xF5 ) { + if ( $ord0 === 0xF0 ) { + if ( $ord1 < 0x90 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 3 ); + } + } elseif ( $ord0 === 0xF4 ) { + if ( $ord1 < 0x80 || $ord1 >= 0x90 ) { + throw new InvalidStringException( $str, $i - 3 ); + } + } elseif ( $ord1 < 0x80 || $ord1 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 3 ); + } + + if ( $ord2 < 0x80 || $ord2 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 2 ); + } + + if ( $ord3 < 0x80 || $ord3 >= 0xC0 ) { + throw new InvalidStringException( $str, $i - 1 ); + } + + yield $index => array( + ( $ord0 - 0xF0 ) * 0x40000 + ( $ord1 - 0x80 ) * 0x1000 + ( $ord2 - 0x80 ) * 64 + $ord3 - 0x80, + array( $ord0, $ord1, $ord2, $ord3 ), + ); + + continue; + } + + throw new InvalidStringException( $str, $i - 1 ); + } + } + + /** + * Converts each code point to a char + * + * @param array $codes + * @param int $mode + * @return array + * @throws InvalidCodePointException + */ + public static function getCharsFromCodePoints( $codes, $mode = self::KEEP_CASE ): array { + $mode = self::getMapByMode( $mode ); + + foreach ( $codes as &$code ) { + $char = self::getCharFromCodePoint( $mode[ $code ] ?? $code ); + if ( $char === '' ) { + throw new InvalidCodePointException( $code ); + } else { + $code = $char; + } + } + + return $codes; + } + + /** + * @param string $str + * @param int $mode + * @return string[] + */ + public static function getCharsFromString( $str, $mode = self::KEEP_CASE ): array { + return self::getCharsFromCodePoints( self::getCodePointsFromString( $str ), $mode ); + } + + /** + * Converts all code points to chars and returns the string + * Invalid code points are ignored + * + * @param array $codes + * @param int $mode + * @return string + */ + public static function getStringFromCodePoints( $codes, $mode = self::KEEP_CASE ): string { + $str = ''; + + $mode = self::getMapByMode( $mode ); + + foreach ( $codes as $code ) { + if ( isset( $mode[ $code ] ) ) { + $code = $mode[ $code ]; + } + + if ( $code < 0x80 ) { + $str .= chr( $code ); + continue; + } + + if ( $code < 0x800 ) { + $str .= chr( ( $code >> 6 ) + 0xC0 ) . chr( ( $code & 0x3F ) + 0x80 ); + continue; + } + + if ( $code >= 0xD800 && $code <= 0xDFFF ) { + continue; + } + + if ( $code <= 0xFFFF ) { + $str .= + chr( ( $code >> 12 ) + 0xE0 ) . + chr( ( ( $code >> 6 ) & 0x3F ) + 0x80 ) . + chr( ( $code & 0x3F ) + 0x80 ); + continue; + } + + if ( $code <= 0x10FFFF ) { + $str .= + chr( ( $code >> 18 ) + 0xF0 ) . + chr( ( ( $code >> 12 ) & 0x3F ) + 0x80 ) . + chr( ( ( $code >> 6 ) & 0x3F ) + 0x80 ) . + chr( ( $code & 0x3F ) + 0x80 ); + } + } + + return $str; + } + + /** + * @param array $codes + * @param int $mode + * @return array + */ + public static function getMappedCodePoints( $codes, $mode ): array { + if ( $mode === self::KEEP_CASE ) { + return $codes; + } + + $mode = self::getMapByMode( $mode ); + + if ( empty( $mode ) ) { + return $codes; + } + + foreach ( $codes as &$code ) { + $code = $mode[ $code ] ?? $code; + } + + return $codes; + } + + /** + * Checks if a code point is valid + * + * @param int $code + * @return bool + */ + public static function isValidCodePoint( $code ): bool { + if ( $code < 0 || $code > 0x10FFFF ) { + return false; + } + + return $code < 0xD800 || $code > 0xDFFF; + } + + /** + * @param int $mode + * @return int[] + */ + private function getMappedCodes( int $mode ): array { + if ( $mode === self::KEEP_CASE || ( $this->cache[ 'i' . $mode ] ?? false ) ) { + return $this->codes; + } + + $key = 'm' . $mode; + + if ( ! isset( $this->cache[ $key ] ) ) { + $this->cache[ $key ] = self::getMappedCodePoints( $this->codes, $mode ); + } + + return $this->cache[ $key ]; + } + + /** + * @param int $mode + * @return bool + */ + private function isCase( int $mode ): bool { + $key = 'i' . $mode; + + if ( ! isset( $this->cache[ $key ] ) ) { + $list = self::getMapByMode( $mode ); + foreach ( $this->codes as $code ) { + if ( isset( $list[ $code ] ) ) { + return $this->cache[ $key ] = false; + } + } + + return $this->cache[ $key ] = true; + } + + return $this->cache[ $key ]; + } + + /** + * @param int[] $codes + * @param int[] $text + * @param int $offset + * @return int + */ + private function doIndexOf( array $codes, array $text, int $offset = 0 ): int { + $len = count( $text ); + + for ( $i = $offset, $last = count( $codes ) - $len; $i <= $last; $i++ ) { + $match = true; + + for ( $j = 0; $j < $len; $j++ ) { + if ( $codes[ $i + $j ] !== $text[ $j ] ) { + $match = false; + break; + } + } + + if ( $match ) { + return $i; + } + } + + return -1; + } + + /** + * @param string|self|int[]|string[] $mask + * @param bool $left + * @param bool $right + * @return static + */ + private function doTrim( $mask, bool $left, bool $right ): self { + if ( $this->length === 0 ) { + return clone $this; + } + + $mask = self::resolveCodePoints( $mask ); + + if ( empty( $mask ) ) { + return clone $this; + } + + $codes = $this->codes; + + if ( $left ) { + while ( in_array( $codes[0], $mask, true ) ) { + array_shift( $codes ); + if ( empty( $codes ) ) { + return new static(); + } + } + } + + if ( $right ) { + $last = count( $codes ) - 1; + while ( in_array( $codes[ $last ], $mask, true ) ) { + array_pop( $codes ); + if ( --$last < 0 ) { + return new static(); + } + } + } + + return new static( $codes ); + } + + + /** + * @param string|self|int[]|string[] $text + * @param int $mode + * @return array + */ + private static function resolveCodePoints( $text, int $mode = self::KEEP_CASE ): array { + if ( $text instanceof self ) { + return $text->getMappedCodes( $mode ); + } + + if ( is_string( $text ) ) { + return self::getCodePointsFromString( $text, $mode ); + } + + if ( $text && is_array( $text ) && is_int( $text[0] ) ) { + // assume code point array + return self::getMappedCodePoints( $text, $mode ); + } + + return array(); + } + + /** + * @param self|string|int|string[]|int[] $text + * @param int $invalid + * @return int + */ + private static function resolveFirstCodePoint( $text, int $invalid = -1 ): int { + if ( $text instanceof self ) { + return $text->length === 0 ? $invalid : $text->codes[0]; + } + + if ( is_array( $text ) ) { + if ( empty( $text ) ) { + return $invalid; + } + $text = reset( $text ); + } + + if ( is_string( $text ) ) { + if ( isset( $text[4] ) ) { + $text = substr( $text, 0, 4 ); + } + return self::getCodePointsFromString( $text )[0] ?? $invalid; + } + + if ( is_int( $text ) ) { + return self::isValidCodePoint( $text ) ? $text : $invalid; + } + + return $invalid; + } + + /** + * @param int $mode + * @return int[] + */ + private static function getMapByMode( int $mode ): array { + if ( isset( self::$maps[ $mode ] ) ) { + return self::$maps[ $mode ]; + } + + switch ( $mode ) { + case self::LOWER_CASE: + $file = 'lower'; + break; + case self::UPPER_CASE: + $file = 'upper'; + break; + case self::ASCII_CONV: + $file = 'ascii'; + break; + case self::FOLD_CASE: + $file = 'fold'; + break; + default: + return array(); + } + + /** @noinspection PhpIncludeInspection */ + return self::$maps[ $mode ] = include __DIR__ . "/../res/{$file}.php"; + } } diff --git a/src/opis/uri/autoload.php b/src/opis/uri/autoload.php index 8aa754c5..6ebeb341 100644 --- a/src/opis/uri/autoload.php +++ b/src/opis/uri/autoload.php @@ -1,5 +1,6 @@ = $n && $input[$i] < $m) { - $m = $input[$i]; - } - } - - if (($m - $n) > intdiv(self::MAX_INT - $delta, $handled + 1)) { - throw new PunycodeException("Punycode overflow"); - } - - $delta += ($m - $n) * ($handled + 1); - - $n = $m; - - for ($i = 0; $i < $input_len; $i++) { - if ($input[$i] < $n && (++$delta === 0)) { - throw new PunycodeException("Punycode overflow"); - } - - if ($input[$i] === $n) { - $q = $delta; - for ($k = self::BASE; ; $k += self::BASE) { - $t = self::threshold($k, $bias); - if ($q < $t) { - break; - } +final class Punycode { - $base_minus_t = self::BASE - $t; + const BASE = 36; + const TMIN = 1; + const TMAX = 26; + const SKEW = 38; + const DAMP = 700; + const INITIAL_BIAS = 72; + const INITIAL_N = 0x80; + const PREFIX = 'xn--'; + const PREFIX_LEN = 4; + const DELIMITER = 0x2D; + const MAX_INT = 0x7FFFFFFF; + const NON_ASCII = '#[^\0-\x7E]#'; - $q -= $t; + public static function encode( string $input ): string { + return implode( '.', array_map( array( self::class, 'encodePart' ), explode( '.', $input ) ) ); + } - $output[] = self::encodeDigit($t + ($q % $base_minus_t)); + public static function decode( string $input ): string { + return implode( '.', array_map( array( self::class, 'decodePart' ), explode( '.', $input ) ) ); + } + + public static function normalize( string $input ): string { + return implode( '.', array_map( array( self::class, 'normalizePart' ), explode( '.', $input ) ) ); + } - $q = intdiv($q, $base_minus_t); - } + public static function encodePart( string $input ): string { + if ( ! preg_match( self::NON_ASCII, $input ) ) { + return $input; + } + + $input = UnicodeString::getCodePointsFromString( $input, UnicodeString::LOWER_CASE ); + $input_len = count( $input ); + + $output = array_filter( + $input, + static function ( int $code ): bool { + return $code < 0x80; + } + ); + + if ( $output ) { + $output = array_values( $output ); + } + + $delta = 0; + $n = self::INITIAL_N; + $bias = self::INITIAL_BIAS; + + $handled = $basic_length = count( $output ); + + if ( $basic_length ) { + $output[] = self::DELIMITER; + } + + while ( $handled < $input_len ) { + $m = self::MAX_INT; + + for ( $i = 0; $i < $input_len; $i++ ) { + if ( $input[ $i ] >= $n && $input[ $i ] < $m ) { + $m = $input[ $i ]; + } + } + + if ( ( $m - $n ) > intdiv( self::MAX_INT - $delta, $handled + 1 ) ) { + throw new PunycodeException( 'Punycode overflow' ); + } + + $delta += ( $m - $n ) * ( $handled + 1 ); + + $n = $m; + + for ( $i = 0; $i < $input_len; $i++ ) { + if ( $input[ $i ] < $n && ( ++$delta === 0 ) ) { + throw new PunycodeException( 'Punycode overflow' ); + } - $output[] = self::encodeDigit($q); + if ( $input[ $i ] === $n ) { + $q = $delta; + for ( $k = self::BASE; ; $k += self::BASE ) { + $t = self::threshold( $k, $bias ); + if ( $q < $t ) { + break; + } - $bias = self::adapt($delta, $handled + 1, $handled === $basic_length); - $delta = 0; - $handled++; - } - } + $base_minus_t = self::BASE - $t; - $delta++; $n++; - } + $q -= $t; - return self::PREFIX . UnicodeString::getStringFromCodePoints($output); - } + $output[] = self::encodeDigit( $t + ( $q % $base_minus_t ) ); - public static function decodePart(string $input): string - { - if (stripos($input, self::PREFIX) !== 0) { - return $input; - } + $q = intdiv( $q, $base_minus_t ); + } - $input = UnicodeString::getCodePointsFromString(substr($input, self::PREFIX_LEN), UnicodeString::LOWER_CASE); - $input_len = count($input); + $output[] = self::encodeDigit( $q ); - $pos = array_keys($input, self::DELIMITER, true); - if ($pos) { - $pos = end($pos); - } else { - $pos = -1; - } + $bias = self::adapt( $delta, $handled + 1, $handled === $basic_length ); + $delta = 0; + ++$handled; + } + } - /** @var int $pos */ + ++$delta; + ++$n; + } - if ($pos === -1) { - $output = []; - $pos = $output_len = 0; - } else { - $output = array_slice($input, 0, ++$pos); - $output_len = $pos; - for ($i = 0; $i < $pos; $i++) { - if ($output[$i] >= 0x80) { - throw new PunycodeException("Non-basic code point is not allowed: {$output[$i]}"); - } - } - } + return self::PREFIX . UnicodeString::getStringFromCodePoints( $output ); + } - $i = 0; - $n = self::INITIAL_N; - $bias = self::INITIAL_BIAS; + public static function decodePart( string $input ): string { + if ( stripos( $input, self::PREFIX ) !== 0 ) { + return $input; + } - while ($pos < $input_len) { - $old_i = $i; + $input = UnicodeString::getCodePointsFromString( substr( $input, self::PREFIX_LEN ), UnicodeString::LOWER_CASE ); + $input_len = count( $input ); - for ($w = 1, $k = self::BASE; ; $k += self::BASE) { - if ($pos >= $input_len) { - throw new PunycodeException("Punycode bad input"); - } + $pos = array_keys( $input, self::DELIMITER, true ); + if ( $pos ) { + $pos = end( $pos ); + } else { + $pos = -1; + } - $digit = self::decodeDigit($input[$pos++]); + /** @var int $pos */ - if ($digit >= self::BASE || $digit > intdiv(self::MAX_INT - $i, $w)) { - throw new PunycodeException("Punycode overflow"); - } + if ( $pos === -1 ) { + $output = array(); + $pos = $output_len = 0; + } else { + $output = array_slice( $input, 0, ++$pos ); + $output_len = $pos; + for ( $i = 0; $i < $pos; $i++ ) { + if ( $output[ $i ] >= 0x80 ) { + throw new PunycodeException( "Non-basic code point is not allowed: {$output[$i]}" ); + } + } + } - $i += $digit * $w; + $i = 0; + $n = self::INITIAL_N; + $bias = self::INITIAL_BIAS; - $t = self::threshold($k, $bias); - if ($digit < $t) { - break; - } + while ( $pos < $input_len ) { + $old_i = $i; - $t = self::BASE - $t; + for ( $w = 1, $k = self::BASE; ; $k += self::BASE ) { + if ( $pos >= $input_len ) { + throw new PunycodeException( 'Punycode bad input' ); + } - if ($w > intdiv(self::MAX_INT, $t)) { - throw new PunycodeException("Punycode overflow"); - } + $digit = self::decodeDigit( $input[ $pos++ ] ); - $w *= $t; - } + if ( $digit >= self::BASE || $digit > intdiv( self::MAX_INT - $i, $w ) ) { + throw new PunycodeException( 'Punycode overflow' ); + } - $output_len++; + $i += $digit * $w; - if (intdiv($i, $output_len) > self::MAX_INT - $n) { - throw new PunycodeException("Punycode overflow"); - } + $t = self::threshold( $k, $bias ); + if ( $digit < $t ) { + break; + } - $n += intdiv($i, $output_len); + $t = self::BASE - $t; - $bias = self::adapt($i - $old_i, $output_len, $old_i === 0); + if ( $w > intdiv( self::MAX_INT, $t ) ) { + throw new PunycodeException( 'Punycode overflow' ); + } - $i %= $output_len; + $w *= $t; + } - array_splice($output, $i, 0, $n); + ++$output_len; - $i++; - } + if ( intdiv( $i, $output_len ) > self::MAX_INT - $n ) { + throw new PunycodeException( 'Punycode overflow' ); + } - return UnicodeString::getStringFromCodePoints($output); - } + $n += intdiv( $i, $output_len ); - public static function normalizePart(string $input): string - { - $input = strtolower($input); + $bias = self::adapt( $i - $old_i, $output_len, $old_i === 0 ); - if (strpos($input, self::DELIMITER) === 0) { - self::decodePart($input); // just validate - return $input; - } + $i %= $output_len; - return self::encodePart($input); - } + array_splice( $output, $i, 0, $n ); - private static function encodeDigit(int $digit): int - { - return $digit + 0x16 + ($digit < 0x1A ? 0x4B: 0x00); - } + ++$i; + } - private static function decodeDigit(int $code): int - { - if ($code < 0x3A) { - return $code - 0x16; - } - if ($code < 0x5B) { - return $code - 0x41; - } - if ($code < 0x7B) { - return $code - 0x61; - } + return UnicodeString::getStringFromCodePoints( $output ); + } - return self::BASE; - } + public static function normalizePart( string $input ): string { + $input = strtolower( $input ); - private static function threshold(int $k, int $bias): int - { - $d = $k - $bias; + if ( strpos( $input, self::DELIMITER ) === 0 ) { + self::decodePart( $input ); // just validate + return $input; + } - if ($d <= self::TMIN) { - return self::TMIN; - } + return self::encodePart( $input ); + } - if ($d >= self::TMAX) { - return self::TMAX; - } + private static function encodeDigit( int $digit ): int { + return $digit + 0x16 + ( $digit < 0x1A ? 0x4B : 0x00 ); + } - return $d; - } + private static function decodeDigit( int $code ): int { + if ( $code < 0x3A ) { + return $code - 0x16; + } + if ( $code < 0x5B ) { + return $code - 0x41; + } + if ( $code < 0x7B ) { + return $code - 0x61; + } - private static function adapt(int $delta, int $num_points, bool $first_time = false): int - { - $delta = intdiv($delta, $first_time ? self::DAMP : 2); - $delta += intdiv($delta, $num_points); + return self::BASE; + } - $k = 0; - $base_tmin_diff = self::BASE - self::TMIN; - $lim = $base_tmin_diff * self::TMAX / 2; + private static function threshold( int $k, int $bias ): int { + $d = $k - $bias; - while ($delta > $lim) { - $delta = intdiv($delta, $base_tmin_diff); - $k += self::BASE; - } + if ( $d <= self::TMIN ) { + return self::TMIN; + } - $k += intdiv(($base_tmin_diff + 1) * $delta, $delta + self::SKEW); + if ( $d >= self::TMAX ) { + return self::TMAX; + } - return $k; - } + return $d; + } + + private static function adapt( int $delta, int $num_points, bool $first_time = false ): int { + $delta = intdiv( $delta, $first_time ? self::DAMP : 2 ); + $delta += intdiv( $delta, $num_points ); + + $k = 0; + $base_tmin_diff = self::BASE - self::TMIN; + $lim = $base_tmin_diff * self::TMAX / 2; + + while ( $delta > $lim ) { + $delta = intdiv( $delta, $base_tmin_diff ); + $k += self::BASE; + } + + $k += intdiv( ( $base_tmin_diff + 1 ) * $delta, $delta + self::SKEW ); + + return $k; + } } diff --git a/src/opis/uri/src/PunycodeException.php b/src/opis/uri/src/PunycodeException.php index 6eb1c129..dde604ee 100644 --- a/src/opis/uri/src/PunycodeException.php +++ b/src/opis/uri/src/PunycodeException.php @@ -1,5 +1,6 @@ [^:]+)(?::(?.*))?$`'; - - const HOST_LABEL_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-]+)*$`i'; - - const AUTHORITY_REGEX = '`^(?:(?[^@]+)\@)?(?(\[[a-f0-9:]+\]|[^:]+))(?::(?\d+))?$`i'; - - const PATH_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-._~!$&\'()*+,;=:@/]+)*$`i'; - - const QUERY_OR_FRAGMENT_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-._~!$&\'"()\[\]*+,;=:@?/%]+)*$`i'; - - /** - * @var mixed[] - */ - protected $components; - - /** - * @var string|null - */ - protected $str; - - /** - * @param array $components An array of normalized components - */ - public function __construct(array $components) - { - $this->components = $components + [ - 'scheme' => null, - 'user' => null, - 'pass' => null, - 'host' => null, - 'port' => null, - 'path' => null, - 'query' => null, - 'fragment' => null, - ]; - } - - /** - * @return string|null - */ - public function scheme() - { - return $this->components['scheme']; - } - - /** - * @return string|null - */ - public function user() - { - return $this->components['user']; - } - - /** - * @return string|null - */ - public function pass() - { - return $this->components['pass']; - } - - /** - * @return string|null - */ - public function userInfo() - { - if ($this->components['user'] === null) { - return null; - } - - if ($this->components['pass'] === null) { - return $this->components['user']; - } - - return $this->components['user'] . ':' . $this->components['pass']; - } - - /** - * @return string|null - */ - public function host() - { - return $this->components['host']; - } - - /** - * @return int|null - */ - public function port() - { - return $this->components['port']; - } - - /** - * @return string|null - */ - public function authority() - { - if ($this->components['host'] === null) { - return null; - } - - $authority = $this->userInfo(); - if ($authority !== null) { - $authority .= '@'; - } - - $authority .= $this->components['host']; - - if ($this->components['port'] !== null) { - $authority .= ':' . $this->components['port']; - } - - return $authority; - } - - /** - * @return string|null - */ - public function path() - { - return $this->components['path']; - } - - /** - * @return string|null - */ - public function query() - { - return $this->components['query']; - } - - /** - * @return string|null - */ - public function fragment() - { - return $this->components['fragment']; - } - - /** - * @return array|null[] - */ - public function components(): array - { - return $this->components; - } - - /** - * @return bool - */ - public function isAbsolute(): bool - { - return $this->components['scheme'] !== null; - } - - /** - * Use this URI as base to resolve the reference - * @param static|string|array $ref - * @param bool $normalize - * @return $this|null - */ - public function resolveRef($ref, $normalize = false) - { - $ref = self::resolveComponents($ref); - if ($ref === null) { - return $this; - } - - return new static(self::mergeComponents($ref, $this->components, $normalize)); - } - - /** - * Resolve this URI reference using a base URI - * @param static|string|array $base - * @param bool $normalize - * @return static - */ - public function resolve($base, $normalize = false): self - { - if ($this->isAbsolute()) { - return $this; - } - - $base = self::resolveComponents($base); - - if ($base === null) { - return $this; - } - - return new static(self::mergeComponents($this->components, $base, $normalize)); - } - - /** - * @return string - */ - public function __toString(): string - { - if ($this->str !== null) { - return $this->str; - } - - $str = ''; - - if ($this->components['scheme'] !== null) { - $str .= $this->components['scheme'] . ':'; - } - - if ($this->components['host'] !== null) { - $str .= '//' . $this->authority(); - } - - $str .= $this->components['path']; - - if ($this->components['query'] !== null) { - $str .= '?' . $this->components['query']; - } - - if ($this->components['fragment'] !== null) { - $str .= '#' . $this->components['fragment']; - } - - return $this->str = $str; - } - - /** - * @param string $uri - * @param bool $normalize - * @return static|null - */ - public static function create($uri, $normalize = false) - { - $comp = self::parseComponents($uri); - if (!$comp) { - return null; - } - - if ($normalize) { - $comp = self::normalizeComponents($comp); - } - - return new static($comp); - } - - /** - * Checks if the scheme contains valid chars - * @param string $scheme - * @return bool - */ - public static function isValidScheme($scheme): bool - { - return (bool)preg_match(self::SCHEME_REGEX, $scheme); - } - - /** - * Checks if user contains valid chars - * @param string $user - * @return bool - */ - public static function isValidUser($user): bool - { - return (bool)preg_match(self::USER_OR_PASS_REGEX, $user); - } - - /** - * Checks if pass contains valid chars - * @param string $pass - * @return bool - */ - public static function isValidPass($pass): bool - { - return (bool)preg_match(self::USER_OR_PASS_REGEX, $pass); - } - - /** - * @param string $userInfo - * @return bool - */ - public static function isValidUserInfo($userInfo): bool - { - /** @var array|string $userInfo */ - - if (!preg_match(self::USERINFO_REGEX, $userInfo, $userInfo)) { - return false; - } - - if (!self::isValidUser($userInfo['user'])) { - return false; - } - - if (isset($userInfo['pass'])) { - return self::isValidPass($userInfo['pass']); - } - - return true; - } - - /** - * Checks if host is valid - * @param string $host - * @return bool - */ - public static function isValidHost($host): bool - { - // min and max length - if ($host === '' || isset($host[253])) { - return false; - } - - // check ipv6 - if ($host[0] === '[') { - if ($host[-1] !== ']') { - return false; - } - - return filter_var( - substr($host, 1, -1), - \FILTER_VALIDATE_IP, - \FILTER_FLAG_IPV6 - ) !== false; - } - - // check ipv4 - if (preg_match('`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\$`', $host)) { - return \filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4) !== false; - } - - foreach (explode('.', $host) as $host) { - // empty or too long label - if ($host === '' || isset($host[63])) { - return false; - } - if ($host[0] === '-' || $host[-1] === '-') { - return false; - } - if (!preg_match(self::HOST_LABEL_REGEX, $host)) { - return false; - } - } - - return true; - } - - /** - * Checks if the port is valid - * @param int $port - * @return bool - */ - public static function isValidPort($port): bool - { - return $port >= 0 && $port <= 65535; - } - - /** - * Checks if authority contains valid chars - * @param string $authority - * @return bool - */ - public static function isValidAuthority($authority): bool - { - if ($authority === '') { - return true; - } - - /** @var array|string $authority */ - - if (!preg_match(self::AUTHORITY_REGEX, $authority, $authority)) { - return false; - } - - if (isset($authority['port']) && !self::isValidPort((int)$authority['port'])) { - return false; - } - - if (isset($authority['userinfo']) && !self::isValidUserInfo($authority['userinfo'])) { - return false; - } - - return self::isValidHost($authority['host']); - } - - /** - * Checks if the path contains valid chars - * @param string $path - * @return bool - */ - public static function isValidPath($path): bool - { - return $path === '' || (bool)preg_match(self::PATH_REGEX, $path); - } - - /** - * Checks if the query string contains valid chars - * @param string $query - * @return bool - */ - public static function isValidQuery($query): bool - { - return $query === '' || (bool)preg_match(self::QUERY_OR_FRAGMENT_REGEX, $query); - } - - /** - * Checks if the fragment contains valid chars - * @param string $fragment - * @return bool - */ - public static function isValidFragment($fragment): bool - { - return $fragment === '' || (bool)preg_match(self::QUERY_OR_FRAGMENT_REGEX, $fragment); - } - - /** - * @param string $uri - * @param bool $expand_authority - * @param bool $validate - * @return array|null - */ - public static function parseComponents($uri, $expand_authority = true, $validate = true) - { - if (!preg_match(self::URI_REGEX, $uri, $uri)) { - return null; - } - - $comp = []; - - // scheme - if (isset($uri[2]) && $uri[2] !== '') { - if ($validate && !self::isValidScheme($uri[2])) { - return null; - } - $comp['scheme'] = $uri[2]; - } - - // authority - if (isset($uri[4]) && isset($uri[3][0])) { - if ($uri[4] === '') { - if ($expand_authority) { - $comp['host'] = ''; - } else { - $comp['authority'] = ''; - } - } elseif ($expand_authority) { - $au = self::parseAuthorityComponents($uri[4], $validate); - if ($au === null) { - return null; - } - $comp += $au; - unset($au); - } else { - if ($validate && !self::isValidAuthority($uri[4])) { - return null; - } - $comp['authority'] = $uri[4]; - } - } - - // path - if (isset($uri[5])) { - if ($validate && !self::isValidPath($uri[5])) { - return null; - } - $comp['path'] = $uri[5]; - // not a relative uri, remove dot segments - if (isset($comp['scheme']) || isset($comp['authority']) || isset($comp['host'])) { - $comp['path'] = self::removeDotSegmentsFromPath($comp['path']); - } - } - - // query - if (isset($uri[7]) && isset($uri[6][0])) { - if ($validate && !self::isValidQuery($uri[7])) { - return null; - } - $comp['query'] = $uri[7]; - } - - // fragment - if (isset($uri[9]) && isset($uri[8][0])) { - if ($validate && !self::isValidFragment($uri[9])) { - return null; - } - $comp['fragment'] = $uri[9]; - } - - return $comp; - } - - /** - * @param self|string|array $uri - * @return array|null - */ - public static function resolveComponents($uri) - { - if ($uri instanceof self) { - return $uri->components; - } - - if (is_string($uri)) { - return self::parseComponents($uri); - } - - if (is_array($uri)) { - if (isset($uri['host'])) { - unset($uri['authority']); - } elseif (isset($uri['authority'])) { - $au = self::parseAuthorityComponents($uri['authority']); - unset($uri['authority']); - if ($au !== null) { - unset($uri['user'], $uri['pass'], $uri['host'], $uri['port']); - $uri += $au; - } - } - return $uri; - } - - return null; - } - - /** - * @param string $authority - * @param bool $validate - * @return array|null - */ - public static function parseAuthorityComponents($authority, $validate = true) - { - /** @var array|string $authority */ - - if (!preg_match(self::AUTHORITY_REGEX, $authority, $authority)) { - return null; - } - - $comp = []; - - // userinfo - if (isset($authority['userinfo']) && $authority['userinfo'] !== '') { - if (!preg_match(self::USERINFO_REGEX, $authority['userinfo'], $ui)) { - return null; - } - - // user - if ($validate && !self::isValidUser($ui['user'])) { - return null; - } - $comp['user'] = $ui['user']; - - // pass - if (isset($ui['pass']) && $ui['pass'] !== '') { - if ($validate && !self::isValidPass($ui['pass'])) { - return null; - } - $comp['pass'] = $ui['pass']; - } - - unset($ui); - } - - // host - if ($validate && !self::isValidHost($authority['host'])) { - return null; - } - $comp['host'] = $authority['host']; - - - // port - if (isset($authority['port'])) { - $authority['port'] = (int)$authority['port']; - if (!self::isValidPort($authority['port'])) { - return null; - } - $comp['port'] = $authority['port']; - } - - return $comp; - } - - /** - * @param array $ref - * @param array $base - * @param bool $normalize - * @return array - */ - public static function mergeComponents($ref, $base, $normalize = false): array - { - if (isset($ref['scheme'])) { - $dest = $ref; - } else { - $dest = []; - - $dest['scheme'] = $base['scheme'] ?? null; - - if (isset($ref['authority']) || isset($ref['host'])) { - $dest += $ref; - } else { - if (isset($base['authority'])) { - $dest['authority'] = $base['authority']; - } else { - $dest['user'] = $base['user'] ?? null; - $dest['pass'] = $base['pass'] ?? null; - $dest['host'] = $base['host'] ?? null; - $dest['port'] = $base['port'] ?? null; - } - - if (!isset($ref['path'])) { - $ref['path'] = ''; - } - if (!isset($base['path'])) { - $base['path'] = ''; - } - - if ($ref['path'] === '') { - $dest['path'] = $base['path']; - $dest['query'] = $ref['query'] ?? $base['query'] ?? null; - } else { - if ($ref['path'][0] === '/') { - $dest['path'] = $ref['path']; - } else { - if ((isset($base['authority']) || isset($base['host'])) && $base['path'] === '') { - $dest['path'] = '/' . $ref['path']; - } else { - $dest['path'] = $base['path']; - - if ($dest['path'] !== '') { - $pos = strrpos($dest['path'], '/'); - if ($pos === false) { - $dest['path'] = ''; - } else { - $dest['path'] = substr($dest['path'], 0, $pos); - } - - unset($pos); - } - $dest['path'] .= '/' . $ref['path']; - } - } - - $dest['query'] = $ref['query'] ?? null; - } - } - } - - $dest['fragment'] = $ref['fragment'] ?? null; - - if ($normalize) { - return self::normalizeComponents($dest); - } - - if (isset($dest['path'])) { - $dest['path'] = self::removeDotSegmentsFromPath($dest['path']); - } - - return $dest; - } - - /** - * @param mixed[] $components - */ - public static function normalizeComponents($components): array - { - if (isset($components['scheme'])) { - $components['scheme'] = strtolower($components['scheme']); - // Remove default port - if (isset($components['port']) && self::getSchemePort($components['scheme']) === $components['port']) { - $components['port'] = null; - } - } - - if (isset($components['host'])) { - $components['host'] = strtolower($components['host']); - } - - if (isset($components['path'])) { - $components['path'] = self::removeDotSegmentsFromPath($components['path']); - } - - if (isset($components['query'])) { - $components['query'] = self::normalizeQueryString($components['query']); - } - - return $components; - } - - /** - * Removes dot segments from path - * @param string $path - * @return string - */ - public static function removeDotSegmentsFromPath($path): string - { - // Fast check common simple paths - if ($path === '' || $path === '/') { - return $path; - } - - $output = ''; - $last_slash = 0; - - $len = strlen($path); - $i = 0; - - while ($i < $len) { - if ($path[$i] === '.') { - $j = $i + 1; - // search for . - if ($j >= $len) { - break; - } - - // search for ./ - if ($path[$j] === '/') { - $i = $j + 1; - continue; - } - - // search for ../ - if ($path[$j] === '.') { - $k = $j + 1; - if ($k >= $len) { - break; - } - if ($path[$k] === '/') { - $i = $k + 1; - continue; - } - } - } elseif ($path[$i] === '/') { - $j = $i + 1; - if ($j >= $len) { - $output .= '/'; - break; - } - - // search for /. - if ($path[$j] === '.') { - $k = $j + 1; - if ($k >= $len) { - $output .= '/'; - break; - } - // search for /./ - if ($path[$k] === '/') { - $i = $k; - continue; - } - // search for /.. - if ($path[$k] === '.') { - $n = $k + 1; - if ($n >= $len) { - // keep the slash - $output = substr($output, 0, $last_slash + 1); - break; - } - // search for /../ - if ($path[$n] === '/') { - $output = substr($output, 0, $last_slash); - $last_slash = (int)strrpos($output, '/'); - $i = $n; - continue; - } - } - } - } - - $pos = strpos($path, '/', $i + 1); - - if ($pos === false) { - $output .= substr($path, $i); - break; - } - - $last_slash = strlen($output); - $output .= substr($path, $i, $pos - $i); - - $i = $pos; - } - - return $output; - } - - /** - * @param string|null $query - * @return array - */ - public static function parseQueryString($query): array - { - if ($query === null) { - return []; - } - - $list = []; - - foreach (explode('&', $query) as $name) { - $value = null; - if (($pos = strpos($name, '=')) !== false) { - $value = self::decodeComponent(substr($name, $pos + 1)); - $name = self::decodeComponent(substr($name, 0, $pos)); - } else { - $name = self::decodeComponent($name); - } - $list[$name] = $value; - } - - return $list; - } - - /** - * @param array $qs - * @param string|null $prefix - * @param string $separator - * @param bool $sort - * @return string - */ - public static function buildQueryString($qs, $prefix = null, - $separator = '&', $sort = false): string - { - $isIndexed = static function (array $array): bool { - for ($i = 0, $max = count($array); $i < $max; $i++) { - if (!array_key_exists($i, $array)) { - return false; - } - } - return true; - }; - - $f = static function (array $arr, $prefix = null) use (&$f, &$isIndexed) { - $indexed = $prefix !== null && $isIndexed($arr); - - foreach ($arr as $key => $value) { - if ($prefix !== null) { - $key = $prefix . ($indexed ? "[]" : "[{$key}]"); - } - if (is_array($value)) { - yield from $f($value, $key); - } else { - yield $key => $value; - } - } - }; - - $data = []; - - foreach ($f($qs, $prefix) as $key => $value) { - $item = is_string($key) ? self::encodeComponent($key) : $key; - if ($value !== null) { - $item .= '='; - $item .= is_string($value) ? self::encodeComponent($value) : $value; - } - if ($item === '' || $item === '=') { - continue; - } - $data[] = $item; - } - - if (!$data) { - return ''; - } - - if ($sort) { - sort($data); - } - - return implode($separator, $data); - } - - /** - * @param string $query - * @return string - */ - public static function normalizeQueryString($query): string - { - return static::buildQueryString(self::parseQueryString($query), null, '&', true); - } - - /** - * @param string $component - */ - public static function decodeComponent($component): string - { - return rawurldecode($component); - } - - /** - * @param string $component - * @param mixed[]|null $skip - */ - public static function encodeComponent($component, $skip = null): string - { - if (!$skip) { - return rawurlencode($component); - } - - $str = ''; - - foreach (UnicodeString::walkString($component) as list($cp, $chars)) { - if ($cp < 0x80) { - if ($cp === 0x2D || $cp === 0x2E || - $cp === 0x5F || $cp === 0x7E || - ($cp >= 0x41 && $cp <= 0x5A) || - ($cp >= 0x61 && $cp <= 0x7A) || - ($cp >= 0x30 && $cp <= 0x39) || - in_array($cp, $skip, true) - ) { - $str .= chr($cp); - } else { - $str .= '%' . strtoupper(dechex($cp)); - } - } else { - $i = 0; - while (isset($chars[$i])) { - $str .= '%' . strtoupper(dechex($chars[$i++])); - } - } - } - - return $str; - } - - /** - * @param string $scheme - * @param int|null $port - */ - public static function setSchemePort($scheme, $port) - { - $scheme = strtolower($scheme); - - if ($port === null) { - unset(self::$KNOWN_PORTS[$scheme]); - } else { - self::$KNOWN_PORTS[$scheme] = $port; - } - } - - /** - * @param string $scheme - */ - public static function getSchemePort($scheme) - { - return self::$KNOWN_PORTS[strtolower($scheme)] ?? null; - } - - /** - * @var mixed[] - */ - protected static $KNOWN_PORTS = [ - 'ftp' => 21, - 'ssh' => 22, - 'telnet' => 23, - 'smtp' => 25, - 'tftp' => 69, - 'http' => 80, - 'pop' => 110, - 'sftp' => 115, - 'imap' => 143, - 'irc' => 194, - 'ldap' => 389, - 'https' => 443, - 'ldaps' => 636, - 'telnets' => 992, - 'imaps' => 993, - 'ircs' => 994, - 'pops' => 995, - ]; +class Uri { + + const URI_REGEX = '`^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$`'; + + const SCHEME_REGEX = '`^[a-z][a-z0-9-+.]*$`i'; + + const USER_OR_PASS_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-._~!$&\'()*+,:;=]+)*$`i'; + + const USERINFO_REGEX = '`^(?[^:]+)(?::(?.*))?$`'; + + const HOST_LABEL_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-]+)*$`i'; + + const AUTHORITY_REGEX = '`^(?:(?[^@]+)\@)?(?(\[[a-f0-9:]+\]|[^:]+))(?::(?\d+))?$`i'; + + const PATH_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-._~!$&\'()*+,;=:@/]+)*$`i'; + + const QUERY_OR_FRAGMENT_REGEX = '`^(?:(?:%[a-f0-9]{2})+|[a-z0-9-._~!$&\'"()\[\]*+,;=:@?/%]+)*$`i'; + + /** + * @var mixed[] + */ + protected $components; + + /** + * @var string|null + */ + protected $str; + + /** + * @param array $components An array of normalized components + */ + public function __construct( array $components ) { + $this->components = $components + array( + 'scheme' => null, + 'user' => null, + 'pass' => null, + 'host' => null, + 'port' => null, + 'path' => null, + 'query' => null, + 'fragment' => null, + ); + } + + /** + * @return string|null + */ + public function scheme() { + return $this->components['scheme']; + } + + /** + * @return string|null + */ + public function user() { + return $this->components['user']; + } + + /** + * @return string|null + */ + public function pass() { + return $this->components['pass']; + } + + /** + * @return string|null + */ + public function userInfo() { + if ( $this->components['user'] === null ) { + return null; + } + + if ( $this->components['pass'] === null ) { + return $this->components['user']; + } + + return $this->components['user'] . ':' . $this->components['pass']; + } + + /** + * @return string|null + */ + public function host() { + return $this->components['host']; + } + + /** + * @return int|null + */ + public function port() { + return $this->components['port']; + } + + /** + * @return string|null + */ + public function authority() { + if ( $this->components['host'] === null ) { + return null; + } + + $authority = $this->userInfo(); + if ( $authority !== null ) { + $authority .= '@'; + } + + $authority .= $this->components['host']; + + if ( $this->components['port'] !== null ) { + $authority .= ':' . $this->components['port']; + } + + return $authority; + } + + /** + * @return string|null + */ + public function path() { + return $this->components['path']; + } + + /** + * @return string|null + */ + public function query() { + return $this->components['query']; + } + + /** + * @return string|null + */ + public function fragment() { + return $this->components['fragment']; + } + + /** + * @return array|null[] + */ + public function components(): array { + return $this->components; + } + + /** + * @return bool + */ + public function isAbsolute(): bool { + return $this->components['scheme'] !== null; + } + + /** + * Use this URI as base to resolve the reference + * + * @param static|string|array $ref + * @param bool $normalize + * @return $this|null + */ + public function resolveRef( $ref, $normalize = false ) { + $ref = self::resolveComponents( $ref ); + if ( $ref === null ) { + return $this; + } + + return new static( self::mergeComponents( $ref, $this->components, $normalize ) ); + } + + /** + * Resolve this URI reference using a base URI + * + * @param static|string|array $base + * @param bool $normalize + * @return static + */ + public function resolve( $base, $normalize = false ): self { + if ( $this->isAbsolute() ) { + return $this; + } + + $base = self::resolveComponents( $base ); + + if ( $base === null ) { + return $this; + } + + return new static( self::mergeComponents( $this->components, $base, $normalize ) ); + } + + /** + * @return string + */ + public function __toString(): string { + if ( $this->str !== null ) { + return $this->str; + } + + $str = ''; + + if ( $this->components['scheme'] !== null ) { + $str .= $this->components['scheme'] . ':'; + } + + if ( $this->components['host'] !== null ) { + $str .= '//' . $this->authority(); + } + + $str .= $this->components['path']; + + if ( $this->components['query'] !== null ) { + $str .= '?' . $this->components['query']; + } + + if ( $this->components['fragment'] !== null ) { + $str .= '#' . $this->components['fragment']; + } + + return $this->str = $str; + } + + /** + * @param string $uri + * @param bool $normalize + * @return static|null + */ + public static function create( $uri, $normalize = false ) { + $comp = self::parseComponents( $uri ); + if ( ! $comp ) { + return null; + } + + if ( $normalize ) { + $comp = self::normalizeComponents( $comp ); + } + + return new static( $comp ); + } + + /** + * Checks if the scheme contains valid chars + * + * @param string $scheme + * @return bool + */ + public static function isValidScheme( $scheme ): bool { + return (bool) preg_match( self::SCHEME_REGEX, $scheme ); + } + + /** + * Checks if user contains valid chars + * + * @param string $user + * @return bool + */ + public static function isValidUser( $user ): bool { + return (bool) preg_match( self::USER_OR_PASS_REGEX, $user ); + } + + /** + * Checks if pass contains valid chars + * + * @param string $pass + * @return bool + */ + public static function isValidPass( $pass ): bool { + return (bool) preg_match( self::USER_OR_PASS_REGEX, $pass ); + } + + /** + * @param string $userInfo + * @return bool + */ + public static function isValidUserInfo( $userInfo ): bool { + /** @var array|string $userInfo */ + + if ( ! preg_match( self::USERINFO_REGEX, $userInfo, $userInfo ) ) { + return false; + } + + if ( ! self::isValidUser( $userInfo['user'] ) ) { + return false; + } + + if ( isset( $userInfo['pass'] ) ) { + return self::isValidPass( $userInfo['pass'] ); + } + + return true; + } + + /** + * Checks if host is valid + * + * @param string $host + * @return bool + */ + public static function isValidHost( $host ): bool { + // min and max length + if ( $host === '' || isset( $host[253] ) ) { + return false; + } + + // check ipv6 + if ( $host[0] === '[' ) { + if ( $host[-1] !== ']' ) { + return false; + } + + return filter_var( + substr( $host, 1, -1 ), + \FILTER_VALIDATE_IP, + \FILTER_FLAG_IPV6 + ) !== false; + } + + // check ipv4 + if ( preg_match( '`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\$`', $host ) ) { + return \filter_var( $host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4 ) !== false; + } + + foreach ( explode( '.', $host ) as $host ) { + // empty or too long label + if ( $host === '' || isset( $host[63] ) ) { + return false; + } + if ( $host[0] === '-' || $host[-1] === '-' ) { + return false; + } + if ( ! preg_match( self::HOST_LABEL_REGEX, $host ) ) { + return false; + } + } + + return true; + } + + /** + * Checks if the port is valid + * + * @param int $port + * @return bool + */ + public static function isValidPort( $port ): bool { + return $port >= 0 && $port <= 65535; + } + + /** + * Checks if authority contains valid chars + * + * @param string $authority + * @return bool + */ + public static function isValidAuthority( $authority ): bool { + if ( $authority === '' ) { + return true; + } + + /** @var array|string $authority */ + + if ( ! preg_match( self::AUTHORITY_REGEX, $authority, $authority ) ) { + return false; + } + + if ( isset( $authority['port'] ) && ! self::isValidPort( (int) $authority['port'] ) ) { + return false; + } + + if ( isset( $authority['userinfo'] ) && ! self::isValidUserInfo( $authority['userinfo'] ) ) { + return false; + } + + return self::isValidHost( $authority['host'] ); + } + + /** + * Checks if the path contains valid chars + * + * @param string $path + * @return bool + */ + public static function isValidPath( $path ): bool { + return $path === '' || (bool) preg_match( self::PATH_REGEX, $path ); + } + + /** + * Checks if the query string contains valid chars + * + * @param string $query + * @return bool + */ + public static function isValidQuery( $query ): bool { + return $query === '' || (bool) preg_match( self::QUERY_OR_FRAGMENT_REGEX, $query ); + } + + /** + * Checks if the fragment contains valid chars + * + * @param string $fragment + * @return bool + */ + public static function isValidFragment( $fragment ): bool { + return $fragment === '' || (bool) preg_match( self::QUERY_OR_FRAGMENT_REGEX, $fragment ); + } + + /** + * @param string $uri + * @param bool $expand_authority + * @param bool $validate + * @return array|null + */ + public static function parseComponents( $uri, $expand_authority = true, $validate = true ) { + if ( ! preg_match( self::URI_REGEX, $uri, $uri ) ) { + return null; + } + + $comp = array(); + + // scheme + if ( isset( $uri[2] ) && $uri[2] !== '' ) { + if ( $validate && ! self::isValidScheme( $uri[2] ) ) { + return null; + } + $comp['scheme'] = $uri[2]; + } + + // authority + if ( isset( $uri[4] ) && isset( $uri[3][0] ) ) { + if ( $uri[4] === '' ) { + if ( $expand_authority ) { + $comp['host'] = ''; + } else { + $comp['authority'] = ''; + } + } elseif ( $expand_authority ) { + $au = self::parseAuthorityComponents( $uri[4], $validate ); + if ( $au === null ) { + return null; + } + $comp += $au; + unset( $au ); + } else { + if ( $validate && ! self::isValidAuthority( $uri[4] ) ) { + return null; + } + $comp['authority'] = $uri[4]; + } + } + + // path + if ( isset( $uri[5] ) ) { + if ( $validate && ! self::isValidPath( $uri[5] ) ) { + return null; + } + $comp['path'] = $uri[5]; + // not a relative uri, remove dot segments + if ( isset( $comp['scheme'] ) || isset( $comp['authority'] ) || isset( $comp['host'] ) ) { + $comp['path'] = self::removeDotSegmentsFromPath( $comp['path'] ); + } + } + + // query + if ( isset( $uri[7] ) && isset( $uri[6][0] ) ) { + if ( $validate && ! self::isValidQuery( $uri[7] ) ) { + return null; + } + $comp['query'] = $uri[7]; + } + + // fragment + if ( isset( $uri[9] ) && isset( $uri[8][0] ) ) { + if ( $validate && ! self::isValidFragment( $uri[9] ) ) { + return null; + } + $comp['fragment'] = $uri[9]; + } + + return $comp; + } + + /** + * @param self|string|array $uri + * @return array|null + */ + public static function resolveComponents( $uri ) { + if ( $uri instanceof self ) { + return $uri->components; + } + + if ( is_string( $uri ) ) { + return self::parseComponents( $uri ); + } + + if ( is_array( $uri ) ) { + if ( isset( $uri['host'] ) ) { + unset( $uri['authority'] ); + } elseif ( isset( $uri['authority'] ) ) { + $au = self::parseAuthorityComponents( $uri['authority'] ); + unset( $uri['authority'] ); + if ( $au !== null ) { + unset( $uri['user'], $uri['pass'], $uri['host'], $uri['port'] ); + $uri += $au; + } + } + return $uri; + } + + return null; + } + + /** + * @param string $authority + * @param bool $validate + * @return array|null + */ + public static function parseAuthorityComponents( $authority, $validate = true ) { + /** @var array|string $authority */ + + if ( ! preg_match( self::AUTHORITY_REGEX, $authority, $authority ) ) { + return null; + } + + $comp = array(); + + // userinfo + if ( isset( $authority['userinfo'] ) && $authority['userinfo'] !== '' ) { + if ( ! preg_match( self::USERINFO_REGEX, $authority['userinfo'], $ui ) ) { + return null; + } + + // user + if ( $validate && ! self::isValidUser( $ui['user'] ) ) { + return null; + } + $comp['user'] = $ui['user']; + + // pass + if ( isset( $ui['pass'] ) && $ui['pass'] !== '' ) { + if ( $validate && ! self::isValidPass( $ui['pass'] ) ) { + return null; + } + $comp['pass'] = $ui['pass']; + } + + unset( $ui ); + } + + // host + if ( $validate && ! self::isValidHost( $authority['host'] ) ) { + return null; + } + $comp['host'] = $authority['host']; + + // port + if ( isset( $authority['port'] ) ) { + $authority['port'] = (int) $authority['port']; + if ( ! self::isValidPort( $authority['port'] ) ) { + return null; + } + $comp['port'] = $authority['port']; + } + + return $comp; + } + + /** + * @param array $ref + * @param array $base + * @param bool $normalize + * @return array + */ + public static function mergeComponents( $ref, $base, $normalize = false ): array { + if ( isset( $ref['scheme'] ) ) { + $dest = $ref; + } else { + $dest = array(); + + $dest['scheme'] = $base['scheme'] ?? null; + + if ( isset( $ref['authority'] ) || isset( $ref['host'] ) ) { + $dest += $ref; + } else { + if ( isset( $base['authority'] ) ) { + $dest['authority'] = $base['authority']; + } else { + $dest['user'] = $base['user'] ?? null; + $dest['pass'] = $base['pass'] ?? null; + $dest['host'] = $base['host'] ?? null; + $dest['port'] = $base['port'] ?? null; + } + + if ( ! isset( $ref['path'] ) ) { + $ref['path'] = ''; + } + if ( ! isset( $base['path'] ) ) { + $base['path'] = ''; + } + + if ( $ref['path'] === '' ) { + $dest['path'] = $base['path']; + $dest['query'] = $ref['query'] ?? $base['query'] ?? null; + } else { + if ( $ref['path'][0] === '/' ) { + $dest['path'] = $ref['path']; + } elseif ( ( isset( $base['authority'] ) || isset( $base['host'] ) ) && $base['path'] === '' ) { + $dest['path'] = '/' . $ref['path']; + } else { + $dest['path'] = $base['path']; + + if ( $dest['path'] !== '' ) { + $pos = strrpos( $dest['path'], '/' ); + if ( $pos === false ) { + $dest['path'] = ''; + } else { + $dest['path'] = substr( $dest['path'], 0, $pos ); + } + + unset( $pos ); + } + $dest['path'] .= '/' . $ref['path']; + } + + $dest['query'] = $ref['query'] ?? null; + } + } + } + + $dest['fragment'] = $ref['fragment'] ?? null; + + if ( $normalize ) { + return self::normalizeComponents( $dest ); + } + + if ( isset( $dest['path'] ) ) { + $dest['path'] = self::removeDotSegmentsFromPath( $dest['path'] ); + } + + return $dest; + } + + /** + * @param mixed[] $components + */ + public static function normalizeComponents( $components ): array { + if ( isset( $components['scheme'] ) ) { + $components['scheme'] = strtolower( $components['scheme'] ); + // Remove default port + if ( isset( $components['port'] ) && self::getSchemePort( $components['scheme'] ) === $components['port'] ) { + $components['port'] = null; + } + } + + if ( isset( $components['host'] ) ) { + $components['host'] = strtolower( $components['host'] ); + } + + if ( isset( $components['path'] ) ) { + $components['path'] = self::removeDotSegmentsFromPath( $components['path'] ); + } + + if ( isset( $components['query'] ) ) { + $components['query'] = self::normalizeQueryString( $components['query'] ); + } + + return $components; + } + + /** + * Removes dot segments from path + * + * @param string $path + * @return string + */ + public static function removeDotSegmentsFromPath( $path ): string { + // Fast check common simple paths + if ( $path === '' || $path === '/' ) { + return $path; + } + + $output = ''; + $last_slash = 0; + + $len = strlen( $path ); + $i = 0; + + while ( $i < $len ) { + if ( $path[ $i ] === '.' ) { + $j = $i + 1; + // search for . + if ( $j >= $len ) { + break; + } + + // search for ./ + if ( $path[ $j ] === '/' ) { + $i = $j + 1; + continue; + } + + // search for ../ + if ( $path[ $j ] === '.' ) { + $k = $j + 1; + if ( $k >= $len ) { + break; + } + if ( $path[ $k ] === '/' ) { + $i = $k + 1; + continue; + } + } + } elseif ( $path[ $i ] === '/' ) { + $j = $i + 1; + if ( $j >= $len ) { + $output .= '/'; + break; + } + + // search for /. + if ( $path[ $j ] === '.' ) { + $k = $j + 1; + if ( $k >= $len ) { + $output .= '/'; + break; + } + // search for /./ + if ( $path[ $k ] === '/' ) { + $i = $k; + continue; + } + // search for /.. + if ( $path[ $k ] === '.' ) { + $n = $k + 1; + if ( $n >= $len ) { + // keep the slash + $output = substr( $output, 0, $last_slash + 1 ); + break; + } + // search for /../ + if ( $path[ $n ] === '/' ) { + $output = substr( $output, 0, $last_slash ); + $last_slash = (int) strrpos( $output, '/' ); + $i = $n; + continue; + } + } + } + } + + $pos = strpos( $path, '/', $i + 1 ); + + if ( $pos === false ) { + $output .= substr( $path, $i ); + break; + } + + $last_slash = strlen( $output ); + $output .= substr( $path, $i, $pos - $i ); + + $i = $pos; + } + + return $output; + } + + /** + * @param string|null $query + * @return array + */ + public static function parseQueryString( $query ): array { + if ( $query === null ) { + return array(); + } + + $list = array(); + + foreach ( explode( '&', $query ) as $name ) { + $value = null; + if ( ( $pos = strpos( $name, '=' ) ) !== false ) { + $value = self::decodeComponent( substr( $name, $pos + 1 ) ); + $name = self::decodeComponent( substr( $name, 0, $pos ) ); + } else { + $name = self::decodeComponent( $name ); + } + $list[ $name ] = $value; + } + + return $list; + } + + /** + * @param array $qs + * @param string|null $prefix + * @param string $separator + * @param bool $sort + * @return string + */ + public static function buildQueryString( + $qs, + $prefix = null, + $separator = '&', + $sort = false + ): string { + $isIndexed = static function ( array $array ): bool { + for ( $i = 0, $max = count( $array ); $i < $max; $i++ ) { + if ( ! array_key_exists( $i, $array ) ) { + return false; + } + } + return true; + }; + + $f = static function ( array $arr, $prefix = null ) use ( &$f, &$isIndexed ) { + $indexed = $prefix !== null && $isIndexed( $arr ); + + foreach ( $arr as $key => $value ) { + if ( $prefix !== null ) { + $key = $prefix . ( $indexed ? '[]' : "[{$key}]" ); + } + if ( is_array( $value ) ) { + yield from $f( $value, $key ); + } else { + yield $key => $value; + } + } + }; + + $data = array(); + + foreach ( $f( $qs, $prefix ) as $key => $value ) { + $item = is_string( $key ) ? self::encodeComponent( $key ) : $key; + if ( $value !== null ) { + $item .= '='; + $item .= is_string( $value ) ? self::encodeComponent( $value ) : $value; + } + if ( $item === '' || $item === '=' ) { + continue; + } + $data[] = $item; + } + + if ( ! $data ) { + return ''; + } + + if ( $sort ) { + sort( $data ); + } + + return implode( $separator, $data ); + } + + /** + * @param string $query + * @return string + */ + public static function normalizeQueryString( $query ): string { + return static::buildQueryString( self::parseQueryString( $query ), null, '&', true ); + } + + /** + * @param string $component + */ + public static function decodeComponent( $component ): string { + return rawurldecode( $component ); + } + + /** + * @param string $component + * @param mixed[]|null $skip + */ + public static function encodeComponent( $component, $skip = null ): string { + if ( ! $skip ) { + return rawurlencode( $component ); + } + + $str = ''; + + foreach ( UnicodeString::walkString( $component ) as list($cp, $chars) ) { + if ( $cp < 0x80 ) { + if ( $cp === 0x2D || $cp === 0x2E || + $cp === 0x5F || $cp === 0x7E || + ( $cp >= 0x41 && $cp <= 0x5A ) || + ( $cp >= 0x61 && $cp <= 0x7A ) || + ( $cp >= 0x30 && $cp <= 0x39 ) || + in_array( $cp, $skip, true ) + ) { + $str .= chr( $cp ); + } else { + $str .= '%' . strtoupper( dechex( $cp ) ); + } + } else { + $i = 0; + while ( isset( $chars[ $i ] ) ) { + $str .= '%' . strtoupper( dechex( $chars[ $i++ ] ) ); + } + } + } + + return $str; + } + + /** + * @param string $scheme + * @param int|null $port + */ + public static function setSchemePort( $scheme, $port ) { + $scheme = strtolower( $scheme ); + + if ( $port === null ) { + unset( self::$KNOWN_PORTS[ $scheme ] ); + } else { + self::$KNOWN_PORTS[ $scheme ] = $port; + } + } + + /** + * @param string $scheme + */ + public static function getSchemePort( $scheme ) { + return self::$KNOWN_PORTS[ strtolower( $scheme ) ] ?? null; + } + + /** + * @var mixed[] + */ + protected static $KNOWN_PORTS = array( + 'ftp' => 21, + 'ssh' => 22, + 'telnet' => 23, + 'smtp' => 25, + 'tftp' => 69, + 'http' => 80, + 'pop' => 110, + 'sftp' => 115, + 'imap' => 143, + 'irc' => 194, + 'ldap' => 389, + 'https' => 443, + 'ldaps' => 636, + 'telnets' => 992, + 'imaps' => 993, + 'ircs' => 994, + 'pops' => 995, + ); } diff --git a/src/opis/uri/src/UriTemplate.php b/src/opis/uri/src/UriTemplate.php index 3de35d6e..04b896ab 100644 --- a/src/opis/uri/src/UriTemplate.php +++ b/src/opis/uri/src/UriTemplate.php @@ -1,5 +1,6 @@ [a-zA-Z0-9\_\%\.]+)(?:(?\*)?|\:(?\d+))?$~'; +class UriTemplate { - /** @var string */ - const TEMPLATE_REGEX = <<<'REGEX' + /** @var string */ + const TEMPLATE_VARSPEC_REGEX = '~^(?[a-zA-Z0-9\_\%\.]+)(?:(?\*)?|\:(?\d+))?$~'; + + /** @var string */ + const TEMPLATE_REGEX = <<<'REGEX' ~\{ (?[+#./;&=,!@|\?])? (? @@ -38,486 +39,472 @@ class UriTemplate \}~x REGEX; - /** @var array */ - const TEMPLATE_TABLE = [ - '' => [ - 'first' => '', - 'sep' => ',', - 'named' => false, - 'ifemp' => '', - 'allow' => false, - ], - '+' => [ - 'first' => '', - 'sep' => ',', - 'named' => false, - 'ifemp' => '', - 'allow' => true, - ], - '.' => [ - 'first' => '.', - 'sep' => '.', - 'named' => false, - 'ifemp' => '', - 'allow' => false, - ], - '/' => [ - 'first' => '/', - 'sep' => '/', - 'named' => false, - 'ifemp' => '', - 'allow' => false, - ], - ';' => [ - 'first' => ';', - 'sep' => ';', - 'named' => true, - 'ifemp' => '', - 'allow' => false, - ], - '?' => [ - 'first' => '?', - 'sep' => '&', - 'named' => true, - 'ifemp' => '=', - 'allow' => false, - ], - '&' => [ - 'first' => '&', - 'sep' => '&', - 'named' => true, - 'ifemp' => '=', - 'allow' => false, - ], - '#' => [ - 'first' => '#', - 'sep' => ',', - 'named' => false, - 'ifemp' => '', - 'allow' => true, - ], - ]; - - /** - * @var string - */ - protected $uri; - - /** @var bool|null|array */ - protected $parsed = false; - - /** - * UriTemplate constructor. - * @param string $uri_template - */ - public function __construct(string $uri_template) - { - $this->uri = $uri_template; - } - - /** - * @param array $vars - * @return string - */ - public function resolve($vars): string - { - if ($this->parsed === false) { - $this->parsed = $this->parse($this->uri); - } - if ($this->parsed === null || !$vars) { - return $this->uri; - } - - $data = ''; - $vars = $this->prepareVars($vars); - - foreach ($this->parsed as $item) { - if (!is_array($item)) { - $data .= $item; - continue; - } - - $data .= $this->parseTemplateExpression( - self::TEMPLATE_TABLE[$item['operator']], - $this->resolveVars($item['vars'], $vars) - ); - } - - return $data; - } - - /** - * @return bool - */ - public function hasPlaceholders(): bool - { - if ($this->parsed === false) { - $this->parse($this->uri); - } - - return $this->parsed !== null; - } - - /** - * @param string $uri - * @return array|null - */ - protected function parse($uri) - { - $placeholders = null; - preg_match_all(self::TEMPLATE_REGEX, $uri, $placeholders, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); - - if (!$placeholders) { - return null; - } - - $dataIndex = -1; - $data = []; - - $hasVars = false; - $nextOffset = 0; - foreach ($placeholders as &$p) { - $offset = $p[0][1]; - if ($nextOffset < $offset) { - $data[] = substr($uri, $nextOffset, $offset - $nextOffset); - $dataIndex++; - } - $matched = $p[0][0]; - $nextOffset = $offset + strlen($matched); - - $operator = $p['operator'][0] ?? null; - if ($operator === null || !isset(self::TEMPLATE_TABLE[$operator])) { - if ($dataIndex >= 0 && is_string($data[$dataIndex])) { - $data[$dataIndex] .= $matched; - } else { - $data[] = $matched; - $dataIndex++; - } - continue; - } - - $varList = $p['varlist'][0] ?? ''; - $varList = $varList === '' ? [] : explode(',', $varList); - $p = null; - - $varData = []; - - foreach ($varList as $var) { - if (!preg_match(self::TEMPLATE_VARSPEC_REGEX, $var, $spec)) { - continue; - } - - $varData[] = [ - 'name' => $spec['varname'], - 'explode' => isset($spec['explode']) && $spec['explode'] === '*', - 'prefix' => isset($spec['prefix']) ? (int)$spec['prefix'] : 0, - ]; - - unset($var, $spec); - } - - if ($varData) { - $hasVars = true; - $data[] = [ - 'operator' => $operator, - 'vars' => $varData, - ]; - $dataIndex++; - } else { - if ($dataIndex >= 0 && is_string($data[$dataIndex])) { - $data[$dataIndex] .= $matched; - } else { - $data[] = $matched; - $dataIndex++; - } - } - - unset($varData, $varList, $operator); - } - - if (!$hasVars) { - return null; - } - - $matched = substr($uri, $nextOffset); - if ($matched !== false && $matched !== '') { - if ($dataIndex >= 0 && is_string($data[$dataIndex])) { - $data[$dataIndex] .= $matched; - } else { - $data[] = $matched; - } - } - - return $data; - } - - /** - * Convert assoc arrays to objects - * @param array $vars - * @return array - */ - protected function prepareVars($vars): array - { - foreach ($vars as &$value) { - if (is_scalar($value)) { - if (!is_string($value)) { - $value = (string)$value; - } - continue; - } - - if (!is_array($value)) { - continue; - } - - $len = count($value); - for ($i = 0; $i < $len; $i++) { - if (!array_key_exists($i, $value)) { - $value = (object)$value; - break; - } - } - } - - return $vars; - } - - /** - * @param array $vars - * @param array $data - * @return array - */ - protected function resolveVars($vars, $data): array - { - $resolved = []; - - foreach ($vars as $info) { - $name = $info['name']; - - if (!isset($data[$name])) { - continue; - } - - $resolved[] = $info + ['value' => &$data[$name]]; - } - - return $resolved; - } - - /** - * @param array $table - * @param array $data - * @return string - */ - protected function parseTemplateExpression($table, $data): string - { - $result = []; - foreach ($data as $var) { - $str = ""; - if (is_string($var['value'])) { - if ($table['named']) { - $str .= $var['name']; - if ($var['value'] === '') { - $str .= $table['ifemp']; - } else { - $str .= '='; - } - } - if ($var['prefix']) { - $str .= $this->encodeTemplateString(self::prefix($var['value'], $var['prefix']), $table['allow']); - } else { - $str .= $this->encodeTemplateString($var['value'], $table['allow']); - } - } elseif ($var['explode']) { - $list = []; - if ($table['named']) { - if (is_array($var['value'])) { - foreach ($var['value'] as $v) { - if (is_null($v) || !is_scalar($v)) { - continue; - } - $v = $this->encodeTemplateString((string)$v, $table['allow']); - if ($v === '') { - $list[] = $var['name'] . $table['ifemp']; - } else { - $list[] = $var['name'] . '=' . $v; - } - } - } elseif (is_object($var['value'])) { - foreach ($var['value'] as $prop => $v) { - if (is_null($v) || !is_scalar($v)) { - continue; - } - $v = $this->encodeTemplateString((string)$v, $table['allow']); - $prop = $this->encodeTemplateString((string)$prop, $table['allow']); - if ($v === '') { - $list[] = $prop . $table['ifemp']; - } else { - $list[] = $prop . '=' . $v; - } - } - } - } else { - if (is_array($var['value'])) { - foreach ($var['value'] as $v) { - if (is_null($v) || !is_scalar($v)) { - continue; - } - $list[] = $this->encodeTemplateString($v, $table['allow']); - } - } elseif (is_object($var['value'])) { - foreach ($var['value'] as $prop => $v) { - if (is_null($v) || !is_scalar($v)) { - continue; - } - $v = $this->encodeTemplateString((string)$v, $table['allow']); - $prop = $this->encodeTemplateString((string)$prop, $table['allow']); - $list[] = $prop . '=' . $v; - } - } - } - - if ($list) { - $str .= implode($table['sep'], $list); - } - unset($list); - } else { - if ($table['named']) { - $str .= $var['name']; - if ($var['value'] === '') { - $str .= $table['ifemp']; - } else { - $str .= '='; - } - } - $list = []; - if (is_array($var['value'])) { - foreach ($var['value'] as $v) { - $list[] = $this->encodeTemplateString($v, $table['allow']); - } - } elseif (is_object($var['value'])) { - foreach ($var['value'] as $prop => $v) { - $list[] = $this->encodeTemplateString((string)$prop, $table['allow']); - $list[] = $this->encodeTemplateString((string)$v, $table['allow']); - } - } - if ($list) { - $str .= implode(',', $list); - } - unset($list); - } - - if ($str !== '') { - $result[] = $str; - } - } - - if (!$result) { - return ''; - } - - $result = implode($table['sep'], $result); - - if ($result !== '') { - $result = $table['first'] . $result; - } - - return $result; - } - - /** - * @param string $data - * @param bool $reserved - * @return string - */ - protected function encodeTemplateString($data, $reserved): string - { - $skip = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~'; - - if ($reserved) { - $skip .= ':/?#[]@!$&\'()*+,;='; - } - - $result = ''; - $temp = ''; - for ($i = 0, $len = strlen($data); $i < $len; $i++) { - if (strpos($skip, $data[$i]) !== false) { - if ($temp !== '') { - $result .= Uri::encodeComponent($temp); - $temp = ''; - } - $result .= $data[$i]; - continue; - } - if ($reserved && $data[$i] === '%') { - if (isset($data[$i + 1]) && isset($data[$i + 2]) - && strpos('ABCDEF0123456789', $data[$i + 1]) !== false - && strpos('ABCDEF0123456789', $data[$i + 2]) !== false) { - if ($temp !== '') { - $result .= Uri::encodeComponent($temp); - } - $result .= '%' . $data[$i + 1] . $data[$i + 2]; - $i += 3; - continue; - } - } - $temp .= $data[$i]; - } - - if ($temp !== '') { - $result .= Uri::encodeComponent($temp); - } - - return $result; - } - - /** - * @return string - */ - public function value(): string - { - return $this->uri; - } - - public function __toString(): string - { - return $this->uri; - } - - /** - * @param string $uri - * @return bool - */ - public static function isTemplate($uri): bool - { - $open = substr_count($uri, '{'); - if ($open === 0) { - return false; - } - $close = substr_count($uri, '}'); - if ($open !== $close) { - return false; - } - - return (bool)preg_match(self::TEMPLATE_REGEX, $uri); - } - - /** - * @param string $str - * @param int $len - * @return string - */ - protected static function prefix($str, $len): string - { - if ($len === 0) { - return ''; - } - - if ($len >= strlen($str)) { - // Prefix is longer than string length - return $str; - } - - return (string)UnicodeString::from($str)->substring(0, $len); - } + /** @var array */ + const TEMPLATE_TABLE = array( + '' => array( + 'first' => '', + 'sep' => ',', + 'named' => false, + 'ifemp' => '', + 'allow' => false, + ), + '+' => array( + 'first' => '', + 'sep' => ',', + 'named' => false, + 'ifemp' => '', + 'allow' => true, + ), + '.' => array( + 'first' => '.', + 'sep' => '.', + 'named' => false, + 'ifemp' => '', + 'allow' => false, + ), + '/' => array( + 'first' => '/', + 'sep' => '/', + 'named' => false, + 'ifemp' => '', + 'allow' => false, + ), + ';' => array( + 'first' => ';', + 'sep' => ';', + 'named' => true, + 'ifemp' => '', + 'allow' => false, + ), + '?' => array( + 'first' => '?', + 'sep' => '&', + 'named' => true, + 'ifemp' => '=', + 'allow' => false, + ), + '&' => array( + 'first' => '&', + 'sep' => '&', + 'named' => true, + 'ifemp' => '=', + 'allow' => false, + ), + '#' => array( + 'first' => '#', + 'sep' => ',', + 'named' => false, + 'ifemp' => '', + 'allow' => true, + ), + ); + + /** + * @var string + */ + protected $uri; + + /** @var bool|null|array */ + protected $parsed = false; + + /** + * UriTemplate constructor. + * + * @param string $uri_template + */ + public function __construct( string $uri_template ) { + $this->uri = $uri_template; + } + + /** + * @param array $vars + * @return string + */ + public function resolve( $vars ): string { + if ( $this->parsed === false ) { + $this->parsed = $this->parse( $this->uri ); + } + if ( $this->parsed === null || ! $vars ) { + return $this->uri; + } + + $data = ''; + $vars = $this->prepareVars( $vars ); + + foreach ( $this->parsed as $item ) { + if ( ! is_array( $item ) ) { + $data .= $item; + continue; + } + + $data .= $this->parseTemplateExpression( + self::TEMPLATE_TABLE[ $item['operator'] ], + $this->resolveVars( $item['vars'], $vars ) + ); + } + + return $data; + } + + /** + * @return bool + */ + public function hasPlaceholders(): bool { + if ( $this->parsed === false ) { + $this->parse( $this->uri ); + } + + return $this->parsed !== null; + } + + /** + * @param string $uri + * @return array|null + */ + protected function parse( $uri ) { + $placeholders = null; + preg_match_all( self::TEMPLATE_REGEX, $uri, $placeholders, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ); + + if ( ! $placeholders ) { + return null; + } + + $dataIndex = -1; + $data = array(); + + $hasVars = false; + $nextOffset = 0; + foreach ( $placeholders as &$p ) { + $offset = $p[0][1]; + if ( $nextOffset < $offset ) { + $data[] = substr( $uri, $nextOffset, $offset - $nextOffset ); + ++$dataIndex; + } + $matched = $p[0][0]; + $nextOffset = $offset + strlen( $matched ); + + $operator = $p['operator'][0] ?? null; + if ( $operator === null || ! isset( self::TEMPLATE_TABLE[ $operator ] ) ) { + if ( $dataIndex >= 0 && is_string( $data[ $dataIndex ] ) ) { + $data[ $dataIndex ] .= $matched; + } else { + $data[] = $matched; + ++$dataIndex; + } + continue; + } + + $varList = $p['varlist'][0] ?? ''; + $varList = $varList === '' ? array() : explode( ',', $varList ); + $p = null; + + $varData = array(); + + foreach ( $varList as $var ) { + if ( ! preg_match( self::TEMPLATE_VARSPEC_REGEX, $var, $spec ) ) { + continue; + } + + $varData[] = array( + 'name' => $spec['varname'], + 'explode' => isset( $spec['explode'] ) && $spec['explode'] === '*', + 'prefix' => isset( $spec['prefix'] ) ? (int) $spec['prefix'] : 0, + ); + + unset( $var, $spec ); + } + + if ( $varData ) { + $hasVars = true; + $data[] = array( + 'operator' => $operator, + 'vars' => $varData, + ); + ++$dataIndex; + } elseif ( $dataIndex >= 0 && is_string( $data[ $dataIndex ] ) ) { + $data[ $dataIndex ] .= $matched; + } else { + $data[] = $matched; + ++$dataIndex; + } + + unset( $varData, $varList, $operator ); + } + + if ( ! $hasVars ) { + return null; + } + + $matched = substr( $uri, $nextOffset ); + if ( $matched !== false && $matched !== '' ) { + if ( $dataIndex >= 0 && is_string( $data[ $dataIndex ] ) ) { + $data[ $dataIndex ] .= $matched; + } else { + $data[] = $matched; + } + } + + return $data; + } + + /** + * Convert assoc arrays to objects + * + * @param array $vars + * @return array + */ + protected function prepareVars( $vars ): array { + foreach ( $vars as &$value ) { + if ( is_scalar( $value ) ) { + if ( ! is_string( $value ) ) { + $value = (string) $value; + } + continue; + } + + if ( ! is_array( $value ) ) { + continue; + } + + $len = count( $value ); + for ( $i = 0; $i < $len; $i++ ) { + if ( ! array_key_exists( $i, $value ) ) { + $value = (object) $value; + break; + } + } + } + + return $vars; + } + + /** + * @param array $vars + * @param array $data + * @return array + */ + protected function resolveVars( $vars, $data ): array { + $resolved = array(); + + foreach ( $vars as $info ) { + $name = $info['name']; + + if ( ! isset( $data[ $name ] ) ) { + continue; + } + + $resolved[] = $info + array( 'value' => &$data[ $name ] ); + } + + return $resolved; + } + + /** + * @param array $table + * @param array $data + * @return string + */ + protected function parseTemplateExpression( $table, $data ): string { + $result = array(); + foreach ( $data as $var ) { + $str = ''; + if ( is_string( $var['value'] ) ) { + if ( $table['named'] ) { + $str .= $var['name']; + if ( $var['value'] === '' ) { + $str .= $table['ifemp']; + } else { + $str .= '='; + } + } + if ( $var['prefix'] ) { + $str .= $this->encodeTemplateString( self::prefix( $var['value'], $var['prefix'] ), $table['allow'] ); + } else { + $str .= $this->encodeTemplateString( $var['value'], $table['allow'] ); + } + } elseif ( $var['explode'] ) { + $list = array(); + if ( $table['named'] ) { + if ( is_array( $var['value'] ) ) { + foreach ( $var['value'] as $v ) { + if ( is_null( $v ) || ! is_scalar( $v ) ) { + continue; + } + $v = $this->encodeTemplateString( (string) $v, $table['allow'] ); + if ( $v === '' ) { + $list[] = $var['name'] . $table['ifemp']; + } else { + $list[] = $var['name'] . '=' . $v; + } + } + } elseif ( is_object( $var['value'] ) ) { + foreach ( $var['value'] as $prop => $v ) { + if ( is_null( $v ) || ! is_scalar( $v ) ) { + continue; + } + $v = $this->encodeTemplateString( (string) $v, $table['allow'] ); + $prop = $this->encodeTemplateString( (string) $prop, $table['allow'] ); + if ( $v === '' ) { + $list[] = $prop . $table['ifemp']; + } else { + $list[] = $prop . '=' . $v; + } + } + } + } elseif ( is_array( $var['value'] ) ) { + foreach ( $var['value'] as $v ) { + if ( is_null( $v ) || ! is_scalar( $v ) ) { + continue; + } + $list[] = $this->encodeTemplateString( $v, $table['allow'] ); + } + } elseif ( is_object( $var['value'] ) ) { + foreach ( $var['value'] as $prop => $v ) { + if ( is_null( $v ) || ! is_scalar( $v ) ) { + continue; + } + $v = $this->encodeTemplateString( (string) $v, $table['allow'] ); + $prop = $this->encodeTemplateString( (string) $prop, $table['allow'] ); + $list[] = $prop . '=' . $v; + } + } + + if ( $list ) { + $str .= implode( $table['sep'], $list ); + } + unset( $list ); + } else { + if ( $table['named'] ) { + $str .= $var['name']; + if ( $var['value'] === '' ) { + $str .= $table['ifemp']; + } else { + $str .= '='; + } + } + $list = array(); + if ( is_array( $var['value'] ) ) { + foreach ( $var['value'] as $v ) { + $list[] = $this->encodeTemplateString( $v, $table['allow'] ); + } + } elseif ( is_object( $var['value'] ) ) { + foreach ( $var['value'] as $prop => $v ) { + $list[] = $this->encodeTemplateString( (string) $prop, $table['allow'] ); + $list[] = $this->encodeTemplateString( (string) $v, $table['allow'] ); + } + } + if ( $list ) { + $str .= implode( ',', $list ); + } + unset( $list ); + } + + if ( $str !== '' ) { + $result[] = $str; + } + } + + if ( ! $result ) { + return ''; + } + + $result = implode( $table['sep'], $result ); + + if ( $result !== '' ) { + $result = $table['first'] . $result; + } + + return $result; + } + + /** + * @param string $data + * @param bool $reserved + * @return string + */ + protected function encodeTemplateString( $data, $reserved ): string { + $skip = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~'; + + if ( $reserved ) { + $skip .= ':/?#[]@!$&\'()*+,;='; + } + + $result = ''; + $temp = ''; + for ( $i = 0, $len = strlen( $data ); $i < $len; $i++ ) { + if ( strpos( $skip, $data[ $i ] ) !== false ) { + if ( $temp !== '' ) { + $result .= Uri::encodeComponent( $temp ); + $temp = ''; + } + $result .= $data[ $i ]; + continue; + } + if ( $reserved && $data[ $i ] === '%' ) { + if ( isset( $data[ $i + 1 ] ) && isset( $data[ $i + 2 ] ) + && strpos( 'ABCDEF0123456789', $data[ $i + 1 ] ) !== false + && strpos( 'ABCDEF0123456789', $data[ $i + 2 ] ) !== false ) { + if ( $temp !== '' ) { + $result .= Uri::encodeComponent( $temp ); + } + $result .= '%' . $data[ $i + 1 ] . $data[ $i + 2 ]; + $i += 3; + continue; + } + } + $temp .= $data[ $i ]; + } + + if ( $temp !== '' ) { + $result .= Uri::encodeComponent( $temp ); + } + + return $result; + } + + /** + * @return string + */ + public function value(): string { + return $this->uri; + } + + public function __toString(): string { + return $this->uri; + } + + /** + * @param string $uri + * @return bool + */ + public static function isTemplate( $uri ): bool { + $open = substr_count( $uri, '{' ); + if ( $open === 0 ) { + return false; + } + $close = substr_count( $uri, '}' ); + if ( $open !== $close ) { + return false; + } + + return (bool) preg_match( self::TEMPLATE_REGEX, $uri ); + } + + /** + * @param string $str + * @param int $len + * @return string + */ + protected static function prefix( $str, $len ): string { + if ( $len === 0 ) { + return ''; + } + + if ( $len >= strlen( $str ) ) { + // Prefix is longer than string length + return $str; + } + + return (string) UnicodeString::from( $str )->substring( 0, $len ); + } }