Файловый менеджер - Редактировать - /home/tuudkjt/globeasy/wp-includes/ID3/wordpress-importer.tar
Назад
parsers/class-wxr-parser-simplexml.php 0000777 00000017774 15251156632 0014216 0 ustar 00 <?php /** * WordPress eXtended RSS file parser implementations * * @package WordPress * @subpackage Importer */ /** * WXR Parser that makes use of the SimpleXML PHP extension. */ class WXR_Parser_SimpleXML { public function parse( $file ) { $authors = array(); $posts = array(); $categories = array(); $tags = array(); $terms = array(); $internal_errors = libxml_use_internal_errors( true ); $dom = new DOMDocument(); $old_value = null; if ( function_exists( 'libxml_disable_entity_loader' ) && PHP_VERSION_ID < 80000 ) { $old_value = libxml_disable_entity_loader( true ); } $success = $dom->loadXML( file_get_contents( $file ) ); if ( ! is_null( $old_value ) ) { libxml_disable_entity_loader( $old_value ); } if ( ! $success || isset( $dom->doctype ) ) { return new WP_Error( 'SimpleXML_parse_error', __( 'There was an error when reading this WXR file', 'wordpress-importer' ), libxml_get_errors() ); } $xml = simplexml_import_dom( $dom ); unset( $dom ); // halt if loading produces an error if ( ! $xml ) { return new WP_Error( 'SimpleXML_parse_error', __( 'There was an error when reading this WXR file', 'wordpress-importer' ), libxml_get_errors() ); } $wxr_version = $xml->xpath( '/rss/channel/wp:wxr_version' ); if ( ! $wxr_version ) { return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) ); } $wxr_version = (string) trim( $wxr_version[0] ); // confirm that we are dealing with the correct file format if ( ! preg_match( '/^\d+\.\d+$/', $wxr_version ) ) { return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) ); } $base_url = $xml->xpath( '/rss/channel/wp:base_site_url' ); $base_url = (string) trim( isset( $base_url[0] ) ? $base_url[0] : '' ); $base_blog_url = $xml->xpath( '/rss/channel/wp:base_blog_url' ); if ( $base_blog_url ) { $base_blog_url = (string) trim( $base_blog_url[0] ); } else { $base_blog_url = $base_url; } $namespaces = $xml->getDocNamespaces(); if ( ! isset( $namespaces['wp'] ) ) { $namespaces['wp'] = 'http://wordpress.org/export/1.1/'; } if ( ! isset( $namespaces['excerpt'] ) ) { $namespaces['excerpt'] = 'http://wordpress.org/export/1.1/excerpt/'; } // grab authors foreach ( $xml->xpath( '/rss/channel/wp:author' ) as $author_arr ) { $a = $author_arr->children( $namespaces['wp'] ); $login = (string) $a->author_login; $authors[ $login ] = array( 'author_id' => (int) $a->author_id, 'author_login' => $login, 'author_email' => (string) $a->author_email, 'author_display_name' => (string) $a->author_display_name, 'author_first_name' => (string) $a->author_first_name, 'author_last_name' => (string) $a->author_last_name, ); } // grab cats, tags and terms foreach ( $xml->xpath( '/rss/channel/wp:category' ) as $term_arr ) { $t = $term_arr->children( $namespaces['wp'] ); $category = array( 'term_id' => (int) $t->term_id, 'category_nicename' => (string) $t->category_nicename, 'category_parent' => (string) $t->category_parent, 'cat_name' => (string) $t->cat_name, 'category_description' => (string) $t->category_description, ); foreach ( $t->termmeta as $meta ) { $category['termmeta'][] = array( 'key' => (string) $meta->meta_key, 'value' => (string) $meta->meta_value, ); } $categories[] = $category; } foreach ( $xml->xpath( '/rss/channel/wp:tag' ) as $term_arr ) { $t = $term_arr->children( $namespaces['wp'] ); $tag = array( 'term_id' => (int) $t->term_id, 'tag_slug' => (string) $t->tag_slug, 'tag_name' => (string) $t->tag_name, 'tag_description' => (string) $t->tag_description, ); foreach ( $t->termmeta as $meta ) { $tag['termmeta'][] = array( 'key' => (string) $meta->meta_key, 'value' => (string) $meta->meta_value, ); } $tags[] = $tag; } foreach ( $xml->xpath( '/rss/channel/wp:term' ) as $term_arr ) { $t = $term_arr->children( $namespaces['wp'] ); $term = array( 'term_id' => (int) $t->term_id, 'term_taxonomy' => (string) $t->term_taxonomy, 'slug' => (string) $t->term_slug, 'term_parent' => (string) $t->term_parent, 'term_name' => (string) $t->term_name, 'term_description' => (string) $t->term_description, ); foreach ( $t->termmeta as $meta ) { $term['termmeta'][] = array( 'key' => (string) $meta->meta_key, 'value' => (string) $meta->meta_value, ); } $terms[] = $term; } // grab posts foreach ( $xml->channel->item as $item ) { $post = array( 'post_title' => (string) $item->title, 'guid' => (string) $item->guid, ); $dc = $item->children( 'http://purl.org/dc/elements/1.1/' ); $post['post_author'] = (string) $dc->creator; $content = $item->children( 'http://purl.org/rss/1.0/modules/content/' ); $excerpt = $item->children( $namespaces['excerpt'] ); $post['post_content'] = (string) $content->encoded; $post['post_excerpt'] = (string) $excerpt->encoded; $wp = $item->children( $namespaces['wp'] ); $post['post_id'] = (int) $wp->post_id; $post['post_date'] = (string) $wp->post_date; $post['post_date_gmt'] = (string) $wp->post_date_gmt; $post['comment_status'] = (string) $wp->comment_status; $post['ping_status'] = (string) $wp->ping_status; $post['post_name'] = (string) $wp->post_name; $post['status'] = (string) $wp->status; $post['post_parent'] = (int) $wp->post_parent; $post['menu_order'] = (int) $wp->menu_order; $post['post_type'] = (string) $wp->post_type; $post['post_password'] = (string) $wp->post_password; $post['is_sticky'] = (int) $wp->is_sticky; if ( isset( $wp->attachment_url ) ) { $post['attachment_url'] = (string) $wp->attachment_url; } foreach ( $item->category as $c ) { $att = $c->attributes(); if ( isset( $att['nicename'] ) ) { $post['terms'][] = array( 'name' => (string) $c, 'slug' => (string) $att['nicename'], 'domain' => (string) $att['domain'], ); } } foreach ( $wp->postmeta as $meta ) { $post['postmeta'][] = array( 'key' => (string) $meta->meta_key, 'value' => (string) $meta->meta_value, ); } foreach ( $wp->comment as $comment ) { $meta = array(); if ( isset( $comment->commentmeta ) ) { foreach ( $comment->commentmeta as $m ) { $meta[] = array( 'key' => (string) $m->meta_key, 'value' => (string) $m->meta_value, ); } } $post['comments'][] = array( 'comment_id' => (int) $comment->comment_id, 'comment_author' => (string) $comment->comment_author, 'comment_author_email' => (string) $comment->comment_author_email, 'comment_author_IP' => (string) $comment->comment_author_IP, 'comment_author_url' => (string) $comment->comment_author_url, 'comment_date' => (string) $comment->comment_date, 'comment_date_gmt' => (string) $comment->comment_date_gmt, 'comment_content' => (string) $comment->comment_content, 'comment_approved' => (string) $comment->comment_approved, 'comment_type' => (string) $comment->comment_type, 'comment_parent' => (string) $comment->comment_parent, 'comment_user_id' => (int) $comment->comment_user_id, 'commentmeta' => $meta, ); } $posts[] = $post; } return array( 'authors' => $authors, 'posts' => $posts, 'categories' => $categories, 'tags' => $tags, 'terms' => $terms, 'base_url' => $base_url, 'base_blog_url' => $base_blog_url, 'version' => $wxr_version, ); } } parsers/class-wxr-parser.php 0000777 00000005774 15251156632 0012203 0 ustar 00 <?php /** * WordPress eXtended RSS file parser implementations * * @package WordPress * @subpackage Importer */ /** * WordPress Importer class for managing parsing of WXR files. */ class WXR_Parser { public function parse( $file ) { // Allow forcing a specific parser via WXR_PARSER: simplexml|xml|regex|xmlprocessor $preferred_parser = defined( 'PREFERRED_WXR_PARSER' ) ? constant( 'PREFERRED_WXR_PARSER' ) : null; if ( $preferred_parser ) { $available_parsers = array( 'simplexml' => 'WXR_Parser_SimpleXML', 'xml' => 'WXR_Parser_XML', 'regex' => 'WXR_Parser_Regex', 'xmlprocessor' => 'WXR_Parser_XML_Processor', ); if ( isset( $available_parsers[ $preferred_parser ] ) ) { $parser = new $available_parsers[ $preferred_parser ](); $result = $parser->parse( $file ); } else { _doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid parser specified: %s', 'wordpress-importer' ), $preferred_parser ), '0.9.0' ); $result = new WP_Error( 'invalid_parser', sprintf( __( 'Invalid parser specified: %s', 'wordpress-importer' ), $preferred_parser ) ); } // If XMLParser succeeds or this is an invalid WXR file then return the results if ( ! is_wp_error( $result ) || 'XML_parse_error' != $result->get_error_code() ) { return $result; } } else { // Attempt to auto-select the best XML parser based on available extensions if ( extension_loaded( 'simplexml' ) ) { $parser = new WXR_Parser_SimpleXML(); $result = $parser->parse( $file ); // If SimpleXML succeeds or this is an invalid WXR file then return the results if ( ! is_wp_error( $result ) || 'SimpleXML_parse_error' != $result->get_error_code() ) { return $result; } } elseif ( extension_loaded( 'xml' ) ) { $parser = new WXR_Parser_XML(); $result = $parser->parse( $file ); // If XMLParser succeeds or this is an invalid WXR file then return the results if ( ! is_wp_error( $result ) || 'XML_parse_error' != $result->get_error_code() ) { return $result; } } } // We have a malformed XML file, so display the error and fallthrough to regex if ( isset( $result ) && defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { echo '<pre>'; if ( 'SimpleXML_parse_error' == $result->get_error_code() ) { foreach ( $result->get_error_data() as $error ) { echo $error->line . ':' . $error->column . ' ' . esc_html( $error->message ) . "\n"; } } elseif ( 'XML_parse_error' == $result->get_error_code() ) { $error = $result->get_error_data(); echo $error[0] . ':' . $error[1] . ' ' . esc_html( $error[2] ); } echo '</pre>'; echo '<p><strong>' . __( 'There was an error when reading this WXR file', 'wordpress-importer' ) . '</strong><br />'; echo __( 'Details are shown above. The importer will now try again with a different parser...', 'wordpress-importer' ) . '</p>'; } // use regular expressions if nothing else available or this is bad XML $parser = new WXR_Parser_XML_Processor(); return $parser->parse( $file ); } } parsers/class-wxr-parser-xml-processor.php 0000777 00000033437 15251156632 0015013 0 ustar 00 <?php /** * WordPress eXtended RSS file parser implementations * * @package WordPress * @subpackage Importer */ use WordPress\ByteStream\ReadStream\FileReadStream; /** * WXR Parser that uses the XMLProcessor component. */ class WXR_Parser_XML_Processor { public $authors = array(); public $posts = array(); public $categories = array(); public $tags = array(); public $terms = array(); public $base_url = ''; public $base_blog_url = ''; /** * Parse a WXR file * * @param string $file Path to WXR file * @return array|WP_Error Parsed data or error object */ public function parse( $file ) { // Trigger a warning for non-existent files to match legacy behavior and tests. if ( ! is_readable( $file ) ) { // Intentionally trigger a PHP warning; return value is ignored. file_get_contents( $file ); } // Initialize variables $this->authors = array(); $this->posts = array(); $this->categories = array(); $this->tags = array(); $this->terms = array(); $this->base_url = ''; $this->base_blog_url = ''; $wxr_version = ''; try { $reader = $this->create_wxr_entity_reader( $file ); // Parse the XML document $last_term = null; while ( $reader->next_entity() ) { $entity = $reader->get_entity(); $trimmed_data = array(); foreach ( $entity->get_data() as $k => $v ) { if ( ! is_string( $v ) ) { $trimmed_data[ $k ] = $v; continue; } $trimmed_data[ $k ] = $v; } switch ( $entity->get_type() ) { case 'wxr_version': $wxr_version = $trimmed_data['wxr_version']; break; case 'site_option': if ( isset( $trimmed_data['option_name'], $trimmed_data['option_value'] ) ) { switch ( $trimmed_data['option_name'] ) { case 'wxr_version': $wxr_version = $trimmed_data['option_value']; break; case 'siteurl': $this->base_url = $trimmed_data['option_value']; break; case 'home': $this->base_blog_url = $trimmed_data['option_value']; break; } } break; case 'user': $key = isset( $trimmed_data['author_login'] ) ? $trimmed_data['author_login'] : ( isset( $trimmed_data['author_email'] ) ? $trimmed_data['author_email'] : ( isset( $trimmed_data['author_id'] ) ? $trimmed_data['author_id'] : count( $this->authors ) ) ); $this->authors[ $key ] = $trimmed_data; break; case 'post': $this->posts[] = $trimmed_data; break; case 'post_meta': $last_post_key = count( $this->posts ) - 1; if ( ! isset( $this->posts[ $last_post_key ]['postmeta'] ) ) { $this->posts[ $last_post_key ]['postmeta'] = array(); } // Ensure only expected keys 'key' and 'value' are present to match tests if ( isset( $trimmed_data['post_id'] ) ) { unset( $trimmed_data['post_id'] ); } $this->posts[ $last_post_key ]['postmeta'][] = $trimmed_data; break; case 'comment': $last_post_key = count( $this->posts ) - 1; if ( ! isset( $this->posts[ $last_post_key ]['comments'] ) ) { $this->posts[ $last_post_key ]['comments'] = array(); } $trimmed_data['commentmeta'] = array(); $this->posts[ $last_post_key ]['comments'][] = $trimmed_data; break; case 'comment_meta': $last_post_key = count( $this->posts ) - 1; $last_comment_index = count( $this->posts[ $last_post_key ]['comments'] ) - 1; if ( $last_comment_index >= 0 ) { // Do not include comment_id in the final commentmeta array to match expected shape. if ( isset( $trimmed_data['comment_id'] ) ) { unset( $trimmed_data['comment_id'] ); } $this->posts[ $last_post_key ]['comments'][ $last_comment_index ]['commentmeta'][] = $trimmed_data; } break; case 'category': if ( isset( $trimmed_data['term_id'] ) ) { $trimmed_data['term_id'] = (int) $trimmed_data['term_id']; } unset( $trimmed_data['taxonomy'], $trimmed_data['term_description'] ); $this->categories[] = $trimmed_data; $last_term_index = count( $this->categories ) - 1; $last_term = &$this->categories[ $last_term_index ]; break; case 'tag': if ( isset( $trimmed_data['term_id'] ) ) { $trimmed_data['term_id'] = (int) $trimmed_data['term_id']; } unset( $trimmed_data['taxonomy'], $trimmed_data['term_description'] ); $this->tags[] = $trimmed_data; $last_term_index = count( $this->tags ) - 1; $last_term = &$this->tags[ $last_term_index ]; break; case 'term': if ( isset( $trimmed_data['term_id'] ) ) { $trimmed_data['term_id'] = (int) $trimmed_data['term_id']; } // unset($trimmed_data['taxonomy'], $trimmed_data['term_description']); // $trimmed_data['taxonomy'] id 'domain' // $trimmed_data['slug'] id 'nicename' $this->terms[] = $trimmed_data; $last_term_index = count( $this->terms ) - 1; $last_term = &$this->terms[ $last_term_index ]; break; case 'termmeta': case 'term_meta': if ( ! isset( $last_term['termmeta'] ) ) { $last_term['termmeta'] = array(); } $last_term['termmeta'][] = $trimmed_data; break; case 'wxr_version': // Support entity-style wxr_version array or raw string if ( isset( $trimmed_data['wxr_version'] ) ) { $wxr_version = $trimmed_data['wxr_version']; } else { $wxr_version = $trimmed_data; } break; default: // Ignore unknown entity types silently to avoid emitting notices. break; } } } catch ( Exception $e ) { return new WP_Error( 'WXR_parse_error', $e->getMessage() ); } // Normalize per-post terms to legacy shape { domain, slug, name } when needed. foreach ( $this->posts as $idx => $post ) { if ( isset( $post['terms'] ) && is_array( $post['terms'] ) ) { foreach ( $post['terms'] as $tidx => $term ) { if ( ! isset( $term['domain'] ) && isset( $term['taxonomy'] ) ) { $mapped = array( 'domain' => $term['taxonomy'], 'slug' => isset( $term['slug'] ) ? $term['slug'] : '', 'name' => isset( $term['description'] ) ? $term['description'] : '', ); $this->posts[ $idx ]['terms'][ $tidx ] = $mapped; } } } } // Validate WXR version if ( empty( $wxr_version ) || ! preg_match( '/^\d+\.\d+$/', $wxr_version ) ) { return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) ); } return array( 'authors' => $this->authors, 'posts' => $this->posts, 'categories' => $this->categories, 'tags' => $this->tags, 'terms' => $this->terms, 'base_url' => $this->base_url, 'base_blog_url' => $this->base_blog_url, 'version' => $wxr_version, ); } private function create_wxr_entity_reader( $file ) { // Every XML element is a combination of a long-form namespace and a // local element name, e.g. a syntax <wp:post_id> could actually refer // to a (https://wordpress.org/export/1.0/, post_id) element. // // Namespaces are paramount for parsing XML and cannot be ignored. Elements // element must be matched based on both their namespace and local name. // // Unfortunately, different WXR files defined the `wp` namespace in a different way. // Folks use a mixture of HTTP vs HTTPS protocols and version numbers. We must // account for all possible options to parse these documents correctly. $wxr_namespaces = array( 'http://wordpress.org/export/1.0/', 'https://wordpress.org/export/1.0/', 'http://wordpress.org/export/1.1/', 'https://wordpress.org/export/1.1/', 'http://wordpress.org/export/1.2/', 'https://wordpress.org/export/1.2/', ); $known_entities = array( 'item' => array( 'type' => 'post', 'fields' => array( 'title' => 'post_title', 'guid' => 'guid', 'description' => 'post_excerpt', '{http://purl.org/dc/elements/1.1/}creator' => 'post_author', '{http://purl.org/rss/1.0/modules/content/}encoded' => 'post_content', '{http://wordpress.org/export/1.0/excerpt/}encoded' => 'post_excerpt', '{http://wordpress.org/export/1.1/excerpt/}encoded' => 'post_excerpt', '{http://wordpress.org/export/1.2/excerpt/}encoded' => 'post_excerpt', ), ), ); $known_site_options = array(); foreach ( $wxr_namespaces as $wxr_namespace ) { $known_site_options = array_merge( $known_site_options, array( '{' . $wxr_namespace . '}base_blog_url' => 'home', '{' . $wxr_namespace . '}base_site_url' => 'siteurl', '{' . $wxr_namespace . '}wxr_version' => 'wxr_version', 'title' => 'blogname', ) ); $known_entities['item']['fields'] = array_merge( $known_entities['item']['fields'], array( '{' . $wxr_namespace . '}post_id' => 'post_id', '{' . $wxr_namespace . '}status' => 'status', '{' . $wxr_namespace . '}post_date' => 'post_date', '{' . $wxr_namespace . '}post_date_gmt' => 'post_date_gmt', '{' . $wxr_namespace . '}post_modified' => 'post_modified', '{' . $wxr_namespace . '}post_modified_gmt' => 'post_modified_gmt', '{' . $wxr_namespace . '}comment_status' => 'comment_status', '{' . $wxr_namespace . '}ping_status' => 'ping_status', '{' . $wxr_namespace . '}post_name' => 'post_name', '{' . $wxr_namespace . '}post_parent' => 'post_parent', '{' . $wxr_namespace . '}menu_order' => 'menu_order', '{' . $wxr_namespace . '}post_type' => 'post_type', '{' . $wxr_namespace . '}post_password' => 'post_password', '{' . $wxr_namespace . '}is_sticky' => 'is_sticky', '{' . $wxr_namespace . '}attachment_url' => 'attachment_url', ) ); $known_entities = array_merge( $known_entities, array( '{' . $wxr_namespace . '}comment' => array( 'type' => 'comment', 'fields' => array( '{' . $wxr_namespace . '}comment_id' => 'comment_id', '{' . $wxr_namespace . '}comment_author' => 'comment_author', '{' . $wxr_namespace . '}comment_author_email' => 'comment_author_email', '{' . $wxr_namespace . '}comment_author_url' => 'comment_author_url', '{' . $wxr_namespace . '}comment_author_IP' => 'comment_author_IP', '{' . $wxr_namespace . '}comment_date' => 'comment_date', '{' . $wxr_namespace . '}comment_date_gmt' => 'comment_date_gmt', '{' . $wxr_namespace . '}comment_content' => 'comment_content', '{' . $wxr_namespace . '}comment_approved' => 'comment_approved', '{' . $wxr_namespace . '}comment_type' => 'comment_type', '{' . $wxr_namespace . '}comment_parent' => 'comment_parent', '{' . $wxr_namespace . '}comment_user_id' => 'comment_user_id', ), ), '{' . $wxr_namespace . '}commentmeta' => array( 'type' => 'comment_meta', 'fields' => array( '{' . $wxr_namespace . '}meta_key' => 'key', '{' . $wxr_namespace . '}meta_value' => 'value', ), ), '{' . $wxr_namespace . '}author' => array( 'type' => 'user', 'fields' => array( '{' . $wxr_namespace . '}author_id' => 'author_id', '{' . $wxr_namespace . '}author_login' => 'author_login', '{' . $wxr_namespace . '}author_email' => 'author_email', '{' . $wxr_namespace . '}author_display_name' => 'author_display_name', '{' . $wxr_namespace . '}author_first_name' => 'author_first_name', '{' . $wxr_namespace . '}author_last_name' => 'author_last_name', ), ), '{' . $wxr_namespace . '}postmeta' => array( 'type' => 'post_meta', 'fields' => array( '{' . $wxr_namespace . '}meta_key' => 'key', '{' . $wxr_namespace . '}meta_value' => 'value', ), ), '{' . $wxr_namespace . '}term' => array( 'type' => 'term', 'fields' => array( '{' . $wxr_namespace . '}term_id' => 'term_id', '{' . $wxr_namespace . '}term_taxonomy' => 'term_taxonomy', '{' . $wxr_namespace . '}term_slug' => 'slug', '{' . $wxr_namespace . '}term_parent' => 'term_parent', '{' . $wxr_namespace . '}term_name' => 'term_name', '{' . $wxr_namespace . '}term_description' => 'term_description', ), ), '{' . $wxr_namespace . '}termmeta' => array( 'type' => 'term_meta', 'fields' => array( '{' . $wxr_namespace . '}meta_key' => 'key', '{' . $wxr_namespace . '}meta_value' => 'value', ), ), '{' . $wxr_namespace . '}tag' => array( 'type' => 'tag', 'fields' => array( '{' . $wxr_namespace . '}term_id' => 'term_id', '{' . $wxr_namespace . '}tag_slug' => 'tag_slug', '{' . $wxr_namespace . '}tag_name' => 'tag_name', '{' . $wxr_namespace . '}tag_description' => 'tag_description', ), ), '{' . $wxr_namespace . '}category' => array( 'type' => 'category', 'fields' => array( '{' . $wxr_namespace . '}term_id' => 'term_id', '{' . $wxr_namespace . '}category_nicename' => 'category_nicename', '{' . $wxr_namespace . '}category_parent' => 'category_parent', '{' . $wxr_namespace . '}cat_name' => 'cat_name', '{' . $wxr_namespace . '}category_description' => 'category_description', ), ), ) ); } return WordPress\DataLiberation\EntityReader\WXREntityReader::create( FileReadStream::from_path( $file ), null, array( 'known_site_options' => $known_site_options, 'known_entities' => $known_entities, 'use_legacy_post_term_keys' => true, 'remap_wp_author' => false, ) ); } } parsers/class-wxr-parser-regex.php 0000777 00000026561 15251156632 0013310 0 ustar 00 <?php /** * WordPress eXtended RSS file parser implementations * * @package WordPress * @subpackage Importer */ /** * WXR Parser that uses regular expressions. Fallback for installs without an XML parser. */ class WXR_Parser_Regex { public $authors = array(); public $posts = array(); public $categories = array(); public $tags = array(); public $terms = array(); public $base_url = ''; public $base_blog_url = ''; public $has_gzip; public function __construct() { $this->has_gzip = is_callable( 'gzopen' ); } public function parse( $file ) { $wxr_version = false; $in_multiline = false; $multiline_content = ''; $multiline_tags = array( 'item' => array( 'posts', array( $this, 'process_post' ) ), 'wp:category' => array( 'categories', array( $this, 'process_category' ) ), 'wp:tag' => array( 'tags', array( $this, 'process_tag' ) ), 'wp:term' => array( 'terms', array( $this, 'process_term' ) ), ); $fp = $this->fopen( $file, 'r' ); if ( $fp ) { while ( ! $this->feof( $fp ) ) { $is_tag_line = false; $importline = rtrim( $this->fgets( $fp ) ); if ( ! $wxr_version && preg_match( '|<wp:wxr_version>(\d+\.\d+)</wp:wxr_version>|', $importline, $version ) ) { $wxr_version = isset( $version[1] ) ? $version[1] : ''; } if ( false !== strpos( $importline, '<wp:base_site_url>' ) ) { preg_match( '|<wp:base_site_url>(.*?)</wp:base_site_url>|is', $importline, $url ); $this->base_url = isset( $url[1] ) ? $url[1] : ''; continue; } if ( false !== strpos( $importline, '<wp:base_blog_url>' ) ) { preg_match( '|<wp:base_blog_url>(.*?)</wp:base_blog_url>|is', $importline, $blog_url ); $this->base_blog_url = isset( $blog_url[1] ) ? $blog_url[1] : ''; continue; } elseif ( empty( $this->base_blog_url ) ) { $this->base_blog_url = $this->base_url; } if ( false !== strpos( $importline, '<wp:author>' ) ) { preg_match( '|<wp:author>(.*?)</wp:author>|is', $importline, $author ); if ( isset( $author[1] ) ) { $a = $this->process_author( $author[1] ); $this->authors[ $a['author_login'] ] = $a; } continue; } foreach ( $multiline_tags as $tag => $handler ) { // Handle multi-line tags on a singular line $pos = strpos( $importline, "<$tag>" ); $pos_closing = strpos( $importline, "</$tag>" ); if ( preg_match( '|<' . $tag . '>(.*?)</' . $tag . '>|is', $importline, $matches ) ) { $this->{$handler[0]}[] = call_user_func( $handler[1], isset( $matches[1] ) ? $matches[1] : '' ); } elseif ( false !== $pos ) { // Take note of any content after the opening tag $multiline_content = trim( substr( $importline, $pos + strlen( $tag ) + 2 ) ); // We don't want to have this line added to `$is_multiline` below. $in_multiline = $tag; $is_tag_line = true; } elseif ( false !== $pos_closing ) { $in_multiline = false; $multiline_content .= trim( substr( $importline, 0, $pos_closing ) ); $this->{$handler[0]}[] = call_user_func( $handler[1], $multiline_content ); } } if ( $in_multiline && ! $is_tag_line ) { $multiline_content .= $importline . "\n"; } } $this->fclose( $fp ); } if ( ! $wxr_version ) { return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) ); } return array( 'authors' => $this->authors, 'posts' => $this->posts, 'categories' => $this->categories, 'tags' => $this->tags, 'terms' => $this->terms, 'base_url' => $this->base_url, 'base_blog_url' => $this->base_blog_url, 'version' => $wxr_version, ); } public function get_tag( $text, $tag ) { if ( null === $text ) { return ''; } preg_match( "|<$tag.*?>(.*?)</$tag>|is", $text, $return ); if ( isset( $return[1] ) ) { if ( substr( $return[1], 0, 9 ) == '<![CDATA[' ) { if ( strpos( $return[1], ']]]]><![CDATA[>' ) !== false ) { preg_match_all( '|<!\[CDATA\[(.*?)\]\]>|s', $return[1], $matches ); $return = ''; if ( isset( $matches[1] ) ) { foreach ( $matches[1] as $match ) { $return .= $match; } } } else { $return = preg_replace( '|^<!\[CDATA\[(.*)\]\]>$|s', '$1', $return[1] ); } } else { $return = $return[1]; } } else { $return = ''; } return $return; } public function process_category( $c ) { $term = array( 'term_id' => $this->get_tag( $c, 'wp:term_id' ), 'cat_name' => $this->get_tag( $c, 'wp:cat_name' ), 'category_nicename' => $this->get_tag( $c, 'wp:category_nicename' ), 'category_parent' => $this->get_tag( $c, 'wp:category_parent' ), 'category_description' => $this->get_tag( $c, 'wp:category_description' ), ); $term_meta = $this->process_meta( $c, 'wp:termmeta' ); if ( ! empty( $term_meta ) ) { $term['termmeta'] = $term_meta; } return $term; } public function process_tag( $t ) { $term = array( 'term_id' => $this->get_tag( $t, 'wp:term_id' ), 'tag_name' => $this->get_tag( $t, 'wp:tag_name' ), 'tag_slug' => $this->get_tag( $t, 'wp:tag_slug' ), 'tag_description' => $this->get_tag( $t, 'wp:tag_description' ), ); $term_meta = $this->process_meta( $t, 'wp:termmeta' ); if ( ! empty( $term_meta ) ) { $term['termmeta'] = $term_meta; } return $term; } public function process_term( $t ) { $term = array( 'term_id' => $this->get_tag( $t, 'wp:term_id' ), 'term_taxonomy' => $this->get_tag( $t, 'wp:term_taxonomy' ), 'slug' => $this->get_tag( $t, 'wp:term_slug' ), 'term_parent' => $this->get_tag( $t, 'wp:term_parent' ), 'term_name' => $this->get_tag( $t, 'wp:term_name' ), 'term_description' => $this->get_tag( $t, 'wp:term_description' ), ); $term_meta = $this->process_meta( $t, 'wp:termmeta' ); if ( ! empty( $term_meta ) ) { $term['termmeta'] = $term_meta; } return $term; } public function process_meta( $text, $tag ) { $parsed_meta = array(); preg_match_all( "|<$tag>(.+?)</$tag>|is", $text, $meta ); if ( ! isset( $meta[1] ) ) { return $parsed_meta; } foreach ( $meta[1] as $m ) { $parsed_meta[] = array( 'key' => $this->get_tag( $m, 'wp:meta_key' ), 'value' => $this->get_tag( $m, 'wp:meta_value' ), ); } return $parsed_meta; } public function process_author( $a ) { return array( 'author_id' => $this->get_tag( $a, 'wp:author_id' ), 'author_login' => $this->get_tag( $a, 'wp:author_login' ), 'author_email' => $this->get_tag( $a, 'wp:author_email' ), 'author_display_name' => $this->get_tag( $a, 'wp:author_display_name' ), 'author_first_name' => $this->get_tag( $a, 'wp:author_first_name' ), 'author_last_name' => $this->get_tag( $a, 'wp:author_last_name' ), ); } public function process_post( $post ) { $post_id = $this->get_tag( $post, 'wp:post_id' ); $post_title = $this->get_tag( $post, 'title' ); $post_date = $this->get_tag( $post, 'wp:post_date' ); $post_date_gmt = $this->get_tag( $post, 'wp:post_date_gmt' ); $comment_status = $this->get_tag( $post, 'wp:comment_status' ); $ping_status = $this->get_tag( $post, 'wp:ping_status' ); $status = $this->get_tag( $post, 'wp:status' ); $post_name = $this->get_tag( $post, 'wp:post_name' ); $post_parent = $this->get_tag( $post, 'wp:post_parent' ); $menu_order = $this->get_tag( $post, 'wp:menu_order' ); $post_type = $this->get_tag( $post, 'wp:post_type' ); $post_password = $this->get_tag( $post, 'wp:post_password' ); $is_sticky = $this->get_tag( $post, 'wp:is_sticky' ); $guid = $this->get_tag( $post, 'guid' ); $post_author = $this->get_tag( $post, 'dc:creator' ); $post_excerpt = $this->get_tag( $post, 'excerpt:encoded' ); $post_excerpt = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_excerpt ); $post_excerpt = str_replace( '<br>', '<br />', $post_excerpt ); $post_excerpt = str_replace( '<hr>', '<hr />', $post_excerpt ); $post_content = $this->get_tag( $post, 'content:encoded' ); $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content ); $post_content = str_replace( '<br>', '<br />', $post_content ); $post_content = str_replace( '<hr>', '<hr />', $post_content ); $postdata = compact( 'post_id', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_excerpt', 'post_title', 'status', 'post_name', 'comment_status', 'ping_status', 'guid', 'post_parent', 'menu_order', 'post_type', 'post_password', 'is_sticky' ); $attachment_url = $this->get_tag( $post, 'wp:attachment_url' ); if ( $attachment_url ) { $postdata['attachment_url'] = $attachment_url; } preg_match_all( '|<category domain="([^"]+?)" nicename="([^"]+?)">(.+?)</category>|is', $post, $terms, PREG_SET_ORDER ); foreach ( $terms as $t ) { $post_terms[] = array( 'slug' => $t[2], 'domain' => $t[1], 'name' => str_replace( array( '<![CDATA[', ']]>' ), '', $t[3] ), ); } if ( ! empty( $post_terms ) ) { $postdata['terms'] = $post_terms; } preg_match_all( '|<wp:comment>(.+?)</wp:comment>|is', $post, $comments ); $comments = $comments[1]; if ( $comments ) { foreach ( $comments as $comment ) { $post_comments[] = array( 'comment_id' => $this->get_tag( $comment, 'wp:comment_id' ), 'comment_author' => $this->get_tag( $comment, 'wp:comment_author' ), 'comment_author_email' => $this->get_tag( $comment, 'wp:comment_author_email' ), 'comment_author_IP' => $this->get_tag( $comment, 'wp:comment_author_IP' ), 'comment_author_url' => $this->get_tag( $comment, 'wp:comment_author_url' ), 'comment_date' => $this->get_tag( $comment, 'wp:comment_date' ), 'comment_date_gmt' => $this->get_tag( $comment, 'wp:comment_date_gmt' ), 'comment_content' => $this->get_tag( $comment, 'wp:comment_content' ), 'comment_approved' => $this->get_tag( $comment, 'wp:comment_approved' ), 'comment_type' => $this->get_tag( $comment, 'wp:comment_type' ), 'comment_parent' => $this->get_tag( $comment, 'wp:comment_parent' ), 'comment_user_id' => $this->get_tag( $comment, 'wp:comment_user_id' ), 'commentmeta' => $this->process_meta( $comment, 'wp:commentmeta' ), ); } } if ( ! empty( $post_comments ) ) { $postdata['comments'] = $post_comments; } $post_meta = $this->process_meta( $post, 'wp:postmeta' ); if ( ! empty( $post_meta ) ) { $postdata['postmeta'] = $post_meta; } return $postdata; } public function _normalize_tag( $matches ) { return '<' . strtolower( $matches[1] ); } public function fopen( $filename, $mode = 'r' ) { if ( $this->has_gzip ) { return gzopen( $filename, $mode ); } return fopen( $filename, $mode ); } public function feof( $fp ) { if ( $this->has_gzip ) { return gzeof( $fp ); } return feof( $fp ); } public function fgets( $fp, $len = 8192 ) { if ( $this->has_gzip ) { return gzgets( $fp, $len ); } return fgets( $fp, $len ); } public function fclose( $fp ) { if ( $this->has_gzip ) { return gzclose( $fp ); } return fclose( $fp ); } } parsers/class-wxr-parser-xml.php 0000777 00000015422 15251156632 0012770 0 ustar 00 <?php /** * WordPress eXtended RSS file parser implementations * * @package WordPress * @subpackage Importer */ /** * WXR Parser that makes use of the XML Parser PHP extension. */ class WXR_Parser_XML { public $wp_tags = array( 'wp:post_id', 'wp:post_date', 'wp:post_date_gmt', 'wp:comment_status', 'wp:ping_status', 'wp:attachment_url', 'wp:status', 'wp:post_name', 'wp:post_parent', 'wp:menu_order', 'wp:post_type', 'wp:post_password', 'wp:is_sticky', 'wp:term_id', 'wp:category_nicename', 'wp:category_parent', 'wp:cat_name', 'wp:category_description', 'wp:tag_slug', 'wp:tag_name', 'wp:tag_description', 'wp:term_taxonomy', 'wp:term_parent', 'wp:term_name', 'wp:term_description', 'wp:author_id', 'wp:author_login', 'wp:author_email', 'wp:author_display_name', 'wp:author_first_name', 'wp:author_last_name', ); public $wp_sub_tags = array( 'wp:comment_id', 'wp:comment_author', 'wp:comment_author_email', 'wp:comment_author_url', 'wp:comment_author_IP', 'wp:comment_date', 'wp:comment_date_gmt', 'wp:comment_content', 'wp:comment_approved', 'wp:comment_type', 'wp:comment_parent', 'wp:comment_user_id', ); public $wxr_version; public $in_post; public $cdata; public $data; public $sub_data; public $in_tag; public $in_sub_tag; public $authors; public $posts; public $term; public $category; public $tag; public $base_url; public $base_blog_url; public function parse( $file ) { $this->wxr_version = false; $this->in_post = false; $this->cdata = false; $this->data = false; $this->sub_data = false; $this->in_tag = false; $this->in_sub_tag = false; $this->authors = array(); $this->posts = array(); $this->term = array(); $this->category = array(); $this->tag = array(); $xml = xml_parser_create( 'UTF-8' ); xml_parser_set_option( $xml, XML_OPTION_SKIP_WHITE, 1 ); xml_parser_set_option( $xml, XML_OPTION_CASE_FOLDING, 0 ); xml_set_character_data_handler( $xml, array( $this, 'cdata' ) ); xml_set_element_handler( $xml, array( $this, 'tag_open' ), array( $this, 'tag_close' ) ); if ( ! xml_parse( $xml, file_get_contents( $file ), true ) ) { $current_line = xml_get_current_line_number( $xml ); $current_column = xml_get_current_column_number( $xml ); $error_code = xml_get_error_code( $xml ); $error_string = xml_error_string( $error_code ); return new WP_Error( 'XML_parse_error', 'There was an error when reading this WXR file', array( $current_line, $current_column, $error_string ) ); } xml_parser_free( $xml ); if ( ! preg_match( '/^\d+\.\d+$/', $this->wxr_version ) ) { return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) ); } return array( 'authors' => $this->authors, 'posts' => $this->posts, 'categories' => $this->category, 'tags' => $this->tag, 'terms' => $this->term, 'base_url' => $this->base_url, 'base_blog_url' => $this->base_blog_url, 'version' => $this->wxr_version, ); } public function tag_open( $parse, $tag, $attr ) { if ( in_array( $tag, $this->wp_tags, true ) ) { $this->in_tag = substr( $tag, 3 ); return; } if ( in_array( $tag, $this->wp_sub_tags, true ) ) { $this->in_sub_tag = substr( $tag, 3 ); return; } switch ( $tag ) { case 'category': if ( isset( $attr['domain'], $attr['nicename'] ) ) { if ( false === $this->sub_data ) { $this->sub_data = array(); } $this->sub_data['domain'] = $attr['domain']; $this->sub_data['slug'] = $attr['nicename']; } break; case 'item': $this->in_post = true; break; case 'title': if ( $this->in_post ) { $this->in_tag = 'post_title'; } break; case 'guid': $this->in_tag = 'guid'; break; case 'dc:creator': $this->in_tag = 'post_author'; break; case 'content:encoded': $this->in_tag = 'post_content'; break; case 'excerpt:encoded': $this->in_tag = 'post_excerpt'; break; case 'wp:term_slug': $this->in_tag = 'slug'; break; case 'wp:meta_key': $this->in_sub_tag = 'key'; break; case 'wp:meta_value': $this->in_sub_tag = 'value'; break; } } public function cdata( $parser, $cdata ) { if ( ! trim( $cdata ) ) { return; } if ( false !== $this->in_tag || false !== $this->in_sub_tag ) { $this->cdata .= $cdata; } else { $this->cdata .= trim( $cdata ); } } public function tag_close( $parser, $tag ) { switch ( $tag ) { case 'wp:comment': unset( $this->sub_data['key'], $this->sub_data['value'] ); // remove meta sub_data if ( ! empty( $this->sub_data ) ) { $this->data['comments'][] = $this->sub_data; } $this->sub_data = false; break; case 'wp:commentmeta': $this->sub_data['commentmeta'][] = array( 'key' => $this->sub_data['key'], 'value' => $this->sub_data['value'], ); break; case 'category': if ( ! empty( $this->sub_data ) ) { $this->sub_data['name'] = $this->cdata; $this->data['terms'][] = $this->sub_data; } $this->sub_data = false; break; case 'wp:postmeta': if ( ! empty( $this->sub_data ) ) { $this->data['postmeta'][] = $this->sub_data; } $this->sub_data = false; break; case 'item': $this->posts[] = $this->data; $this->data = false; break; case 'wp:category': case 'wp:tag': case 'wp:term': $n = substr( $tag, 3 ); array_push( $this->$n, $this->data ); $this->data = false; break; case 'wp:termmeta': if ( ! empty( $this->sub_data ) ) { $this->data['termmeta'][] = $this->sub_data; } $this->sub_data = false; break; case 'wp:author': if ( ! empty( $this->data['author_login'] ) ) { $this->authors[ $this->data['author_login'] ] = $this->data; } $this->data = false; break; case 'wp:base_site_url': $this->base_url = $this->cdata; if ( ! isset( $this->base_blog_url ) ) { $this->base_blog_url = $this->cdata; } break; case 'wp:base_blog_url': $this->base_blog_url = $this->cdata; break; case 'wp:wxr_version': $this->wxr_version = $this->cdata; break; default: if ( $this->in_sub_tag ) { if ( false === $this->sub_data ) { $this->sub_data = array(); } $this->sub_data[ $this->in_sub_tag ] = ! empty( $this->cdata ) ? $this->cdata : ''; $this->in_sub_tag = false; } elseif ( $this->in_tag ) { if ( false === $this->data ) { $this->data = array(); } $this->data[ $this->in_tag ] = ! empty( $this->cdata ) ? $this->cdata : ''; $this->in_tag = false; } } $this->cdata = false; } } php-toolkit/Encoding/compat-utf8.php 0000777 00000046210 15251156632 0013444 0 ustar 00 <?php namespace WordPress\Encoding\compat; /** * Finds spans of valid and invalid UTF-8 bytes in a given string. * * This is a low-level tool to power various UTF-8 functionality. * It scans through a string until it finds invalid byte spans. * When it does this, it does three things: * * - Assigns `$at` to the position after the last successful code point. * - Assigns `$invalid_length` to the length of the maximal subpart of * the invalid bytes starting at `$at`. * - Returns how many code points were successfully scanned. * * This information is enough to build a number of useful UTF-8 functions. * * Example: * * // ñ is U+F1, which in `ISO-8859-1`/`latin1`/`Windows-1252`/`cp1252` is 0xF1. * "Pi\xF1a" === $pineapple = mb_convert_encoding( "Piña", 'Windows-1252', 'UTF-8' ); * $at = $invalid_length = 0; * * // The first step finds the invalid 0xF1 byte. * 2 === _wp_scan_utf8( $pineapple, $at, $invalid_length ); * $at === 2; $invalid_length === 1; * * // The second step continues to the end of the string. * 1 === _wp_scan_utf8( $pineapple, $at, $invalid_length ); * $at === 4; $invalid_length === 0; * * Note! While passing an options array here might be convenient from a calling-code standpoint, * this function is intended to serve as a very low-level foundation upon which to build * higher level functionality. For the sake of keeping costs explicit all arguments are * passed directly. * * @since 6.9.0 * @access private * * @param string $bytes UTF-8 encoded string which might include invalid spans of bytes. * @param int $at Where to start scanning. * @param int $invalid_length Will be set to how many bytes are to be ignored after `$at`. * @param int|null $max_bytes Stop scanning after this many bytes have been seen. * @param int|null $max_code_points Stop scanning after this many code points have been seen. * @param bool|null $has_noncharacters Set to indicate if scanned string contained noncharacters. * @return int How many code points were successfully scanned. */ function _wp_scan_utf8( string $bytes, int &$at, int &$invalid_length, ?int $max_bytes = null, ?int $max_code_points = null, ?bool &$has_noncharacters = null ): int { $byte_length = strlen( $bytes ); $end = min( $byte_length, $at + ( $max_bytes ?? PHP_INT_MAX ) ); $invalid_length = 0; $count = 0; $max_count = $max_code_points ?? PHP_INT_MAX; $has_noncharacters = false; for ( $i = $at; $i < $end && $count <= $max_count; $i++ ) { /* * Quickly skip past US-ASCII bytes, all of which are valid UTF-8. * * This optimization step improves the speed from 10x to 100x * depending on whether the JIT has optimized the function. */ $ascii_byte_count = strspn( $bytes, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" . "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" . " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f", $i, $end - $i ); if ( $count + $ascii_byte_count >= $max_count ) { $at = $i + ( $max_count - $count ); $count = $max_count; return $count; } $count += $ascii_byte_count; $i += $ascii_byte_count; if ( $i >= $end ) { $at = $end; return $count; } /** * The above fast-track handled all single-byte UTF-8 characters. What * follows MUST be a multibyte sequence otherwise there’s invalid UTF-8. * * Therefore everything past here is checking those multibyte sequences. * * It may look like there’s a need to check against the max bytes here, * but since each match of a single character returns, this functions will * bail already if crossing the max-bytes threshold. This function SHALL * NOT return in the middle of a multi-byte character, so if a character * falls on each side of the max bytes, the entire character will be scanned. * * Because it’s possible that there are truncated characters, the use of * the null-coalescing operator with "\xC0" is a convenience for skipping * length checks on every continuation bytes. This works because 0xC0 is * always invalid in a UTF-8 string, meaning that if the string has been * truncated, it will find 0xC0 and reject as invalid UTF-8. * * > [The following table] lists all of the byte sequences that are well-formed * > in UTF-8. A range of byte values such as A0..BF indicates that any byte * > from A0 to BF (inclusive) is well-formed in that position. Any byte value * > outside of the ranges listed is ill-formed. * * > Table 3-7. Well-Formed UTF-8 Byte Sequences * ╭─────────────────────┬────────────┬──────────────┬─────────────┬──────────────╮ * │ Code Points │ First Byte │ Second Byte │ Third Byte │ Fourth Byte │ * ├─────────────────────┼────────────┼──────────────┼─────────────┼──────────────┤ * │ U+0000..U+007F │ 00..7F │ │ │ │ * │ U+0080..U+07FF │ C2..DF │ 80..BF │ │ │ * │ U+0800..U+0FFF │ E0 │ A0..BF │ 80..BF │ │ * │ U+1000..U+CFFF │ E1..EC │ 80..BF │ 80..BF │ │ * │ U+D000..U+D7FF │ ED │ 80..9F │ 80..BF │ │ * │ U+E000..U+FFFF │ EE..EF │ 80..BF │ 80..BF │ │ * │ U+10000..U+3FFFF │ F0 │ 90..BF │ 80..BF │ 80..BF │ * │ U+40000..U+FFFFF │ F1..F3 │ 80..BF │ 80..BF │ 80..BF │ * │ U+100000..U+10FFFF │ F4 │ 80..8F │ 80..BF │ 80..BF │ * ╰─────────────────────┴────────────┴──────────────┴─────────────┴──────────────╯ * * @see https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G27506 */ // Valid two-byte code points. $b1 = ord( $bytes[ $i ] ); $b2 = ord( $bytes[ $i + 1 ] ?? "\xC0" ); if ( $b1 >= 0xC2 && $b1 <= 0xDF && $b2 >= 0x80 && $b2 <= 0xBF ) { ++$count; ++$i; continue; } // Valid three-byte code points. $b3 = ord( $bytes[ $i + 2 ] ?? "\xC0" ); if ( $b3 < 0x80 || $b3 > 0xBF ) { goto invalid_utf8; } if ( ( 0xE0 === $b1 && $b2 >= 0xA0 && $b2 <= 0xBF ) || ( $b1 >= 0xE1 && $b1 <= 0xEC && $b2 >= 0x80 && $b2 <= 0xBF ) || ( 0xED === $b1 && $b2 >= 0x80 && $b2 <= 0x9F ) || ( $b1 >= 0xEE && $b1 <= 0xEF && $b2 >= 0x80 && $b2 <= 0xBF ) ) { ++$count; $i += 2; // Covers the range U+FDD0–U+FDEF, U+FFFE, U+FFFF. if ( 0xEF === $b1 ) { $has_noncharacters |= ( ( 0xB7 === $b2 && $b3 >= 0x90 && $b3 <= 0xAF ) || ( 0xBF === $b2 && ( 0xBE === $b3 || 0xBF === $b3 ) ) ); } continue; } // Valid four-byte code points. $b4 = ord( $bytes[ $i + 3 ] ?? "\xC0" ); if ( $b4 < 0x80 || $b4 > 0xBF ) { goto invalid_utf8; } if ( ( 0xF0 === $b1 && $b2 >= 0x90 && $b2 <= 0xBF ) || ( $b1 >= 0xF1 && $b1 <= 0xF3 && $b2 >= 0x80 && $b2 <= 0xBF ) || ( 0xF4 === $b1 && $b2 >= 0x80 && $b2 <= 0x8F ) ) { ++$count; $i += 3; // Covers U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, …, U+10FFFE, U+10FFFF. $has_noncharacters |= ( ( 0x0F === ( $b2 & 0x0F ) ) && 0xBF === $b3 && ( 0xBE === $b4 || 0xBF === $b4 ) ); continue; } /** * When encountering invalid byte sequences, Unicode suggests finding the * maximal subpart of a text and replacing that subpart with a single * replacement character. * * > This practice is more secure because it does not result in the * > conversion consuming parts of valid sequences as though they were * > invalid. It also guarantees at least one replacement character will * > occur for each instance of an invalid sequence in the original text. * > Furthermore, this practice can be defined consistently for better * > interoperability between different implementations of conversion. * * @see https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-5/#G40630 */ invalid_utf8: $at = $i; $invalid_length = 1; // Single-byte and two-byte characters. if ( ( 0x00 === ( $b1 & 0x80 ) ) || ( 0xC0 === ( $b1 & 0xE0 ) ) ) { return $count; } $b2 = ord( $bytes[ $i + 1 ] ?? "\xC0" ); $b3 = ord( $bytes[ $i + 2 ] ?? "\xC0" ); // Find the maximal subpart and skip past it. if ( 0xE0 === ( $b1 & 0xF0 ) ) { // Three-byte characters. $b2_valid = ( ( 0xE0 === $b1 && $b2 >= 0xA0 && $b2 <= 0xBF ) || ( $b1 >= 0xE1 && $b1 <= 0xEC && $b2 >= 0x80 && $b2 <= 0xBF ) || ( 0xED === $b1 && $b2 >= 0x80 && $b2 <= 0x9F ) || ( $b1 >= 0xEE && $b1 <= 0xEF && $b2 >= 0x80 && $b2 <= 0xBF ) ); $invalid_length = min( $end - $i, $b2_valid ? 2 : 1 ); return $count; } elseif ( 0xF0 === ( $b1 & 0xF8 ) ) { // Four-byte characters. $b2_valid = ( ( 0xF0 === $b1 && $b2 >= 0x90 && $b2 <= 0xBF ) || ( $b1 >= 0xF1 && $b1 <= 0xF3 && $b2 >= 0x80 && $b2 <= 0xBF ) || ( 0xF4 === $b1 && $b2 >= 0x80 && $b2 <= 0x8F ) ); $b3_valid = $b3 >= 0x80 && $b3 <= 0xBF; $invalid_length = min( $end - $i, $b2_valid ? ( $b3_valid ? 3 : 2 ) : 1 ); return $count; } return $count; } $at = $i; return $count; } /** * Fallback mechanism for safely validating UTF-8 bytes. * * @since 6.9.0 * @access private * * @see wp_is_valid_utf8() * * @param string $bytes String which might contain text encoded as UTF-8. * @return bool Whether the provided bytes can decode as valid UTF-8. */ function _wp_is_valid_utf8_fallback( string $bytes ): bool { $bytes_length = strlen( $bytes ); if ( 0 === $bytes_length ) { return true; } $next_byte_at = 0; $invalid_length = 0; _wp_scan_utf8( $bytes, $next_byte_at, $invalid_length ); return $bytes_length === $next_byte_at && 0 === $invalid_length; } /** * Fallback mechanism for replacing invalid spans of UTF-8 bytes. * * Example: * * 'Pi�a' === _wp_scrub_utf8_fallback( "Pi\xF1a" ); // “ñ” is 0xF1 in Windows-1252. * * @since 6.9.0 * @access private * * @see wp_scrub_utf8() * * @param string $bytes UTF-8 encoded string which might contain spans of invalid bytes. * @return string Input string with spans of invalid bytes swapped with the replacement character. */ function _wp_scrub_utf8_fallback( string $bytes ): string { $bytes_length = strlen( $bytes ); $next_byte_at = 0; $was_at = 0; $invalid_length = 0; $scrubbed = ''; while ( $next_byte_at <= $bytes_length ) { _wp_scan_utf8( $bytes, $next_byte_at, $invalid_length ); if ( $next_byte_at >= $bytes_length ) { if ( 0 === $was_at ) { return $bytes; } return $scrubbed . substr( $bytes, $was_at, $next_byte_at - $was_at - $invalid_length ); } $scrubbed .= substr( $bytes, $was_at, $next_byte_at - $was_at ); $scrubbed .= "\u{FFFD}"; $next_byte_at += $invalid_length; $was_at = $next_byte_at; } return $scrubbed; } /** * Returns how many code points are found in the given UTF-8 string. * * Invalid spans of bytes count as a single code point according * to the maximal subpart rule. This function is a fallback method * for calling `mb_strlen( $text, 'UTF-8' )`. * * When negative values are provided for the byte offsets or length, * this will always report zero code points. * * Example: * * 4 === _wp_utf8_codepoint_count( 'text' ); * * // Groups are 'test', "\x90" as '�', 'wp', "\xE2\x80" as '�', "\xC0" as '�', and 'test'. * 13 === _wp_utf8_codepoint_count( "test\x90wp\xE2\x80\xC0test" ); * * @since 6.9.0 * @access private * * @param string $text Count code points in this string. * @param ?int $byte_offset Start counting after this many bytes in `$text`. Must be positive. * @param ?int $max_byte_length Optional. Stop counting after having scanned past this many bytes. * Default is to scan until the end of the string. Must be positive. * @return int How many code points were found. */ function _wp_utf8_codepoint_count( string $text, ?int $byte_offset = 0, ?int $max_byte_length = PHP_INT_MAX ): int { if ( $byte_offset < 0 ) { return 0; } $count = 0; $at = $byte_offset; $end = strlen( $text ); $invalid_length = 0; $max_byte_length = min( $end - $at, $max_byte_length ); while ( $at < $end && ( $at - $byte_offset ) < $max_byte_length ) { $count += _wp_scan_utf8( $text, $at, $invalid_length, $max_byte_length - ( $at - $byte_offset ) ); $count += $invalid_length > 0 ? 1 : 0; $at += $invalid_length; } return $count; } /** * Given a starting offset within a string and a maximum number of code points, * return how many bytes are occupied by the span of characters. * * Invalid spans of bytes count as a single code point according to the maximal * subpart rule. This function is a fallback method for calling * `strlen( mb_substr( substr( $text, $at ), 0, $max_code_points ) )`. * * @since 6.9.0 * @access private * * @param string $text Count bytes of span in this text. * @param int $byte_offset Start counting at this byte offset. * @param int $max_code_points Stop counting after this many code points have been seen, * or at the end of the string. * @param ?int $found_code_points Optional. Will be set to number of found code points in * span, as this might be smaller than the maximum count if * the string is not long enough. * @return int Number of bytes spanned by the code points. */ function _wp_utf8_codepoint_span( string $text, int $byte_offset, int $max_code_points, ?int &$found_code_points = 0 ): int { $was_at = $byte_offset; $invalid_length = 0; $end = strlen( $text ); $found_code_points = 0; while ( $byte_offset < $end && $found_code_points < $max_code_points ) { $needed = $max_code_points - $found_code_points; $chunk_count = _wp_scan_utf8( $text, $byte_offset, $invalid_length, null, $needed ); $found_code_points += $chunk_count; // Invalid spans only convey one code point count regardless of how long they are. if ( 0 !== $invalid_length && $found_code_points < $max_code_points ) { ++$found_code_points; $byte_offset += $invalid_length; } } return $byte_offset - $was_at; } /** * Fallback support for determining if a string contains Unicode noncharacters. * * @since 6.9.0 * @access private * * @see \wp_has_noncharacters() * * @param string $text Are there noncharacters in this string? * @return bool Whether noncharacters were found in the string. */ function _wp_has_noncharacters_fallback( string $text ): bool { $at = 0; $invalid_length = 0; $has_noncharacters = false; $end = strlen( $text ); while ( $at < $end && ! $has_noncharacters ) { _wp_scan_utf8( $text, $at, $invalid_length, null, null, $has_noncharacters ); $at += $invalid_length; } return $has_noncharacters; } /** * Converts a string from ISO-8859-1 to UTF-8, maintaining backwards compatibility * with the deprecated function from the PHP standard library. * * @since 6.9.0 * @access private * * @see \utf8_encode() * * @param string $iso_8859_1_text Text treated as ISO-8859-1 (latin1) bytes. * @return string Text converted into UTF-8. */ function _wp_utf8_encode_fallback( $iso_8859_1_text ) { $iso_8859_1_text = (string) $iso_8859_1_text; $at = 0; $was_at = 0; $end = strlen( $iso_8859_1_text ); $utf8 = ''; while ( $at < $end ) { // US-ASCII bytes are identical in ISO-8859-1 and UTF-8. These are 0x00–0x7F. $ascii_byte_count = strspn( $iso_8859_1_text, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" . "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" . " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f", $at ); if ( $ascii_byte_count > 0 ) { $at += $ascii_byte_count; continue; } // All other bytes transform into two-byte UTF-8 sequences. $code_point = ord( $iso_8859_1_text[ $at ] ); $byte1 = chr( 0xC0 | ( $code_point >> 6 ) ); $byte2 = chr( 0x80 | ( $code_point & 0x3F ) ); $utf8 .= substr( $iso_8859_1_text, $was_at, $at - $was_at ); $utf8 .= "{$byte1}{$byte2}"; ++$at; $was_at = $at; } if ( 0 === $was_at ) { return $iso_8859_1_text; } $utf8 .= substr( $iso_8859_1_text, $was_at ); return $utf8; } /** * Converts a string from UTF-8 to ISO-8859-1, maintaining backwards compatibility * with the deprecated function from the PHP standard library. * * @since 6.9.0 * @access private * * @see \utf8_decode() * * @param string $utf8_text Text treated as UTF-8 bytes. * @return string Text converted into ISO-8859-1. */ function _wp_utf8_decode_fallback( $utf8_text ) { $utf8_text = (string) $utf8_text; $at = 0; $was_at = 0; $end = strlen( $utf8_text ); $iso_8859_1_text = ''; while ( $at < $end ) { // US-ASCII bytes are identical in ISO-8859-1 and UTF-8. These are 0x00–0x7F. $ascii_byte_count = strspn( $utf8_text, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" . "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" . " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f", $at ); if ( $ascii_byte_count > 0 ) { $at += $ascii_byte_count; continue; } $next_at = $at; $invalid_length = 0; $found = _wp_scan_utf8( $utf8_text, $next_at, $invalid_length, null, 1 ); $span_length = $next_at - $at; $next_byte = '?'; if ( 1 !== $found ) { if ( $invalid_length > 0 ) { $next_byte = ''; goto flush_sub_part; } break; } // All convertible code points are two-bytes long. $byte1 = ord( $utf8_text[ $at ] ); if ( 0xC0 !== ( $byte1 & 0xE0 ) ) { goto flush_sub_part; } // All convertible code points are not greater than U+FF. $byte2 = ord( $utf8_text[ $at + 1 ] ); $code_point = ( ( $byte1 & 0x1F ) << 6 ) | ( ( $byte2 & 0x3F ) ); if ( $code_point > 0xFF ) { goto flush_sub_part; } $next_byte = chr( $code_point ); flush_sub_part: $iso_8859_1_text .= substr( $utf8_text, $was_at, $at - $was_at ); $iso_8859_1_text .= $next_byte; $at += $span_length; $was_at = $at; if ( $invalid_length > 0 ) { $iso_8859_1_text .= '?'; $at += $invalid_length; $was_at = $at; } } if ( 0 === $was_at ) { return $utf8_text; } $iso_8859_1_text .= substr( $utf8_text, $was_at ); return $iso_8859_1_text; } php-toolkit/Encoding/utf8-encoder.php 0000777 00000003611 15251156632 0013576 0 ustar 00 <?php namespace WordPress\Encoding; /** * UTF-8 encoding pipeline by Dennis Snell (@dmsnell). * * It enables parsing XML documents with incomplete UTF-8 byte sequences * without crashing or depending on the mbstring extension. */ /** * Encode a code point number into the UTF-8 encoding. * * This encoder implements the UTF-8 encoding algorithm for converting * a code point into a byte sequence. If it receives an invalid code * point it will return the Unicode Replacement Character U+FFFD `�`. * * Example: * * '🅰' === WP_HTML_Decoder::codepoint_to_utf8_bytes( 0x1f170 ); * * // Half of a surrogate pair is an invalid code point. * '�' === WP_HTML_Decoder::codepoint_to_utf8_bytes( 0xd83c ); * * @since 6.6.0 * * @see https://www.rfc-editor.org/rfc/rfc3629 For the UTF-8 standard. * * @param int $codepoint Which code point to convert. * @return string Converted code point, or `�` if invalid. */ function codepoint_to_utf8_bytes( $codepoint ) { // Pre-check to ensure a valid code point. if ( $codepoint <= 0 || ( $codepoint >= 0xD800 && $codepoint <= 0xDFFF ) || $codepoint > 0x10FFFF ) { return '�'; } if ( $codepoint <= 0x7F ) { return chr( $codepoint ); } if ( $codepoint <= 0x7FF ) { $byte1 = chr( ( 0xC0 | ( ( $codepoint >> 6 ) & 0x1F ) ) ); $byte2 = chr( $codepoint & 0x3F | 0x80 ); return "{$byte1}{$byte2}"; } if ( $codepoint <= 0xFFFF ) { $byte1 = chr( ( $codepoint >> 12 ) | 0xE0 ); $byte2 = chr( ( $codepoint >> 6 ) & 0x3F | 0x80 ); $byte3 = chr( $codepoint & 0x3F | 0x80 ); return "{$byte1}{$byte2}{$byte3}"; } // Any values above U+10FFFF are eliminated above in the pre-check. $byte1 = chr( ( $codepoint >> 18 ) | 0xF0 ); $byte2 = chr( ( $codepoint >> 12 ) & 0x3F | 0x80 ); $byte3 = chr( ( $codepoint >> 6 ) & 0x3F | 0x80 ); $byte4 = chr( $codepoint & 0x3F | 0x80 ); return "{$byte1}{$byte2}{$byte3}{$byte4}"; } php-toolkit/Encoding/utf8.php 0000777 00000021274 15251156632 0012166 0 ustar 00 <?php namespace WordPress\Encoding; use function WordPress\Encoding\compat\_wp_is_valid_utf8_fallback; use function WordPress\Encoding\compat\_wp_scrub_utf8_fallback; use function WordPress\Encoding\compat\_wp_has_noncharacters_fallback; if ( extension_loaded( 'mbstring' ) ) : /** * Determines if a given byte string represents a valid UTF-8 encoding. * * Note that it’s unlikely for non-UTF-8 data to validate as UTF-8, but * it is still possible. Many texts are simultaneously valid UTF-8, * valid US-ASCII, and valid ISO-8859-1 (`latin1`). * * Example: * * true === wp_is_valid_utf8( '' ); * true === wp_is_valid_utf8( 'just a test' ); * true === wp_is_valid_utf8( "\xE2\x9C\x8F" ); // Pencil, U+270F. * true === wp_is_valid_utf8( "\u{270F}" ); // Pencil, U+270F. * true === wp_is_valid_utf8( '✏' ); // Pencil, U+270F. * * false === wp_is_valid_utf8( "just \xC0 test" ); // Invalid bytes. * false === wp_is_valid_utf8( "\xE2\x9C" ); // Invalid/incomplete sequences. * false === wp_is_valid_utf8( "\xC1\xBF" ); // Overlong sequences. * false === wp_is_valid_utf8( "\xED\xB0\x80" ); // Surrogate halves. * false === wp_is_valid_utf8( "B\xFCch" ); // ISO-8859-1 high-bytes. * // E.g. The “ü” in ISO-8859-1 is a single byte 0xFC, * // but in UTF-8 is the two-byte sequence 0xC3 0xBC. * * A “valid” string consists of “well-formed UTF-8 code unit sequence[s],” meaning * that the bytes conform to the UTF-8 encoding scheme, all characters use the minimal * byte sequence required by UTF-8, and that no sequence encodes a UTF-16 surrogate * code point or any character above the representable range. * * @see https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G32860 * * @since 6.9.0 * * @param string $bytes String which might contain text encoded as UTF-8. * @return bool Whether the provided bytes can decode as valid UTF-8. */ function wp_is_valid_utf8( string $bytes ): bool { return mb_check_encoding( $bytes, 'UTF-8' ); } else : /** * Fallback function for validating UTF-8. * * @ignore * @private * * @since 6.9.0 */ // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound function wp_is_valid_utf8( string $string ): bool { return _wp_is_valid_utf8_fallback( $string ); } endif; if ( extension_loaded( 'mbstring' ) && // Maximal subpart substitution introduced by php/php-src@04e59c916f12b322ac55f22314e31bd0176d01cb. version_compare( PHP_VERSION, '8.1.6', '>=' ) ) : /** * Replaces ill-formed UTF-8 byte sequences with the Unicode Replacement Character. * * Knowing what to do in the presence of text encoding issues can be complicated. * This function replaces invalid spans of bytes to neutralize any corruption that * may be there and prevent it from causing further problems downstream. * * However, it’s not always ideal to replace those bytes. In some settings it may * be best to leave the invalid bytes in the string so that downstream code can handle * them in a specific way. Replacing the bytes too early, like escaping for HTML too * early, can introduce other forms of corruption and data loss. * * When in doubt, use this function to replace spans of invalid bytes. * * Replacement follows the “maximal subpart” algorithm for secure and interoperable * strings. This can lead to sequences of multiple replacement characters in a row. * * Example: * * // Valid strings come through unchanged. * 'test' === wp_scrub_utf8( 'test' ); * * // Invalid sequences of bytes are replaced. * $invalid = "the byte \xC0 is never allowed in a UTF-8 string."; * "the byte \u{FFFD} is never allowed in a UTF-8 string." === wp_scrub_utf8( $invalid, true ); * 'the byte � is never allowed in a UTF-8 string.' === wp_scrub_utf8( $invalid, true ); * * // Maximal subparts are replaced individually. * '.�.' === wp_scrub_utf8( ".\xC0." ); // C0 is never valid. * '.�.' === wp_scrub_utf8( ".\xE2\x8C." ); // Missing A3 at end. * '.��.' === wp_scrub_utf8( ".\xE2\x8C\xE2\x8C." ); // Maximal subparts replaced separately. * '.��.' === wp_scrub_utf8( ".\xC1\xBF." ); // Overlong sequence. * '.���.' === wp_scrub_utf8( ".\xED\xA0\x80." ); // Surrogate half. * * Note! The Unicode Replacement Character is itself a Unicode character (U+FFFD). * Once a span of invalid bytes has been replaced by one, it will not be possible * to know whether the replacement character was originally intended to be there * or if it is the result of scrubbing bytes. It is ideal to leave replacement for * display only, but some contexts (e.g. generating XML or passing data into a * large language model) require valid input strings. * * @since 6.9.0 * * @see https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-5/#G40630 * * @param string $text String which is assumed to be UTF-8 but may contain invalid sequences of bytes. * @return string Input text with invalid sequences of bytes replaced with the Unicode replacement character. */ function wp_scrub_utf8( $text ) { /* * While it looks like setting the substitute character could fail, * the internal PHP code will never fail when provided a valid * code point as a number. In this case, there’s no need to check * its return value to see if it succeeded. */ $prev_replacement_character = mb_substitute_character(); mb_substitute_character( 0xFFFD ); $scrubbed = mb_scrub( $text, 'UTF-8' ); mb_substitute_character( $prev_replacement_character ); return $scrubbed; } else : /** * Fallback function for scrubbing UTF-8. * * @ignore * @private * * @since 6.9.0 */ function wp_scrub_utf8( $text ) { return _wp_scrub_utf8_fallback( $text ); } endif; function _wp_can_use_pcre_u( $set = null ) { static $utf8_pcre = 'reset'; if ( null !== $set ) { $utf8_pcre = $set; } if ( 'reset' === $utf8_pcre ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- intentional error generated to detect PCRE/u support. $utf8_pcre = @preg_match( '/^./u', 'a' ); } return $utf8_pcre; } if ( _wp_can_use_pcre_u() ) : /** * Returns whether the given string contains Unicode noncharacters. * * XML recommends against using noncharacters and HTML forbids their * use in attribute names. Unicode recommends that they not be used * in open exchange of data. * * Noncharacters are code points within the following ranges: * - U+FDD0–U+FDEF * - U+FFFE–U+FFFF * - U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, …, U+10FFFE, U+10FFFF * * @see https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-23/#G12612 * @see https://www.w3.org/TR/xml/#charsets * @see https://html.spec.whatwg.org/#attributes-2 * * @since 6.9.0 * * @param string $text Are there noncharacters in this string? * @return bool Whether noncharacters were found in the string. */ function wp_has_noncharacters( string $text ): bool { return 1 === preg_match( '/[\x{FDD0}-\x{FDEF}\x{FFFE}\x{FFFF}\x{1FFFE}\x{1FFFF}\x{2FFFE}\x{2FFFF}\x{3FFFE}\x{3FFFF}\x{4FFFE}\x{4FFFF}\x{5FFFE}\x{5FFFF}\x{6FFFE}\x{6FFFF}\x{7FFFE}\x{7FFFF}\x{8FFFE}\x{8FFFF}\x{9FFFE}\x{9FFFF}\x{AFFFE}\x{AFFFF}\x{BFFFE}\x{BFFFF}\x{CFFFE}\x{CFFFF}\x{DFFFE}\x{DFFFF}\x{EFFFE}\x{EFFFF}\x{FFFFE}\x{FFFFF}\x{10FFFE}\x{10FFFF}]/u', $text ); } else : /** * Fallback function for detecting noncharacters in a text. * * @ignore * @private * * @since 6.9.0 */ function wp_has_noncharacters( string $text ): bool { return _wp_has_noncharacters_fallback( $text ); } endif; /** * Convert a UTF-8 byte sequence to its Unicode codepoint. * * @param string $character UTF-8 encoded byte sequence representing a single Unicode character. * * @return int Unicode codepoint. */ function utf8_ord( string $character ): int { // Convert the byte sequence to its binary representation. $bytes = unpack( 'C*', $character ); // Initialize the codepoint. $codepoint = 0; // Calculate the codepoint based on the number of bytes. if ( 1 === count( $bytes ) ) { $codepoint = $bytes[1]; } elseif ( 2 === count( $bytes ) ) { $codepoint = ( ( $bytes[1] & 0x1F ) << 6 ) | ( $bytes[2] & 0x3F ); } elseif ( 3 === count( $bytes ) ) { $codepoint = ( ( $bytes[1] & 0x0F ) << 12 ) | ( ( $bytes[2] & 0x3F ) << 6 ) | ( $bytes[3] & 0x3F ); } elseif ( 4 === count( $bytes ) ) { $codepoint = ( ( $bytes[1] & 0x07 ) << 18 ) | ( ( $bytes[2] & 0x3F ) << 12 ) | ( ( $bytes[3] & 0x3F ) << 6 ) | ( $bytes[4] & 0x3F ); } return $codepoint; } php-toolkit/load.php 0000777 00000001504 15251156632 0010463 0 ustar 00 <?php require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/Encoding/compat-utf8.php'; require_once __DIR__ . '/Encoding/utf8.php'; require_once __DIR__ . '/Encoding/utf8-encoder.php'; require_once __DIR__ . '/DataLiberation/URL/functions.php'; if ( ! class_exists( '\Normalizer', false ) ) { require_once __DIR__ . '/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/Normalizer.php'; require_once __DIR__ . '/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php'; } require_once __DIR__ . '/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/bootstrap.php'; require_once __DIR__ . '/DataLiberation/vendor-patched/symfony/polyfill-ctype/bootstrap.php'; require_once __DIR__ . '/DataLiberation/vendor-patched/symfony/polyfill-php80/bootstrap.php'; php-toolkit/vendor/composer/autoload_namespaces.php 0000777 00000000213 15251156632 0016673 0 ustar 00 <?php // autoload_namespaces.php @generated by Composer $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( ); php-toolkit/vendor/composer/autoload_static.php 0000777 00000013064 15251156632 0016053 0 ustar 00 <?php // autoload_static.php @generated by Composer namespace Composer\Autoload; class ComposerStaticInit9801abce1960fa4b71d115bb31b779c7 { public static $prefixLengthsPsr4 = array ( 'S' => array ( 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, 'Symfony\\Polyfill\\Ctype\\' => 23, ), 'R' => array ( 'Rowbot\\URL\\' => 11, 'Rowbot\\Punycode\\' => 16, 'Rowbot\\Idna\\Resource\\' => 21, 'Rowbot\\Idna\\' => 12, ), 'P' => array ( 'Psr\\Log\\' => 8, 'Psr\\EventDispatcher\\' => 20, ), 'B' => array ( 'Brick\\Math\\' => 11, ), ); public static $prefixDirsPsr4 = array ( 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/symfony/polyfill-php80/src', ), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/src', ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/symfony/polyfill-ctype/src', ), 'Rowbot\\URL\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/rowbot/url/src', ), 'Rowbot\\Punycode\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/rowbot/punycode/src', ), 'Rowbot\\Idna\\Resource\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/rowbot/idna/resources', ), 'Rowbot\\Idna\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/rowbot/idna/src', ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/psr/log/src', ), 'Psr\\EventDispatcher\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/psr/event-dispatcher/src', ), 'Brick\\Math\\' => array ( 0 => __DIR__ . '/../..' . '/DataLiberation/vendor-patched/brick/math/src', ), ); public static $classMap = array ( 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'WordPress\\ByteStream\\ByteStreamException' => __DIR__ . '/../..' . '/ByteStream/class-bytestreamexception.php', 'WordPress\\ByteStream\\NotEnoughDataException' => __DIR__ . '/../..' . '/ByteStream/class-notenoughdataexception.php', 'WordPress\\ByteStream\\ReadStream\\BaseByteReadStream' => __DIR__ . '/../..' . '/ByteStream/ReadStream/class-basebytereadstream.php', 'WordPress\\ByteStream\\ReadStream\\ByteReadStream' => __DIR__ . '/../..' . '/ByteStream/ReadStream/interface-bytereadstream.php', 'WordPress\\ByteStream\\ReadStream\\FileReadStream' => __DIR__ . '/../..' . '/ByteStream/ReadStream/class-filereadstream.php', 'WordPress\\DataLiberation\\BlockMarkup\\BlockMarkupProcessor' => __DIR__ . '/../..' . '/DataLiberation/BlockMarkup/class-blockmarkupprocessor.php', 'WordPress\\DataLiberation\\BlockMarkup\\BlockMarkupUrlProcessor' => __DIR__ . '/../..' . '/DataLiberation/BlockMarkup/class-blockmarkupurlprocessor.php', 'WordPress\\DataLiberation\\BlockMarkup\\BlockObject' => __DIR__ . '/../..' . '/DataLiberation/BlockMarkup/class-blockobject.php', 'WordPress\\DataLiberation\\CSS\\CSSProcessor' => __DIR__ . '/../..' . '/DataLiberation/CSS/class-cssprocessor.php', 'WordPress\\DataLiberation\\EntityReader\\EntityReader' => __DIR__ . '/../..' . '/DataLiberation/EntityReader/entity-reader.php', 'WordPress\\DataLiberation\\EntityReader\\WXREntityReader' => __DIR__ . '/../..' . '/DataLiberation/EntityReader/class-wxrentityreader.php', 'WordPress\\DataLiberation\\ImportEntity' => __DIR__ . '/../..' . '/DataLiberation/class-importentity.php', 'WordPress\\DataLiberation\\URL\\CSSURLProcessor' => __DIR__ . '/../..' . '/DataLiberation/URL/class-cssurlprocessor.php', 'WordPress\\DataLiberation\\URL\\ConvertedUrl' => __DIR__ . '/../..' . '/DataLiberation/URL/class-convertedurl.php', 'WordPress\\DataLiberation\\URL\\URLInTextProcessor' => __DIR__ . '/../..' . '/DataLiberation/URL/class-urlintextprocessor.php', 'WordPress\\DataLiberation\\URL\\WPURL' => __DIR__ . '/../..' . '/DataLiberation/URL/class-wpurl.php', 'WordPress\\XML\\XMLAttributeToken' => __DIR__ . '/../..' . '/XML/class-xmlattributetoken.php', 'WordPress\\XML\\XMLDecoder' => __DIR__ . '/../..' . '/XML/class-xmldecoder.php', 'WordPress\\XML\\XMLElement' => __DIR__ . '/../..' . '/XML/class-xmlelement.php', 'WordPress\\XML\\XMLProcessor' => __DIR__ . '/../..' . '/XML/class-xmlprocessor.php', 'WordPress\\XML\\XMLUnsupportedException' => __DIR__ . '/../..' . '/XML/class-xmlunsupportedexception.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit9801abce1960fa4b71d115bb31b779c7::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit9801abce1960fa4b71d115bb31b779c7::$prefixDirsPsr4; $loader->classMap = ComposerStaticInit9801abce1960fa4b71d115bb31b779c7::$classMap; }, null, ClassLoader::class); } } php-toolkit/vendor/composer/autoload_classmap.php 0000777 00000005024 15251156632 0016364 0 ustar 00 <?php // autoload_classmap.php @generated by Composer $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'WordPress\\ByteStream\\ByteStreamException' => $baseDir . '/ByteStream/class-bytestreamexception.php', 'WordPress\\ByteStream\\NotEnoughDataException' => $baseDir . '/ByteStream/class-notenoughdataexception.php', 'WordPress\\ByteStream\\ReadStream\\BaseByteReadStream' => $baseDir . '/ByteStream/ReadStream/class-basebytereadstream.php', 'WordPress\\ByteStream\\ReadStream\\ByteReadStream' => $baseDir . '/ByteStream/ReadStream/interface-bytereadstream.php', 'WordPress\\ByteStream\\ReadStream\\FileReadStream' => $baseDir . '/ByteStream/ReadStream/class-filereadstream.php', 'WordPress\\DataLiberation\\BlockMarkup\\BlockMarkupProcessor' => $baseDir . '/DataLiberation/BlockMarkup/class-blockmarkupprocessor.php', 'WordPress\\DataLiberation\\BlockMarkup\\BlockMarkupUrlProcessor' => $baseDir . '/DataLiberation/BlockMarkup/class-blockmarkupurlprocessor.php', 'WordPress\\DataLiberation\\BlockMarkup\\BlockObject' => $baseDir . '/DataLiberation/BlockMarkup/class-blockobject.php', 'WordPress\\DataLiberation\\CSS\\CSSProcessor' => $baseDir . '/DataLiberation/CSS/class-cssprocessor.php', 'WordPress\\DataLiberation\\EntityReader\\EntityReader' => $baseDir . '/DataLiberation/EntityReader/entity-reader.php', 'WordPress\\DataLiberation\\EntityReader\\WXREntityReader' => $baseDir . '/DataLiberation/EntityReader/class-wxrentityreader.php', 'WordPress\\DataLiberation\\ImportEntity' => $baseDir . '/DataLiberation/class-importentity.php', 'WordPress\\DataLiberation\\URL\\CSSURLProcessor' => $baseDir . '/DataLiberation/URL/class-cssurlprocessor.php', 'WordPress\\DataLiberation\\URL\\ConvertedUrl' => $baseDir . '/DataLiberation/URL/class-convertedurl.php', 'WordPress\\DataLiberation\\URL\\URLInTextProcessor' => $baseDir . '/DataLiberation/URL/class-urlintextprocessor.php', 'WordPress\\DataLiberation\\URL\\WPURL' => $baseDir . '/DataLiberation/URL/class-wpurl.php', 'WordPress\\XML\\XMLAttributeToken' => $baseDir . '/XML/class-xmlattributetoken.php', 'WordPress\\XML\\XMLDecoder' => $baseDir . '/XML/class-xmldecoder.php', 'WordPress\\XML\\XMLElement' => $baseDir . '/XML/class-xmlelement.php', 'WordPress\\XML\\XMLProcessor' => $baseDir . '/XML/class-xmlprocessor.php', 'WordPress\\XML\\XMLUnsupportedException' => $baseDir . '/XML/class-xmlunsupportedexception.php', ); php-toolkit/vendor/composer/autoload_real.php 0000777 00000002077 15251156632 0015511 0 ustar 00 <?php // autoload_real.php @generated by Composer class ComposerAutoloaderInit9801abce1960fa4b71d115bb31b779c7 { private static $loader; public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } /** * @return \Composer\Autoload\ClassLoader */ public static function getLoader() { if (null !== self::$loader) { return self::$loader; } spl_autoload_register(array('ComposerAutoloaderInit9801abce1960fa4b71d115bb31b779c7', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); spl_autoload_unregister(array('ComposerAutoloaderInit9801abce1960fa4b71d115bb31b779c7', 'loadClassLoader')); require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit9801abce1960fa4b71d115bb31b779c7::getInitializer($loader)); $loader->register(true); return $loader; } } php-toolkit/vendor/composer/autoload_psr4.php 0000777 00000002224 15251156632 0015450 0 ustar 00 <?php // autoload_psr4.php @generated by Composer $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( 'Symfony\\Polyfill\\Php80\\' => array($baseDir . '/DataLiberation/vendor-patched/symfony/polyfill-php80/src'), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($baseDir . '/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/src'), 'Symfony\\Polyfill\\Ctype\\' => array($baseDir . '/DataLiberation/vendor-patched/symfony/polyfill-ctype/src'), 'Rowbot\\URL\\' => array($baseDir . '/DataLiberation/vendor-patched/rowbot/url/src'), 'Rowbot\\Punycode\\' => array($baseDir . '/DataLiberation/vendor-patched/rowbot/punycode/src'), 'Rowbot\\Idna\\Resource\\' => array($baseDir . '/DataLiberation/vendor-patched/rowbot/idna/resources'), 'Rowbot\\Idna\\' => array($baseDir . '/DataLiberation/vendor-patched/rowbot/idna/src'), 'Psr\\Log\\' => array($baseDir . '/DataLiberation/vendor-patched/psr/log/src'), 'Psr\\EventDispatcher\\' => array($baseDir . '/DataLiberation/vendor-patched/psr/event-dispatcher/src'), 'Brick\\Math\\' => array($baseDir . '/DataLiberation/vendor-patched/brick/math/src'), ); php-toolkit/vendor/composer/ClassLoader.php 0000777 00000037772 15251156632 0015104 0 ustar 00 <?php /* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> * Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier <fabien@symfony.com> * @author Jordi Boggiano <j.boggiano@seld.be> * @see https://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { /** @var \Closure(string):void */ private static $includeFile; /** @var string|null */ private $vendorDir; // PSR-4 /** * @var array<string, array<string, int>> */ private $prefixLengthsPsr4 = array(); /** * @var array<string, list<string>> */ private $prefixDirsPsr4 = array(); /** * @var list<string> */ private $fallbackDirsPsr4 = array(); // PSR-0 /** * List of PSR-0 prefixes * * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) * * @var array<string, array<string, list<string>>> */ private $prefixesPsr0 = array(); /** * @var list<string> */ private $fallbackDirsPsr0 = array(); /** @var bool */ private $useIncludePath = false; /** * @var array<string, string> */ private $classMap = array(); /** @var bool */ private $classMapAuthoritative = false; /** * @var array<string, bool> */ private $missingClasses = array(); /** @var string|null */ private $apcuPrefix; /** * @var array<string, self> */ private static $registeredLoaders = array(); /** * @param string|null $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; self::initializeIncludeClosure(); } /** * @return array<string, list<string>> */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } /** * @return array<string, list<string>> */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } /** * @return list<string> */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } /** * @return list<string> */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } /** * @return array<string, string> Array of classname => path */ public function getClassMap() { return $this->classMap; } /** * @param array<string, string> $classMap Class to filename map * * @return void */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param list<string>|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], $paths ); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param list<string>|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { $paths = (array) $paths; if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], $paths ); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param list<string>|string $paths The PSR-0 base directories * * @return void */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param list<string>|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * * @return void */ public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath * * @return void */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative * * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix * * @return void */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not * * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); if (null === $this->vendorDir) { return; } if ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } } /** * Unregisters this instance as an autoloader. * * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } /** * Loads the given class or interface. * * @param string $class The name of the class * @return true|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { $includeFile = self::$includeFile; $includeFile($file); return true; } return null; } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; } /** * Returns the currently registered loaders keyed by their corresponding vendor directories. * * @return array<string, self> */ public static function getRegisteredLoaders() { return self::$registeredLoaders; } /** * @param string $class * @param string $ext * @return string|false */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } /** * @return void */ private static function initializeIncludeClosure() { if (self::$includeFile !== null) { return; } /** * Scope isolated include. * * Prevents access to $this/self from included files. * * @param string $file * @return void */ self::$includeFile = \Closure::bind(static function($file) { include $file; }, null, null); } } php-toolkit/vendor/composer/LICENSE 0000777 00000002056 15251156632 0013167 0 ustar 00 Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/vendor/autoload.php 0000777 00000001354 15251156632 0012654 0 ustar 00 <?php // autoload.php @generated by Composer if (PHP_VERSION_ID < 50600) { if (!headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; if (!ini_get('display_errors')) { if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { fwrite(STDERR, $err); } elseif (!headers_sent()) { echo $err; } } throw new RuntimeException($err); } require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit9801abce1960fa4b71d115bb31b779c7::getLoader(); php-toolkit/ByteStream/class-notenoughdataexception.php 0000777 00000000145 15251156632 0017505 0 ustar 00 <?php namespace WordPress\ByteStream; class NotEnoughDataException extends ByteStreamException { } php-toolkit/ByteStream/class-bytestreamexception.php 0000777 00000000151 15251156632 0017021 0 ustar 00 <?php namespace WordPress\ByteStream; use Exception; class ByteStreamException extends Exception { } php-toolkit/ByteStream/ReadStream/interface-bytereadstream.php 0000777 00000003636 15251156632 0020633 0 ustar 00 <?php namespace WordPress\ByteStream\ReadStream; /** * Interface for streaming, seekable byte readers. * * Implementations of this interface can be used to read data from * various sources, such as files, strings, network sockets, zip files, * parsers, etc. */ interface ByteReadStream { const PULL_NO_MORE_THAN = '#pull-no-more-than'; const PULL_EXACTLY = '#pull-exactly'; /** * Get the total length of the data stream. * * @return int|null The length of the data stream, or null if the length is unknown. */ public function length(): ?int; /** * Get the current position in the data stream. * * @return int The current byte offset in the data stream. */ public function tell(): int; /** * Seek to a specific position in the data stream. * * @param int $offset The byte offset to seek to. * * @return void * @throws ByteStreamException If the offset is invalid. */ public function seek( int $offset ): void; /** * Check if the end of the data stream has been reached. * At this point, next_bytes() will always return false until * seek() is called. * * @return bool Whether the end of the data stream has been reached. */ public function reached_end_of_data(): bool; /** * Read the next chunk of bytes from the data stream. * * @return int how many bytes were pulled */ public function pull( ?int $n, string $mode = self::PULL_NO_MORE_THAN ): int; /** * Get the next $n bytes without advancing the pointer. * * @return string The bytes read. */ public function peek( int $n ): string; /** * Returns $n bytes and advances the pointer. * * @param int $n * * @return string */ public function consume( int $n ): string; /** * Returns all remaining bytes in the stream. * * @return string */ public function consume_all(): string; /** * Close the data stream. * * @return void */ public function close_reading(): void; } php-toolkit/ByteStream/ReadStream/class-filereadstream.php 0000777 00000004740 15251156632 0017751 0 ustar 00 <?php namespace WordPress\ByteStream\ReadStream; use WordPress\ByteStream\ByteStreamException; class FileReadStream extends BaseByteReadStream { protected $file_pointer; public static function from_path( $file_path ) { if ( ! file_exists( $file_path ) ) { throw new ByteStreamException( esc_html( sprintf( 'File %s does not exist', $file_path ) ) ); } if ( ! is_file( $file_path ) ) { throw new ByteStreamException( esc_html( sprintf( '%s is not a file', $file_path ) ) ); } $handle = fopen( $file_path, 'r' ); if ( ! $handle ) { throw new ByteStreamException( esc_html( sprintf( 'Failed to open file %s', $file_path ) ) ); } return self::from_resource( $handle, filesize( $file_path ) ); } public static function from_resource( $handle, $expected_length = null ) { if ( ! is_resource( $handle ) ) { throw new ByteStreamException( 'Invalid file pointer provided' ); } return new self( $handle, $expected_length ); } public function __construct( $fp, $expected_length = null ) { $this->file_pointer = $fp; $this->expected_length = $expected_length; } protected function internal_pull( $n ): string { $bytes = fread( $this->file_pointer, $n ); /** * Workaround for a streaming bug in WordPress Playground. * * Without the feof() call, Playground doesn't notice when the stream reaches EOF. * The feof() call in internal_reached_end_of_data() somehow does not trigger the * EOF event. It must be here, right after fread(). * * @TODO: Improve the streaming support in WordPress Playground. */ feof( $this->file_pointer ); if ( false === $bytes ) { throw new ByteStreamException( 'Failed to read from file' ); } return $bytes; } protected function seek_outside_of_buffer( int $target_offset ): void { $retval = fseek( $this->file_pointer, $target_offset ); if ( -1 === $retval ) { throw new ByteStreamException( 'Failed to seek to offset' ); } $this->buffer = ''; $this->offset_in_current_buffer = 0; $this->bytes_already_forgotten = $target_offset; } public function close_reading(): void { if ( $this->is_read_closed ) { return; } $this->is_read_closed = true; $this->buffer = ''; if ( ! fclose( $this->file_pointer ) ) { throw new ByteStreamException( 'Failed to close file pointer' ); } $this->file_pointer = null; } protected function internal_reached_end_of_data(): bool { return ! is_resource( $this->file_pointer ) || feof( $this->file_pointer ); } } php-toolkit/ByteStream/ReadStream/class-basebytereadstream.php 0000777 00000014225 15251156632 0020627 0 ustar 00 <?php namespace WordPress\ByteStream\ReadStream; use WordPress\ByteStream\ByteStreamException; use WordPress\ByteStream\NotEnoughDataException; abstract class BaseByteReadStream implements ByteReadStream { const CHUNK_SIZE_BYTES = 64 * 1024; // 64kb. /** * The maximum number of consumed bytes to keep in memory. * * For example: * * The quick brown fox jumps over the lazy dog. * ^-------------------^ * consumed bytes * * Say the maximum lookbehind bytes is 4. Then the byte stream will forget about * all consumed bytes except the last 4: * * fox jumps over the lazy dog. * ^--^ * consumed but retained for seek()-ing backwards. * * @var int */ protected $max_lookbehind_bytes = 2048; /** * The remaining unconsumed bytes. * * @var string */ protected $buffer = ''; /** * How many bytes have already been consumed in the current **buffer**. * * @var int */ protected $offset_in_current_buffer = 0; /** * How many bytes have already been forgotten in the current **stream**. * * @var int */ protected $bytes_already_forgotten = 0; /** * Whether the stream has been closed for reading. * * @var bool */ protected $is_read_closed = false; /** * How many bytes are expected in the stream. Optional. * * When it's null, the stream is unbounded and length() will also return null. * * @var int|null */ protected $expected_length = null; public function length(): ?int { return $this->expected_length; } public function pull( ?int $n = self::CHUNK_SIZE_BYTES, string $mode = self::PULL_NO_MORE_THAN ): int { switch ( $mode ) { case self::PULL_NO_MORE_THAN: case self::PULL_EXACTLY: break; default: throw new ByteStreamException( 'Invalid pull mode' ); } if ( $this->is_read_closed ) { throw new ByteStreamException( 'Cannot pull() on a closed producer' ); } if ( 0 === $n ) { return 0; } if ( $n < 0 ) { throw new ByteStreamException( 'Cannot pull a negative number of bytes' ); } if ( $n <= $this->count_consumable_bytes() ) { return $n; } if ( $this->reached_end_of_data() ) { if ( ByteReadStream::PULL_EXACTLY === $mode ) { throw new NotEnoughDataException( 'End of data reached while pulling' ); } return 0; } if ( ByteReadStream::PULL_NO_MORE_THAN === $mode ) { return $this->pull_no_more_than( $n ); } return $this->pull_exactly( $n ); } protected function pull_exactly( $n ): int { $empty_pulls = 0; while ( $this->count_consumable_bytes() < $n ) { $consumable_before = $this->count_consumable_bytes(); $this->pull_no_more_than( $n ); $consumable_after = $this->count_consumable_bytes(); if ( $consumable_after === $consumable_before ) { ++$empty_pulls; if ( $this->reached_end_of_data() ) { throw new NotEnoughDataException( 'End of data reached while pulling' ); } } if ( $empty_pulls > 4 ) { throw new NotEnoughDataException( '4 empty pulls in a row, we are probably at the end of the data' ); } } return $n; } protected function pull_no_more_than( $n ): int { $this->buffer .= $this->internal_pull( self::CHUNK_SIZE_BYTES ); return min( $n, $this->count_consumable_bytes() ); } public function consume_all(): string { $body = ''; while ( true ) { if ( $this->reached_end_of_data() ) { return $body; } $consumable = $this->pull( self::CHUNK_SIZE_BYTES ); $body .= $this->consume( $consumable ); } } protected function count_consumable_bytes(): int { return strlen( $this->buffer ) - $this->offset_in_current_buffer; } abstract protected function internal_pull( $n ): string; public function peek( int $n ): string { return substr( $this->buffer, $this->offset_in_current_buffer, $n ); } public function consume( int $n ): string { if ( strlen( $this->buffer ) < $this->offset_in_current_buffer + $n ) { throw new NotEnoughDataException( 'Cannot consume more bytes than available in the buffer.' ); } $bytes = substr( $this->buffer, $this->offset_in_current_buffer, $n ); $this->offset_in_current_buffer += $n; if ( $this->offset_in_current_buffer > $this->max_lookbehind_bytes ) { $overflow = $this->offset_in_current_buffer - $this->max_lookbehind_bytes; $this->offset_in_current_buffer -= $overflow; $this->bytes_already_forgotten += $overflow; $this->buffer = substr( $this->buffer, $overflow ); } return $bytes; } public function seek( int $target_offset ): void { // We have that offset in the buffer, let's just update the pointer. if ( $target_offset >= $this->bytes_already_forgotten && $target_offset <= $this->bytes_already_forgotten + strlen( $this->buffer ) ) { $this->offset_in_current_buffer = $target_offset - $this->bytes_already_forgotten; return; } if ( null !== $this->length() && $target_offset > $this->length() ) { $length = $this->length(); throw new NotEnoughDataException( esc_html( sprintf( 'Cannot seek to past the stream length (seeked to %d, stream length is %d).', $target_offset, $length ) ) ); } if ( $target_offset < 0 ) { throw new ByteStreamException( 'Cannot seek to a negative offset' ); } // Seeking outside of buffer range, we need a producer-specific implementation. $this->seek_outside_of_buffer( $target_offset ); } protected function seek_outside_of_buffer( int $target_offset ): void { throw new ByteStreamException( 'Cannot seek outside of the buffered range' ); } public function tell(): int { return $this->bytes_already_forgotten + $this->offset_in_current_buffer; } public function reached_end_of_data(): bool { if ( $this->is_read_closed ) { return true; } if ( $this->count_consumable_bytes() > 0 ) { return false; } if ( null !== $this->length() ) { return $this->tell() >= $this->length(); } return $this->internal_reached_end_of_data(); } protected function internal_reached_end_of_data(): bool { return false; } public function close_reading(): void { $this->is_read_closed = true; $this->internal_close_reading(); } protected function internal_close_reading(): void { } } php-toolkit/composer.json 0000777 00000002324 15251156632 0011556 0 ustar 00 { "name": "wordpress/php-toolkit", "autoload": { "classmap": [ "ByteStream", "DataLiberation", "Encoding", "XML" ], "exclude-from-classmap": [ "./DataLiberation/vendor-patched/" ], "psr-4": { "Brick\\Math\\": "DataLiberation/vendor-patched/brick/math/src/", "Psr\\Log\\": "DataLiberation/vendor-patched/psr/log/src/", "Psr\\EventDispatcher\\": "DataLiberation/vendor-patched/psr/event-dispatcher/src/", "Rowbot\\Idna\\Resource\\": "DataLiberation/vendor-patched/rowbot/idna/resources/", "Rowbot\\Idna\\": "DataLiberation/vendor-patched/rowbot/idna/src/", "Rowbot\\Punycode\\": "DataLiberation/vendor-patched/rowbot/punycode/src/", "Rowbot\\URL\\": "DataLiberation/vendor-patched/rowbot/url/src/", "Symfony\\Polyfill\\Php80\\": "DataLiberation/vendor-patched/symfony/polyfill-php80/src/", "Symfony\\Polyfill\\Intl\\Normalizer\\": "DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/src/", "Symfony\\Polyfill\\Ctype\\": "DataLiberation/vendor-patched/symfony/polyfill-ctype/src/" } } } php-toolkit/XML/class-xmlattributetoken.php 0000777 00000004676 15251156632 0015111 0 ustar 00 <?php namespace WordPress\XML; /** * XML API: XMLAttributeToken class * * @package WordPress * @subpackage XML-API * @since 6.2.0 */ /** * Core class used by the XML tag processor as a data structure for the attribute token, * allowing to drastically improve performance. * * This class is for internal usage of the XMLProcessor class. * * @see XMLProcessor */ class XMLAttributeToken { /** * Attribute name. * * @since 6.2.0 * * @var string */ public $qualified_name; /** * Attribute value. * * @since 6.2.0 * * @var int */ public $value_starts_at; /** * How many bytes the value occupies in the input XML. * * @since 6.2.0 * * @var int */ public $value_length; /** * The string offset where the attribute name starts. * * @since 6.2.0 * * @var int */ public $start; /** * Byte length of text spanning the attribute inside a tag. * * This span starts at the first character of the attribute name * and it ends after one of three cases: * * - at the end of the attribute name for boolean attributes. * - at the end of the value for unquoted attributes. * - at the final single or double quote for quoted attributes. * * @var int */ public $length; /** * Namespace prefix. * * @var string */ public $namespace_prefix; /** * Local name. * * @var string */ public $local_name; /** * Namespace. * * @var string */ public $namespace; /** * Constructor. * * @param int $value_start Attribute value. * @param int $value_length Number of bytes attribute value spans. * @param int $start The string offset where the attribute name starts. * @param int $length Byte length of the entire attribute name or name and value pair expression. * @param string $namespace_prefix Namespace prefix. * @param string $local_name Local name. * @param string $namespace_name Namespace. */ public function __construct( $value_start, $value_length, $start, $length, $namespace_prefix = null, $local_name = null, $namespace_name = null ) { $this->value_starts_at = $value_start; $this->value_length = $value_length; $this->start = $start; $this->length = $length; $this->namespace_prefix = $namespace_prefix; $this->local_name = $local_name; $this->namespace = $namespace_name; $this->qualified_name = $namespace_prefix ? $namespace_prefix . ':' . $local_name : $local_name; } } php-toolkit/XML/class-xmlelement.php 0000777 00000004174 15251156632 0013467 0 ustar 00 <?php namespace WordPress\XML; /** * XML API: XMLElement class * * @package WordPress * @subpackage XML-API * @since 6.2.0 */ /** * Core class used by the XML tag processor as a data structure for the attribute token, * allowing to drastically improve performance. * * This class is for internal usage of the XMLProcessor class. * * @see XMLProcessor */ class XMLElement { /** * Local name. * * @var string */ public $local_name; /** * Namespace Prefix. * * @var string */ public $namespace_prefix; /** * Full XML namespace name. * * @var string */ public $namespace; /** * Namespaces in current element's scope. * * @var array<string, string> */ public $namespaces_in_scope; /** * Qualified name. * * @var string */ public $qualified_name; /** * Constructor. * * @param string $local_name Local name. * @param string $xml_namespace_prefix Namespace prefix. * @param string $xml_namespace Full XML namespace name. * @param array<string, string> $namespaces_in_scope Namespaces in current element's scope. */ public function __construct( $local_name, $xml_namespace_prefix, $xml_namespace, $namespaces_in_scope ) { $this->local_name = $local_name; $this->namespace_prefix = $xml_namespace_prefix; $this->namespace = $xml_namespace; $this->namespaces_in_scope = $namespaces_in_scope; $this->qualified_name = $xml_namespace_prefix ? $xml_namespace_prefix . ':' . $local_name : $local_name; } public function get_full_name() { return $this->namespace ? '{' . $this->namespace . '}' . $this->local_name : $this->local_name; } public function to_array() { return array( 'local_name' => $this->local_name, 'namespace_prefix' => $this->namespace_prefix, 'namespace' => $this->namespace, 'namespaces_in_scope' => $this->namespaces_in_scope, ); } public static function from_array( $array_value ) { return new self( $array_value['local_name'], $array_value['namespace_prefix'], $array_value['namespace'], $array_value['namespaces_in_scope'] ); } } php-toolkit/XML/class-xmlunsupportedexception.php 0000777 00000005756 15251156632 0016354 0 ustar 00 <?php /** * XML API: XMLUnsupportedException class * * @package WordPress\XML */ namespace WordPress\XML; use Exception; /** * Core class used by the XML processor during XML parsing * for indicating that a given operation is unsupported. * * This class is designed for internal use by the XML processor. * * The XML API aims to operate in compliance with the XML 1.0 * specification, but does not implement the full specification. * In cases where it lacks support it should not cause breakage * or unexpected behavior. In the cases where it recognizes that * it cannot proceed, this class is used to abort from any * operation and signify that the given XML cannot be processed. * * @since 6.4.0 * @since 6.7.0 Gained contextual information for use in debugging parse failures. * * @access private * * @see XMLProcessor */ class XMLUnsupportedException extends Exception { /** * Name of the matched token when the exception was raised, * if matched on a token. * * This does not imply that the token itself was unsupported, but it * may have been the case that the token triggered part of the XML * parsing that isn't supported, such as the adoption agency algorithm. * * @var string */ public $token_name; /** * Number of bytes into the input XML document where the parser was * parsing when the exception was raised. * * Use this to reconstruct context for the failure. * * @var int */ public $token_at; /** * Full raw text of the matched token when the exception was raised, * if matched on a token. * * Whereas the `$token_name` will be normalized, this contains the full * raw text of the token, including original casing, duplicated attributes, * and other syntactic variations that are normally abstracted in the XML API. * * @var string */ public $token; /** * Stack of open elements when the exception was raised. * * Use this to trace the parsing circumstances which led to the exception. * * @var string[] */ public $stack_of_open_elements = array(); /** * Constructor function. * * @param string $message Brief message explaining what is unsupported, the reason this exception was raised. * @param string $token_name Normalized name of matched token when this exception was raised. * @param int $token_at Number of bytes into source XML document where matched token starts. * @param string $token Full raw text of matched token when this exception was raised. * @param string[] $stack_of_open_elements Stack of open elements when this exception was raised. */ public function __construct( string $message, string $token_name, int $token_at, string $token, array $stack_of_open_elements ) { parent::__construct( $message ); $this->token_name = $token_name; $this->token_at = $token_at; $this->token = $token; $this->stack_of_open_elements = $stack_of_open_elements; } public function __toString() { return $this->message . ' at ' . $this->token_at . ' in ' . $this->token; } } php-toolkit/XML/class-xmldecoder.php 0000777 00000015732 15251156632 0013445 0 ustar 00 <?php namespace WordPress\XML; use function WordPress\Encoding\codepoint_to_utf8_bytes; /** * XML API: WP_XML_Decoder class * * Decodes spans of raw text found inside XML content, * whether found in an attribute or in a text node. * * Do not use this function on the contents of a CDATA section, * as those sections are not encoded with the XML rules unless * they are embedded XML content. * * @package WordPress * @subpackage HTML-API * @since WP_VERSION */ class XMLDecoder { /** * Decodes a span of XML text. * * Example: * * '&' = WP_XML_Decoder::decode( '&' ); * '…' = WP_XML_Decoder::decode( '…' ); * * @todo Add examples of parse failures, and decide if it should fail or not. * * @since WP_VERSION * * @access private * * @param string $text Text document containing span of text to decode. * @return string Decoded UTF-8 string. */ public static function decode( $text ) { $decoded = ''; $end = strlen( $text ); $at = 0; $was_at = 0; while ( $at < $end ) { $next_character_reference_at = strpos( $text, '&', $at ); if ( false === $next_character_reference_at || $next_character_reference_at >= $end ) { break; } $start_of_potential_reference_at = $next_character_reference_at + 1; if ( $start_of_potential_reference_at >= $end ) { // @todo This is an error. The document ended too early; consume the rest as plaintext, which is wrong. break; } /** * First character after the opening `&`. */ $start_of_potential_reference = $text[ $start_of_potential_reference_at ]; /* * If it's a named character reference, it will be one of the five mandated references. * * - `&` * - `'` * - `>` * - `<` * - `"` * * These all must be found within the five successive characters from the `&`. * * Example: * * ╭ ampersand at 9 = $end - 6 * 'XML' ($end = 15) * ╰───┴─ this length must be at least 5 long, * which is $end - 5. */ if ( $next_character_reference_at < $end - 5 && ( 'a' === $start_of_potential_reference || 'g' === $start_of_potential_reference || 'l' === $start_of_potential_reference || 'q' === $start_of_potential_reference ) ) { foreach ( array( 'amp;' => '&', 'apos;' => "'", 'lt;' => '<', 'gt;' => '>', 'quot;' => '"', ) as $name => $substitution ) { if ( 0 === substr_compare( $text, $name, $start_of_potential_reference_at, strlen( $name ) ) ) { $decoded .= substr( $text, $was_at, $next_character_reference_at - $was_at ) . $substitution; $at = $start_of_potential_reference_at + strlen( $name ); $was_at = $at; continue 2; } } // @todo This is an invalid document. It should be communicated. Treat as plaintext and continue. ++$at; continue; } /* * The shortest numerical character reference is four characters. * * Example: * * 	 */ if ( '#' !== $start_of_potential_reference || $next_character_reference_at + 4 >= $end ) { // @todo This is an error. This ampersand _must_ be encoded. Treat as plaintext and move on. ++$at; continue; } $is_hex = 'x' === $text[ $start_of_potential_reference_at + 1 ]; if ( $is_hex ) { $zeros_at = $start_of_potential_reference_at + 2; $base = 16; $digit_chars = '0123456789abcdefABCDEF'; $max_digits = 6; // ``. } else { $zeros_at = $start_of_potential_reference_at + 1; $base = 10; $digit_chars = '0123456789'; $max_digits = 7; // ``. } $zero_count = strspn( $text, '0', $zeros_at ); $digits_at = $zeros_at + $zero_count; $digit_count = strspn( $text, $digit_chars, $digits_at, $max_digits ); $semi_at = $digits_at + $digit_count; if ( 0 === $digit_count || $semi_at >= $end || ';' !== $text[ $semi_at ] ) { // @todo This is an error. Treat as plaintext and move on. ++$at; continue; } $codepoint = intval( substr( $text, $digits_at, $digit_count ), $base ); $character_reference = codepoint_to_utf8_bytes( $codepoint ); if ( '�' === $character_reference && 0xFFFD !== $codepoint ) { /* * Stop processing if we got an invalid character AND the reference does not * specifically refer code point FFFD (�). * * > It is a fatal error when an XML processor encounters an entity with an * > encoding that it is unable to process. It is a fatal error if an XML entity * > is determined (via default, encoding declaration, or higher-level protocol) * > to be in a certain encoding but contains byte sequences that are not legal * > in that encoding. Specifically, it is a fatal error if an entity encoded in * > UTF-8 contains any ill-formed code unit sequences, as defined in section * > 3.9 of Unicode [Unicode]. Unless an encoding is determined by a higher-level * > protocol, it is also a fatal error if an XML entity contains no encoding * > declaration and its content is not legal UTF-8 or UTF-16. * * See https://www.w3.org/TR/xml/#charencoding */ // @todo This is an error. Treat as plaintext and continue, which is wrong. ++$at; continue; } $decoded .= substr( $text, $was_at, $at - $was_at ); $decoded .= $character_reference; $at = $semi_at + 1; $was_at = $at; } if ( 0 === $was_at ) { return $text; } if ( $was_at < $end ) { $decoded .= substr( $text, $was_at, $end - $was_at ); } return $decoded; } /** * Finds and parses the next entity in a given text starting after the * given byte offset, and being entirely found within the given max length. * * @since {WP_VERSION} * * // @todo Implement this function. * * @param string $text Text in which to search for an XML entity. * @param int $starting_byte_offset Start looking after this byte offset. * @param int $ending_byte_offset Stop looking if entity is not fully contained before this byte offset. * @param int|null $entity_at Optional. If provided, will be set to byte offset where entity was * found, if found. Otherwise, will not be set. * * @return string|null Parsed entity, if parsed, otherwise `null`. */ public static function next_entity( string $text, int $starting_byte_offset, int $ending_byte_offset, ?int &$entity_at = null ): ?string { $at = $starting_byte_offset; $end = $ending_byte_offset; while ( $at < $end ) { $remaining = $end - $at; $amp_after = strcspn( $text, '&', $at, $remaining ); // There are no more possible entities. if ( $amp_after === $remaining ) { return null; } /* * @todo Move the decoding logic from `decode()` above into here, * then call this function in a loop from `decode()`. */ ++$at; } return null; } } php-toolkit/XML/LICENSE.md 0000777 00000043254 15251156632 0011107 0 ustar 00 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. php-toolkit/XML/composer.json 0000777 00000001373 15251156632 0012221 0 ustar 00 { "name": "wp-php-toolkit/xml", "description": "XML component for WordPress.", "type": "library", "authors": [ { "name": "Adam Zielinski", "email": "adam@adamziel.com" }, { "name": "WordPress Team", "email": "wordpress@wordpress.org" } ], "require": { "php": ">=7.2", "wp-php-toolkit/encoding": "^0.1" }, "autoload": { "classmap": [ "./" ], "exclude-from-classmap": [ "/Tests/" ] }, "require-dev": { "phpunit/phpunit": "^9.5" }, "archive": { "exclude": [ "**/.github/", "**/Tests/", "**/bin/" ] } } php-toolkit/XML/class-xmlprocessor.php 0000777 00000423256 15251156632 0014063 0 ustar 00 <?php namespace WordPress\XML; use WP_HTML_Span; use WP_HTML_Text_Replacement; use function WordPress\Encoding\compat\_wp_scan_utf8; use function WordPress\Encoding\utf8_ord; /** * XML API: XMLProcessor class * * Scans through an XML document to find specific tags, then * transforms those tags by adding, removing, or updating the * values of the XML attributes within that tag (opener). * * It implements a subset of the XML 1.0 specification (https://www.w3.org/TR/xml/) * and supports XML documents with the following characteristics: * * – XML 1.0 * – Well-formed * – UTF-8 encoded * – Not standalone (so can use external entities) * – No DTD, DOCTYPE, ATTLIST, ENTITY, or conditional sections (will fail on them) * * XML 1.1 is explicitly not a design goal here. Version 1.1 is * more complex specification and not so widely supported. * * ### Possible future direction for this module * * XMLProcessor only aims to support XML 1.0, which is mostly well-defined and * supported across the web. There are no plans to extend the parsing logic to XML 1.1, * which is a more complex specification and not so widely supported. * * @TODO: Include the cursor string in internal bookmarks and use it for seeking. * * @TODO: Track specific error states, expose informative messages, line * numbers, indexes, and other debugging info. * * @TODO: Skip over the following syntax elements instead of failing: * * <!DOCTYPE, see https://www.w3.org/TR/xml/#sec-prolog-dtd * * <!ATTLIST, see https://www.w3.org/TR/xml/#attdecls * * <!ENTITY, see https://www.w3.org/TR/xml/#sec-entity-decl * * <!NOTATION, see https://www.w3.org/TR/xml/#sec-entity-decl * * Conditional sections, see https://www.w3.org/TR/xml/#sec-condition-sect * * @package WordPress * @subpackage HTML-API * @since WP_VERSION */ /** * Core class used to modify attributes in an XML document for tags matching a query. * * ## Usage * * Use of this class requires three steps: * * 1. Create a new class instance with your input XML document. * 2. Find the tag(s) you are looking for. * 3. Request changes to the attributes in those tag(s). * * Example: * * $processor = XMLProcessor::create_from_string( $xml ); * if ( $processor->next_tag( 'option' ) ) { * $processor->set_attribute( '', 'selected', 'yes' ); * } * * ### Finding tags * * The `next_tag()` function moves the internal cursor through * your input XML document until it finds a tag meeting any of * the supplied restrictions in the optional query argument. If * no argument is provided then it will find the next XML tag, * regardless of what kind it is. * * If you want to _find whatever the next tag is_: * * $processor->next_tag(); * * | Goal | Query | * |----------------------------------------------------------|----------------------------------------------------------| * | Find any tag. | `$processor->next_tag();` | * | Find next image tag. | `$processor->next_tag( array( 'tag_name' => 'image' ) );`| * | Find next image tag (shorthand). | `$processor->next_tag( 'image' );` | * | Find next image tag in the "wp.org" namespace. | `$processor->next_tag( array( 'wp.org', 'image' ) );` | * * #### Namespace Examples * * To work with namespaces, you can use the `breadcrumbs` query format, where each breadcrumb is a tuple of (namespace prefix, local name): * * $xml = '<root xmlns:wp="http://wordpress.org/export/1.2/"><wp:image src="cat.jpg" /></root>'; * $processor = XMLProcessor::create_from_string( $xml ); * // Find the <wp:image> tag * if ( $processor->next_tag( array( 'http://wordpress.org/export/1.2/', 'image' ) ) ) { * // Get the namespace URI of the matched tag * $ns = $processor->get_tag_namespace(); // 'http://wordpress.org/export/1.2/' * // Get the value of the 'src' attribute * $src = $processor->get_attribute( $ns, 'src' ); * // Set a new attribute in the same namespace * $processor->set_attribute( $ns, 'alt', 'A cat' ); * } * * If a tag was found meeting your criteria then `next_tag()` * will return `true` and you can proceed to modify it. If it * returns `false`, it failed to find the tag and moved the cursor to the end of the file. * * Once the cursor reaches the end of the file the processor * is done and if you want to reach an earlier tag you will * need to recreate the processor and start over, as it's * unable to back up or move in reverse (except via bookmarks). * * #### Custom queries * * Sometimes it's necessary to further inspect an XML tag than * the query syntax here permits. In these cases one may further * inspect the search results using the read-only functions * provided by the processor or external state or variables. * * Example: * * // Paint up to the first five `musician` or `actor` tags marked with the "jazzy" style. * $remaining_count = 5; * while ( $remaining_count > 0 && $processor->next_tag() ) { * $tag = $processor->get_tag_local_name(); * if ( * ( 'musician' === $tag || 'actor' === $tag ) && * 'jazzy' === $processor->get_attribute( '', 'data-style' ) * ) { * $processor->set_attribute( '', 'theme-style', 'theme-style-everest-jazz' ); * $remaining_count--; * } * } * * `get_attribute()` will return `null` if the attribute wasn't present * on the tag when it was called. It may return `""` (the empty string) * in cases where the attribute was present but its value was empty. * For boolean attributes, those whose name is present but no value is * given, it will return `true` (the only way to set `false` for an * attribute is to remove it). * * #### When matching fails * * When `next_tag()` returns `false` it could mean different things: * * - The requested tag wasn't found in the input document. * - The input document ended in the middle of an XML syntax element. * * When a document ends in the middle of a syntax element it will pause * the processor. This is to make it possible in the future to extend the * input document and proceed - an important requirement for chunked * streaming parsing of a document. * * Example: * * $processor = XMLProcessor::create_from_string( 'This <content is="a" partial="token' ); * false === $processor->next_tag(); * * If a special element (see next section) is encountered but no closing tag * is found it will count as an incomplete tag. The parser will pause as if * the opening tag were incomplete. * * Example: * * $processor = XMLProcessor::create_from_string( '<style>// there could be more styling to come' ); * false === $processor->next_tag(); * * $processor = XMLProcessor::create_from_string( '<style>// this is everything</style><content>' ); * true === $processor->next_tag( 'content' ); * * * ### Modifying XML attributes for a found tag * * Once you've found the start of an opening tag you can modify * any number of the attributes on that tag. You can set a new * value for an attribute, remove the entire attribute, or do * nothing and move on to the next opening tag. * * Example: * * if ( $processor->next_tag( 'user-group' ) ) { * $processor->set_attribute( '', 'name', 'Content editors' ); * $processor->remove_attribute( '', 'data-test-id' ); * } * * If `set_attribute()` is called for an existing attribute it will * overwrite the existing value. Similarly, calling `remove_attribute()` * for a non-existing attribute has no effect on the document. Both * of these methods are safe to call without knowing if a given attribute * exists beforehand. * * #### Namespaced attribute example * * $processor = XMLProcessor::from_string( '<root xmlns:wp="http://wordpress.org/export/1.2/"><image /></root>' ); * * $ns = 'http://wordpress.org/export/1.2/'; * if ( $processor->next_tag( 'image' ) ) { * $processor->set_attribute( $ns, 'src', 'cat.jpg' ); * } * * echo $processor->get_modifiable_text(); * // <root xmlns:wp="http://wordpress.org/export/1.2/"><image wp:src="cat.jpg" /></root> * * ### Bookmarks * * While scanning through the input XML document it's possible to set * a named bookmark when a particular tag is found. Later on, after * continuing to scan other tags, it's possible to `seek` to one of * the set bookmarks and then proceed again from that point forward. * * Because bookmarks create processing overhead one should avoid * creating too many of them. As a rule, create only bookmarks * of known string literal names; avoid creating "mark_{$index}" * and so on. It's fine from a performance standpoint to create a * bookmark and update it frequently, such as within a loop. * * $total_todos = 0; * while ( $processor->next_tag( array( 'tag_name' => 'todo-list' ) ) ) { * $processor->set_bookmark( 'list-start' ); * while ( $processor->next_tag() ) { * if ( 'todo' === $processor->get_tag_local_name() && $processor->is_tag_closer() ) { * $processor->set_bookmark( 'list-end' ); * $processor->seek( 'list-start' ); * $processor->set_attribute( '', 'data-contained-todos', (string) $total_todos ); * $total_todos = 0; * $processor->seek( 'list-end' ); * break; * } * if ( 'todo-item' === $processor->get_tag_local_name() && ! $processor->is_tag_closer() ) { * $total_todos++; * } * } * } * * ## Tokens and finer-grained processing * * It's possible to scan through every lexical token in the * XML document using the `next_token()` function. This * alternative form takes no argument and provides no built-in * query syntax. * * Example: * * $title = '(untitled)'; * $text = ''; * while ( $processor->next_token() ) { * switch ( $processor->get_token_name() ) { * case '#text': * $text .= $processor->get_modifiable_text(); * break; * case 'new-line': * $text .= "\n"; * break; * case 'title': * $title = $processor->get_modifiable_text(); * break; * } * } * return trim( "# {$title}\n\n{$text}" ); * * ### Tokens and _modifiable text_ * * There are also non-elements which are void/self-closing in nature and contain * modifiable text that is part of that individual syntax token itself. * * - `#text` nodes, whose entire token _is_ the modifiable text. * - XML comments and tokens that become comments due to some syntax error. The * text for these tokens is the portion of the comment inside of the syntax. * E.g. for `<!-- comment -->` the text is `" comment "` (note the spaces are included). * - `CDATA` sections, whose text is the content inside of the section itself. E.g. for * `<![CDATA[some content]]>` the text is `"some content"`. * - XML Processing instruction nodes like `<?xml __( "Like" ); ?>` (with restrictions [1]). * * [1]: XML requires "xml" as a processing instruction name. The Tag Processor captures the entire * processing instruction as a single token up to the closing `?>`. * * ## Design and limitations * * The XMLProcessor is designed to linearly scan XML documents and tokenize * XML tags and their attributes. It's designed to do this as efficiently as * possible without compromising parsing integrity. Therefore it will be * slower than some methods of modifying XML, such as those incorporating * over-simplified PCRE patterns, but will not introduce the defects and * failures that those methods bring in, which lead to broken page renders * and often to security vulnerabilities. On the other hand, it will be faster * than full-blown XML parsers such as DOMDocument and use considerably * less memory. It requires a negligible memory overhead, enough to consider * it a zero-overhead system. * * The performance characteristics are maintained by avoiding tree construction. * * The XMLProcessor checks the most important aspects of XML integrity as it scans * through the document. It verifies that a single root element exists, that there are * no unclosed tags, and that each opener tag has a corresponding closer. It also * ensures no duplicate attributes exist on a single tag. * * At the same time, the XMLProcessor also skips expensive validation of XML entities * in the document. The processor will initially pass through invalid entity references * and only fail when the developer attempts to read their value. If that doesn't happen, * the invalid values will be left untouched in the final document. * * Most operations within the XMLProcessor are designed to minimize the difference * between an input and output document for any given change. For example, the * `set_attribute` and `remove_attribute` methods preserve whitespace and the attribute * ordering within the element definition. An exception to this rule is that all attribute * updates store their values as double-quoted strings, meaning that attributes on input with * single-quoted or unquoted values will appear in the output with double-quotes. * * ### Text Encoding * * The XMLProcessor assumes that the input XML document is encoded with a * UTF-8 encoding and will refuse to process documents that declare other encodings. * * ### Namespaces * * Namespaces are first-class citizens in the XMLProcessor. Methods such as `set_attribute()` and `remove_attribute()` * require the full namespace URI, not just the local name. The XML specification treats the local * name as a mere syntax sugar. The actual matching is always done on the fully qualified namespace name. * * Example: * * $processor = XMLProcessor::from_string( '<root xmlns:wp="http://wordpress.org/export/1.2/"><wp:image src="cat.jpg" /></root>' ); * $processor->next_tag( 'image' ); * $local_name = $processor->get_tag_local_name(); // 'image' * $ns = $processor->get_tag_namespace(); // 'http://wordpress.org/export/1.2/' * echo $processor->get_tag_namespace_and_local_name(); // '{http://wordpress.org/export/1.2/}image' * * #### Internal representation of names * * Internally, the XMLProcessor stores names using the following format: * * {namespace}local_name * * It's safe, because the "{" and "}" bytes cannot be used in tag names or attribute names: * * [4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] * [4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] * * @since WP_VERSION */ class XMLProcessor { /** * The maximum number of bookmarks allowed to exist at * any given time. * * @since WP_VERSION * @var int * * @see XMLProcessor::set_bookmark() */ const MAX_BOOKMARKS = 10; /** * Maximum number of times seek() can be called. * Prevents accidental infinite loops. * * @since WP_VERSION * @var int * * @see XMLProcessor::seek() */ const MAX_SEEK_OPS = 1000; /** * The XML document to parse. * * @since WP_VERSION * @var string */ public $xml; /** * Specifies mode of operation of the parser at any given time. * * | State | Meaning | * | ------------------|----------------------------------------------------------------------| * | *Ready* | The parser is ready to run. | * | *Complete* | There is nothing left to parse. | * | *Incomplete* | The XML ended in the middle of a token; nothing more can be parsed. | * | *Matched tag* | Found an XML tag; it's possible to modify its attributes. | * | *Text node* | Found a #text node; this is plaintext and modifiable. | * | *CDATA node* | Found a CDATA section; this is modifiable. | * | *PI node* | Found a processing instruction; this is modifiable. | * | *XML declaration* | Found an XML declaration; this is modifiable. | * | *Comment* | Found a comment or bogus comment; this is modifiable. | * * @since WP_VERSION * * @see XMLProcessor::STATE_READY * @see XMLProcessor::STATE_COMPLETE * @see XMLProcessor::STATE_INCOMPLETE_INPUT * @see XMLProcessor::STATE_MATCHED_TAG * @see XMLProcessor::STATE_TEXT_NODE * @see XMLProcessor::STATE_CDATA_NODE * @see XMLProcessor::STATE_PI_NODE * @see XMLProcessor::STATE_XML_DECLARATION * @see XMLProcessor::STATE_COMMENT * * @var string */ protected $parser_state = self::STATE_READY; /** * Whether the input has been finished. * * @var bool */ protected $expecting_more_input = true; /** * How many bytes from the current XML chunk have been read and parsed. * * This value points to the latest byte offset in the input document which * has been already parsed. It is the internal cursor for the Tag Processor * and updates while scanning through the XML tokens. * * @since WP_VERSION * @var int */ public $bytes_already_parsed = 0; /** * How many XML bytes from the original stream have already been removed * from the memory. * * @since WP_VERSION * @var int */ public $upstream_bytes_forgotten = 0; /** * Byte offset in the current `$xml` string where the currently recognized token starts. * `null` if no token is currently active. * * Example: * * <content id="test">... * ^-- token_starts_at = 0 * * @since WP_VERSION * * @var int|null */ protected $token_starts_at; /** * Byte length of current token. * * Example: * * <content id="test">... * 012345678901234 * - token length is 14 - 0 = 14 * * a <!-- comment --> is a token. * 0123456789 123456789 123456789 * - token length is 17 - 2 = 15 * * @since WP_VERSION * * @var int|null */ private $token_length; /** * Currently matched XML element object. * * @var XMLElement|null */ private $element; /** * Byte offset in input document where current tag name starts. * * Example: * * <content id="test">... * 01234 * - tag name starts at 1 * * @since WP_VERSION * * @var int|null */ private $tag_name_starts_at; /** * Byte length of current tag name. * * Example: * * <content id="test">... * 01234 * --- tag name length is 3 * * @since WP_VERSION * * @var int|null */ private $tag_name_length; /** * Byte offset into input document where current modifiable text starts. * * @since WP_VERSION * * @var int */ private $text_starts_at; /** * Byte length of modifiable text. * * @since WP_VERSION * * @var string */ private $text_length; /** * Whether the current tag is an opening tag, e.g. <content>, or a closing tag, e.g. </content>. * * @var bool */ private $is_closing_tag; /** * Stores the error for why something failed, if it did. * * @see self::get_last_error * * @since WP_VERSION * * @var string|null */ protected $last_error = null; /** * Stores context for why the parser bailed on unsupported XML, if it did. * * @see self::get_exception * * @var XMLUnsupportedException|null */ private $exception = null; /** * Temporary index of attributes found within an XML tag, keyed by the qualified * attribute name. It is only used during the initial attributes parsing phase and * discarded once all the attributes have been parsed. * * @since WP_VERSION * @var XMLAttributeToken[] */ private $qualified_attributes = array(); /** * Stores the attributes found within an XML tag, keyed by their namespace * and local name combination. * * Example: * * // Supposing the parser just finished parsing the wp:content tag: * // <channel xmlns:wp="http://wordpress.org/export/1.2/"> * // <wp:content wp:id="test-4" class="outline"> * // </channel> * // * // Then, the attributes array would be: * $this->attributes = array( * '{http://wordpress.org/export/1.2/}id' => new XMLAttributeToken( 9, 6, 5, 14, 'wp', 'id' ), * 'class' => new XMLAttributeToken( 23, 7, 17, 13, '', 'class', '' ) * ); * * @since WP_VERSION * @var XMLAttributeToken[] */ private $attributes = array(); /** * Tracks a semantic location in the original XML which * shifts with updates as they are applied to the document. * * @since WP_VERSION * @var WP_HTML_Span[] */ protected $bookmarks = array(); /** * Lexical replacements to apply to input XML document. * * "Lexical" in this class refers to the part of this class which * operates on pure text _as text_ and not as XML. There's a line * between the public interface, with XML-semantic methods like * `set_attribute` and `add_class`, and an internal state that tracks * text offsets in the input document. * * When higher-level XML methods are called, those have to transform their * operations (such as setting an attribute's value) into text diffing * operations (such as replacing the sub-string from indices A to B with * some given new string). These text-diffing operations are the lexical * updates. * * As new higher-level methods are added they need to collapse their * operations into these lower-level lexical updates since that's the * Tag Processor's internal language of change. Any code which creates * these lexical updates must ensure that they do not cross XML syntax * boundaries, however, so these should never be exposed outside of this * class or any classes which intentionally expand its functionality. * * These are enqueued while editing the document instead of being immediately * applied to avoid processing overhead, string allocations, and string * copies when applying many updates to a single document. * * Example: * * // Replace an attribute stored with a new value, indices * // sourced from the lazily-parsed XML recognizer. * $start = $attributes['src']->start; * $length = $attributes['src']->length; * $modifications[] = new WP_HTML_Text_Replacement( $start, $length, $new_value ); * * // Correspondingly, something like this will appear in this array. * $lexical_updates = array( * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) * ); * * @since WP_VERSION * @var WP_HTML_Text_Replacement[] */ protected $lexical_updates = array(); /** * The Name from the DOCTYPE declaration. * * ``` * doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' * ^^^^ * ``` * * @since WP_VERSION * @var WP_HTML_Span|null */ protected $doctype_name = null; /** * The system literal value from the DOCTYPE declaration. * * ``` * doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' * ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral * ^^^^^^^^^^^^^ * ``` * * Example: * * <!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> * * In this example, the system_literal would be: * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" * * @since WP_VERSION * @var WP_HTML_Span|null */ protected $system_literal = null; /** * The public identifier value from the DOCTYPE declaration. * * ``` * doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' * ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral * ``` * ^^^^^^^^^^^^ * Example: * * ``` * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> * ``` * * In this example, the publid_literal would be: * "-//W3C//DTD XHTML 1.0 Strict//EN" * * @since WP_VERSION * @var WP_HTML_Span|null */ protected $pubid_literal = null; /** * Memory budget for the processed XML. * * `append_bytes()` will flush the processed bytes whenever the XML buffer * exceeds this budget. The lexical updates will be applied and the bookmarks * will be reset. * * @var int */ protected $memory_budget = 1024 * 1024 * 1024; /** * Tracks and limits `seek()` calls to prevent accidental infinite loops. * * @since WP_VERSION * @var int * * @see XMLProcessor::seek() */ protected $seek_count = 0; /** * Indicates the current parsing stage. * * A well-formed XML document has the following structure: * * document ::= prolog element Misc* * prolog ::= XMLDecl? Misc* (doctypedecl Misc*)? * Misc ::= Comment | PI | S * * There is exactly one element, called the root. No elements or text nodes may * precede or follow it. * * See https://www.w3.org/TR/xml/#NT-document. * * | Stage | Meaning | * | ----------------|---------------------------------------------------------------------| * | *Prolog* | The parser is parsing the prolog. | * | *Element* | The parser is parsing the root element. | * | *Misc* | The parser is parsing miscellaneous content. | * * @see XMLProcessor::IN_PROLOG_CONTEXT * @see XMLProcessor::IN_ELEMENT_CONTEXT * @see XMLProcessor::IN_MISC_CONTEXT * * @since WP_VERSION * @var bool */ protected $parser_context = self::IN_PROLOG_CONTEXT; /** * Top-level namespaces for the currently parsed document. * * @var array */ private $document_namespaces; /** * Tracks open elements and their namespaces while scanning XML. * * @var array */ private $stack_of_open_elements = array(); public static function create_from_string( $xml, $cursor = null, $known_definite_encoding = 'UTF-8', $document_namespaces = array() ) { $processor = static::create_for_streaming( $xml, $cursor, $known_definite_encoding, $document_namespaces ); if ( null === $processor ) { return false; } $processor->input_finished(); return $processor; } public static function create_for_streaming( $xml = '', $cursor = null, $known_definite_encoding = 'UTF-8', $document_namespaces = array() ) { if ( 'UTF-8' !== $known_definite_encoding ) { return false; } $processor = new XMLProcessor( $xml, $document_namespaces, self::CONSTRUCTOR_UNLOCK_CODE ); if ( null !== $cursor && true !== $processor->initialize_from_cursor( $cursor ) ) { return false; } return $processor; } /** * Returns a re-entrancy cursor – it's a string that can instruct a new XML * Processor instance to continue parsing from the current location in the * document. * * The only stable part of this API is the return type of string. The consumer * of this method MUST NOT assume any specific structure of the returned * string. It will change without a warning between WordPress releases. * * This is not a tell() API. No XML Processor method will accept the cursor * to move to another location. The only way to use this cursor is creating * a new XML Processor instance. If you need to move around the document, use * `set_bookmark()` and `seek()`. */ public function get_reentrancy_cursor() { $stack_of_open_elements = array(); foreach ( $this->stack_of_open_elements as $element ) { $stack_of_open_elements[] = $element->to_array(); } return base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode json_encode( array( 'is_finished' => $this->is_finished(), 'upstream_bytes_forgotten' => $this->upstream_bytes_forgotten, 'parser_context' => $this->parser_context, 'stack_of_open_elements' => $stack_of_open_elements, 'expecting_more_input' => $this->expecting_more_input, 'document_namespaces' => $this->document_namespaces, ) ) ); } /** * Returns the byte offset in the input stream where the current token starts. * * You should probably not use this method. * * It's only exists to allow resuming the input stream at the same offset where * the XML parsing was finished. It will never expose any attribute's byte * offset and no method in the XML processor API will ever accept the byte offset * to move to another location. If you need to move around the document, use * `set_bookmark()` and `seek()` instead. */ public function get_token_byte_offset_in_the_input_stream() { return $this->token_starts_at + $this->upstream_bytes_forgotten; } protected function initialize_from_cursor( $cursor ) { if ( ! is_string( $cursor ) ) { _doing_it_wrong( __METHOD__, 'Cursor must be a JSON-encoded string.', '1.0.0' ); return false; } $cursor = base64_decode( $cursor ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode if ( false === $cursor ) { _doing_it_wrong( __METHOD__, 'Invalid cursor provided to initialize_from_cursor().', '1.0.0' ); return false; } $cursor = json_decode( $cursor, true ); if ( false === $cursor ) { _doing_it_wrong( __METHOD__, 'Invalid cursor provided to initialize_from_cursor().', '1.0.0' ); return false; } if ( $cursor['is_finished'] ) { $this->parser_state = self::STATE_COMPLETE; } // Assume the input stream will start from the last known byte offset. $this->bytes_already_parsed = 0; $this->upstream_bytes_forgotten = $cursor['upstream_bytes_forgotten']; $this->stack_of_open_elements = array(); foreach ( $cursor['stack_of_open_elements'] as $element ) { array_push( $this->stack_of_open_elements, XMLElement::from_array( $element ) ); } $this->document_namespaces = $cursor['document_namespaces']; $this->parser_context = $cursor['parser_context']; $this->expecting_more_input = $cursor['expecting_more_input']; return true; } /** * Constructor. * * Do not use this method. Use the static creator methods instead. * * @access private * * @param string $xml XML to process. * @param array $document_namespaces Document namespaces. * @param string|null $use_the_static_create_methods_instead This constructor should not be called manually. * * @see XMLProcessor::create_stream() * * @since 6.4.0 * * @see XMLProcessor::create_fragment() */ protected function __construct( $xml, $document_namespaces = array(), $use_the_static_create_methods_instead = null ) { if ( self::CONSTRUCTOR_UNLOCK_CODE !== $use_the_static_create_methods_instead ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s: XMLProcessor::create_fragment(). */ __( 'Call %s to create an XML Processor instead of calling the constructor directly.' ), '<code>XMLProcessor::create_fragment()</code>' ), '6.4.0' ); } $this->xml = isset( $xml ) ? $xml : ''; $this->document_namespaces = array_merge( $document_namespaces, // These initial namespaces cannot be overridden. array( 'xml' => 'http://www.w3.org/XML/1998/namespace', // Predefined, cannot be unbound or changed. 'xmlns' => 'http://www.w3.org/2000/xmlns/', // Reserved for xmlns attributes, not a real namespace for elements/attributes. '' => '', // Default namespace is initially empty (no namespace). ) ); } /** * Wipes out the processed XML and appends the next chunk of XML to * any remaining unprocessed XML. * * @param string $next_chunk XML to append. */ public function append_bytes( $next_chunk ) { if ( ! $this->expecting_more_input ) { _doing_it_wrong( __METHOD__, __( 'Cannot append bytes after the last input chunk was provided and input_finished() was called.' ), 'WP_VERSION' ); return false; } $this->xml .= $next_chunk; if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { $this->parser_state = self::STATE_READY; } // Periodically flush the processed bytes to avoid high memory usage. if ( null !== $this->memory_budget && strlen( $this->xml ) > $this->memory_budget ) { $this->flush_processed_xml(); } return true; } /** * Forgets the XML bytes that have been processed and are no longer needed to * avoid high memory usage. * * @return string The flushed bytes. */ private function flush_processed_xml() { // Flush updates. $this->get_updated_xml(); $unreferenced_bytes = $this->bytes_already_parsed; if ( null !== $this->token_starts_at ) { $unreferenced_bytes = min( $unreferenced_bytes, $this->token_starts_at ); } $flushed_bytes = substr( $this->xml, 0, $unreferenced_bytes ); $this->xml = substr( $this->xml, $unreferenced_bytes ); $this->bookmarks = array(); $this->lexical_updates = array(); $this->seek_count = 0; $this->bytes_already_parsed -= $unreferenced_bytes; if ( null !== $this->token_starts_at ) { $this->token_starts_at -= $unreferenced_bytes; } if ( null !== $this->tag_name_starts_at ) { $this->tag_name_starts_at -= $unreferenced_bytes; } if ( null !== $this->text_starts_at ) { $this->text_starts_at -= $unreferenced_bytes; } $this->upstream_bytes_forgotten += $unreferenced_bytes; return $flushed_bytes; } /** * Indicates that all the XML document bytes have been provided. * * After calling this method, the processor will emit errors where * previously it would have entered the STATE_INCOMPLETE_INPUT state. */ public function input_finished() { $this->expecting_more_input = false; $this->parser_state = self::STATE_READY; } /** * Indicates if the processor is expecting more data bytes. * If not, the processor will expect the remaining XML bytes to form * a valid document and will not stop on incomplete input. * * @return bool Whether the processor is expecting more data bytes. */ public function is_expecting_more_input() { return $this->expecting_more_input; } /** * Internal method which finds the next token in the XML document. * * This method is a protected internal function which implements the logic for * finding the next token in a document. It exists so that the parser can update * its state without affecting the location of the cursor in the document and * without triggering subclass methods for things like `next_token()`, e.g. when * applying patches before searching for the next token. * * @return bool Whether a token was parsed. * @since 6.5.0 * * @access private */ protected function parse_next_token() { $was_at = $this->bytes_already_parsed; $this->after_tag(); // Don't proceed if there's nothing more to scan. if ( self::STATE_COMPLETE === $this->parser_state || self::STATE_INCOMPLETE_INPUT === $this->parser_state || null !== $this->last_error ) { return false; } /* * The next step in the parsing loop determines the parsing state; * clear it so that state doesn't linger from the previous step. */ $this->parser_state = self::STATE_READY; if ( $this->bytes_already_parsed >= strlen( $this->xml ) ) { if ( $this->expecting_more_input ) { $this->parser_state = self::STATE_INCOMPLETE_INPUT; } else { $this->parser_state = self::STATE_COMPLETE; } return false; } // Find the next tag if it exists. if ( false === $this->parse_next_tag() ) { if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { $this->bytes_already_parsed = $was_at; } return false; } if ( null !== $this->last_error ) { return false; } /* * For legacy reasons the rest of this function handles tags and their * attributes. If the processor has reached the end of the document * or if it matched any other token then it should return here to avoid * attempting to process tag-specific syntax. */ if ( self::STATE_INCOMPLETE_INPUT !== $this->parser_state && self::STATE_COMPLETE !== $this->parser_state && self::STATE_MATCHED_TAG !== $this->parser_state ) { return true; } if ( $this->is_closing_tag ) { $this->skip_whitespace(); } else { // Parse all of its attributes. while ( $this->parse_next_attribute() ) { continue; } } if ( null !== $this->last_error ) { return false; } if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { $this->bytes_already_parsed = $was_at; return false; } // Ensure that the tag closes before the end of the document. if ( $this->bytes_already_parsed >= strlen( $this->xml ) ) { // Does this appropriately clear state (parsed attributes)? $this->mark_incomplete_input( 'Tag attributes were not closed before the end of the document.' ); $this->bytes_already_parsed = $was_at; return false; } $tag_ends_at = strpos( $this->xml, '>', $this->bytes_already_parsed ); if ( false === $tag_ends_at ) { $this->mark_incomplete_input( 'No > found at the end of a tag.' ); $this->bytes_already_parsed = $was_at; return false; } if ( $this->is_closing_tag && $tag_ends_at !== $this->bytes_already_parsed ) { $this->bail( 'Invalid closing tag encountered.', self::ERROR_SYNTAX ); return false; } $this->parser_state = self::STATE_MATCHED_TAG; $this->bytes_already_parsed = $tag_ends_at + 1; $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; /** * Resolve the namespaces defined in opening tags. */ if ( ! $this->is_closing_tag ) { /** * By default, inherit all namespaces from the parent element. */ $namespaces = $this->get_tag_namespaces_in_scope(); foreach ( $this->qualified_attributes as $attribute ) { /** * `xmlns` attribute is the default namespace * `xmlns:<prefix>` declares a namespace prefix scoped to the current element and its descendants * * @see https://www.w3.org/TR/2006/REC-xml-names11-20060816/#ns-decl */ if ( 'xmlns' === $attribute->qualified_name ) { $value = $this->get_qualified_attribute( $attribute->qualified_name ); // Update the default namespace. $namespaces[''] = $value; continue; } if ( 'xmlns' === $attribute->namespace_prefix ) { $value = $this->get_qualified_attribute( $attribute->qualified_name ); /** * @see https://www.w3.org/TR/2006/REC-xml-names11-20060816/#xmlReserved */ if ( 'xml' === $attribute->local_name && 'http://www.w3.org/XML/1998/namespace' !== $value ) { $this->bail( 'The `xml` namespace prefix is by definition bound to the namespace name http://www.w3.org/XML/1998/namespace and must not be overridden.', self::ERROR_SYNTAX ); return false; } /** * @see https://www.w3.org/TR/2006/REC-xml-names11-20060816/#xmlReserved */ if ( 'xmlns' === $attribute->local_name ) { $this->bail( 'The `xmlns` namespace prefix must not be overridden.', self::ERROR_SYNTAX ); return false; } /** * The attribute value in a namespace declaration for a prefix MAY be empty. * This has the effect, within the scope of the declaration, of removing any * association of the prefix with a namespace name. Further declarations MAY * re-declare the prefix again. */ if ( '' === $value ) { unset( $namespaces[ $attribute->namespace_prefix ] ); continue; } $namespaces[ $attribute->local_name ] = $value; continue; } } /** * Confirm the tag name is valid with respect to XML namespaces. * * @see https://www.w3.org/TR/2006/REC-xml-names11-20060816/#Conformance */ $tag_name = $this->get_tag_name_qualified(); if ( false === $this->validate_qualified_name( $tag_name ) ) { return false; } list( $tag_namespace_prefix, $tag_local_name ) = $this->parse_qualified_name( $tag_name ); /** * Validate the element namespace. */ if ( ! array_key_exists( $tag_namespace_prefix, $namespaces ) ) { $this->bail( sprintf( 'Namespace prefix "%s" does not resolve to any namespace in the current element\'s scope.', $tag_namespace_prefix ), self::ERROR_SYNTAX ); } /** * Compute fully qualified attributes and assert: * * – All attributes have valid namespaces. * – No two attributes have the same (local name, namespace) pair. * * @see https://www.w3.org/TR/2006/REC-xml-names11-20060816/#uniqAttrs */ $namespaced_attributes = array(); foreach ( $this->qualified_attributes as $attribute ) { list( $attribute_namespace_prefix, $attribute_local_name ) = $this->parse_qualified_name( $attribute->qualified_name ); if ( ! array_key_exists( $attribute_namespace_prefix, $namespaces ) ) { $this->bail( sprintf( 'Attribute "%s" has an invalid namespace prefix "%s".', $attribute->qualified_name, $attribute_namespace_prefix ), self::ERROR_SYNTAX ); return false; } $namespace_reference = $attribute_namespace_prefix ? $namespaces[ $attribute_namespace_prefix ] : ''; /** * It looks supicious but it's safe – $local_name is guaranteed to not contain * curly braces at this point. */ $attribute_full_name = $namespace_reference ? '{' . $namespace_reference . '}' . $attribute_local_name : $attribute_local_name; if ( isset( $namespaced_attributes[ $attribute_full_name ] ) ) { $this->bail( sprintf( 'Duplicate attribute "%s" with namespace "%s" found in the same element.', $attribute_local_name, $namespace_reference ), self::ERROR_SYNTAX ); return false; } $namespaced_attributes[ $attribute_full_name ] = $attribute; $attribute->namespace = $namespace_reference; } // Store attributes with their namespaces and discard the temporary // qualified attributes array. $this->attributes = $namespaced_attributes; $this->qualified_attributes = array(); $this->element = new XMLElement( $tag_local_name, $tag_namespace_prefix, $namespaces[ $tag_namespace_prefix ], $namespaces ); // Closers assume $this->element is the element created for the opener. // @see step_in_element. } /* * Preserve the opening tag pointers, as these will be overwritten * when finding the closing tag. They will be reset after finding * the closing to tag to point to the opening of the special atomic * tag sequence. */ $tag_name_starts_at = $this->tag_name_starts_at; $tag_name_length = $this->tag_name_length; $tag_ends_at = $this->token_starts_at + $this->token_length; $attributes = $this->qualified_attributes; /* * The values here look like they reference the opening tag but they reference * the closing tag instead. This is why the opening tag values were stored * above in a variable. It reads confusingly here, but that's because the * functions that skip the contents have moved all the internal cursors past * the inner content of the tag. */ $this->token_starts_at = $was_at; $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; $this->text_starts_at = $tag_ends_at; $this->text_length = $this->tag_name_starts_at - $this->text_starts_at; $this->tag_name_starts_at = $tag_name_starts_at; $this->tag_name_length = $tag_name_length; $this->qualified_attributes = $attributes; return true; } private function get_tag_namespaces_in_scope() { $top = $this->top_element(); if ( null === $top ) { // Namespaces defined by default in every XML document. return $this->document_namespaces; } return $top->namespaces_in_scope; } /** * Returns the namespace prefix of the matched tag or, when the $namespace * argument is given, the prefix of the requested fully-qualified namespace . * * Examples: * * $p = new XMLProcessor( '<wp:content xmlns:xhtml="http://www.w3.org/1999/xhtml">Test</wp:content>' ); * $p->next_tag() === true; * $p->get_tag_namespace_prefix() === 'xhtml'; * * $p = new XMLProcessor( ' * <wp:content * xmlns:xhtml="http://www.w3.org/1999/xhtml" * xmlns:wp="http://wordpress.org/export/1.2/" * > * Test * </wp:content> * ' ); * $p->next_tag() === true; * $p->get_tag_namespace_prefix('http://wordpress.org/export/1.2/') === 'wp'; * * @internal * @param string|null $xml_namespace Fully-qualified namespace to return the prefix for. * @return string|null The namespace prefix of the matched tag, or null if not available. */ private function get_tag_namespace_prefix( $xml_namespace = null ) { if ( null === $xml_namespace ) { if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { return null; } return $this->element->namespace_prefix; } else { $namespaces_in_scope = $this->get_tag_namespaces_in_scope(); foreach ( $namespaces_in_scope as $prefix => $uri ) { if ( $uri === $xml_namespace ) { return $prefix; } } return false; } } /** * Returns the top XMLElement on the stack without removing it. * * @return XMLElement|null Returns the top element, or null if stack is empty. */ private function top_element() { if ( empty( $this->stack_of_open_elements ) ) { return null; } return $this->stack_of_open_elements[ count( $this->stack_of_open_elements ) - 1 ]; } /** * Whether the processor paused because the input XML document ended * in the middle of a syntax element, such as in the middle of a tag. * * Example: * * $processor = new XMLProcessor( '<input type="text" value="Th' ); * false === $processor->get_next_tag(); * true === $processor->is_paused_at_incomplete_token(); * * @return bool Whether the parse paused at the start of an incomplete token. * @since WP_VERSION */ public function is_paused_at_incomplete_input(): bool { return self::STATE_INCOMPLETE_INPUT === $this->parser_state; } /** * Whether the processor finished processing. * * @return bool Whether the processor finished processing. * @since WP_VERSION */ public function is_finished() { return self::STATE_COMPLETE === $this->parser_state; } /** * Sets a bookmark in the XML document. * * Bookmarks represent specific places or tokens in the XML * document, such as a tag opener or closer. When applying * edits to a document, such as setting an attribute, the * text offsets of that token may shift; the bookmark is * kept updated with those shifts and remains stable unless * the entire span of text in which the token sits is removed. * * Release bookmarks when they are no longer needed. * * Example: * * <main><h2>Surprising fact you may not know!</h2></main> * ^ ^ * \-|-- this `H2` opener bookmark tracks the token * * <main class="clickbait"><h2>Surprising fact you may no… * ^ ^ * \-|-- it shifts with edits * * Bookmarks provide the ability to seek to a previously-scanned * place in the XML document. This avoids the need to re-scan * the entire document. * * Example: * * <ul><li>One</li><li>Two</li><li>Three</li></ul> * ^^^^ * want to note this last item * * $p = new XMLProcessor( $xml ); * $in_list = false; * while ( $p->next_tag( array( 'tag_closers' => $in_list ? 'visit' : 'skip' ) ) ) { * if ( 'UL' === $p->get_qualified_tag() ) { * if ( $p->is_tag_closer() ) { * $in_list = false; * $p->set_bookmark( 'resume' ); * if ( $p->seek( 'last-li' ) ) { * $p->add_class( 'last-li' ); * } * $p->seek( 'resume' ); * $p->release_bookmark( 'last-li' ); * $p->release_bookmark( 'resume' ); * } else { * $in_list = true; * } * } * * if ( 'LI' === $p->get_qualified_tag() ) { * $p->set_bookmark( 'last-li' ); * } * } * * Bookmarks intentionally hide the internal string offsets * to which they refer. They are maintained internally as * updates are applied to the XML document and therefore * retain their "position" - the location to which they * originally pointed. The inability to use bookmarks with * functions like `substr` is therefore intentional to guard * against accidentally breaking the XML. * * Because bookmarks allocate memory and require processing * for every applied update, they are limited and require * a name. They should not be created with programmatically-made * names, such as "li_{$index}" with some loop. As a general * rule they should only be created with string-literal names * like "start-of-section" or "last-paragraph". * * Bookmarks are a powerful tool to enable complicated behavior. * Consider double-checking that you need this tool if you are * reaching for it, as inappropriate use could lead to broken * XML structure or unwanted processing overhead. * * @param string $name Identifies this particular bookmark. * * @return bool Whether the bookmark was successfully created. * @since WP_VERSION */ public function set_bookmark( $name ) { // It only makes sense to set a bookmark if the parser has paused on a concrete token. if ( self::STATE_COMPLETE === $this->parser_state || self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { return false; } if ( ! array_key_exists( $name, $this->bookmarks ) && count( $this->bookmarks ) >= static::MAX_BOOKMARKS ) { _doing_it_wrong( __METHOD__, __( 'Too many bookmarks: cannot create any more.' ), 'WP_VERSION' ); return false; } $this->bookmarks[ $name ] = new WP_HTML_Span( $this->token_starts_at, $this->token_length ); return true; } /** * Removes a bookmark that is no longer needed. * * Releasing a bookmark frees up the small * performance overhead it requires. * * @param string $name Name of the bookmark to remove. * * @return bool Whether the bookmark already existed before removal. */ public function release_bookmark( $name ) { if ( ! array_key_exists( $name, $this->bookmarks ) ) { return false; } unset( $this->bookmarks[ $name ] ); return true; } /** * Returns the last error, if any. * * Various situations lead to parsing failure but this class will * return `false` in all those cases. To determine why something * failed it's possible to request the last error. This can be * helpful to know to distinguish whether a given tag couldn't * be found or if content in the document caused the processor * to give up and abort processing. * * Example * * $processor = XMLProcessor::create_fragment( '<content invalid-attr></content>' ); * false === $processor->next_tag(); * XMLProcessor::ERROR_SYNTAX === $processor->get_last_error(); * * @return string|null The last error, if one exists, otherwise null. * @see self::ERROR_UNSUPPORTED * @see self::ERROR_EXCEEDED_MAX_BOOKMARKS * * @since WP_VERSION */ public function get_last_error(): ?string { return $this->last_error; } /** * Finds the next element matching the $query. * * This doesn't currently have a way to represent non-tags and doesn't process * semantic rules for text nodes. For access to the raw tokens consider using * XMLProcessor instead. * * @param array|string|null $query_or_ns { * Optional. Which tag name to find, having which class, etc. Default is to find any tag. * * @type string|null $tag_name Which tag to find, or `null` for "any tag." * @type int|null $match_offset Find the Nth tag matching all search criteria. * 1 for "first" tag, 3 for "third," etc. * Defaults to first tag. * @type string[] $breadcrumbs DOM sub-path at which element is found, e.g. `array( 'FIGURE', 'IMG' )`. * May also contain the wildcard `*` which matches a single element, e.g. `array( 'SECTION', '*' )`. * } * @return bool Whether a tag was matched. * @since WP_VERSION */ public function next_tag( $query_or_ns = null, $null_or_local_name = null ) { if ( null === $query_or_ns && null === $null_or_local_name ) { while ( $this->step() ) { if ( '#tag' !== $this->get_token_type() ) { continue; } if ( ! $this->is_tag_closer() ) { return true; } } return false; } if ( is_string( $query_or_ns ) ) { if ( is_string( $null_or_local_name ) ) { $query = array( 'breadcrumbs' => array( array( $query_or_ns, $null_or_local_name ) ) ); } else { $query = array( 'breadcrumbs' => array( array( '', $query_or_ns ) ) ); } } else { $query = $query_or_ns; } if ( ! is_array( $query ) ) { _doing_it_wrong( __METHOD__, __( 'Please pass a query array to this function.' ), 'WP_VERSION' ); return false; } if ( array( 0, 1 ) === array_keys( $query ) && is_string( $query[0] ) && is_string( $query[1] ) ) { $query = array( 'breadcrumbs' => array( $query ) ); } if ( ! ( array_key_exists( 'breadcrumbs', $query ) && is_array( $query['breadcrumbs'] ) ) ) { while ( $this->step() ) { if ( '#tag' !== $this->get_token_type() ) { continue; } if ( ! $this->is_tag_closer() ) { return true; } } return false; } if ( isset( $query['tag_closers'] ) && 'visit' === $query['tag_closers'] ) { _doing_it_wrong( __METHOD__, __( 'Cannot visit tag closers in XML Processor.' ), 'WP_VERSION' ); return false; } $namespaced_breadcrumbs = array(); foreach ( $query['breadcrumbs'] as $breadcrumb ) { if ( is_array( $breadcrumb ) && 2 === count( $breadcrumb ) ) { $namespaced_breadcrumbs[] = $breadcrumb; } elseif ( is_string( $breadcrumb ) ) { $namespaced_breadcrumbs[] = array( '', $breadcrumb ); } else { _doing_it_wrong( __METHOD__, __( 'Breadcrumbs must be an array of strings or two-tuples of (namespace, local name).' ), 'WP_VERSION' ); } } $breadcrumbs = $namespaced_breadcrumbs; $match_offset = isset( $query['match_offset'] ) ? (int) $query['match_offset'] : 1; while ( $match_offset > 0 && $this->step() ) { if ( '#tag' !== $this->get_token_type() ) { continue; } if ( $this->matches_breadcrumbs( $breadcrumbs ) && 0 === --$match_offset ) { return true; } } return false; } /** * Parses the next tag. * * This will find and start parsing the next tag, including * the opening `<`, the potential closer `/`, and the tag * name. It does not parse the attributes or scan to the * closing `>`; these are left for other methods. * * @return bool Whether a tag was found before the end of the document. * @since WP_VERSION */ private function parse_next_tag() { $this->after_tag(); $xml = $this->xml; $doc_length = strlen( $xml ); $was_at = $this->bytes_already_parsed; $at = $was_at; while ( false !== $at && $at < $doc_length ) { $at = strpos( $xml, '<', $at ); if ( false === $at ) { break; } if ( $at > $was_at ) { $this->parser_state = self::STATE_TEXT_NODE; $this->token_starts_at = $was_at; $this->token_length = $at - $was_at; $this->text_starts_at = $was_at; $this->text_length = $this->token_length; $this->bytes_already_parsed = $at; return true; } $this->token_starts_at = $at; if ( $at + 1 < $doc_length && '/' === $this->xml[ $at + 1 ] ) { $this->is_closing_tag = true; ++$at; } else { $this->is_closing_tag = false; } if ( $at + 1 >= $doc_length ) { $this->mark_incomplete_input(); return false; } /* * XML tag names are defined by the same `Name` grammar rule as attribute * names. * * Reference: * – https://www.w3.org/TR/xml/#NT-STag * – https://www.w3.org/TR/xml/#NT-Name */ $tag_name_length = $this->parse_name( $at + 1 ); if ( false === $tag_name_length ) { return false; } if ( $tag_name_length > 0 ) { ++$at; $this->parser_state = self::STATE_MATCHED_TAG; $this->tag_name_starts_at = $at; $this->tag_name_length = $tag_name_length; $this->token_length = $this->tag_name_length; $this->bytes_already_parsed = $at + $this->tag_name_length; return true; } /* * Abort if no tag is found before the end of * the document. There is nothing left to parse. */ if ( $at + 1 >= $doc_length ) { $this->mark_incomplete_input( 'No more tags found before the end of the document.' ); return false; } /* * `<!` indicates one of a few possible constructs: */ if ( ! $this->is_closing_tag && '!' === $xml[ $at + 1 ] ) { /* * `<!--` mark a beginning of a comment. * https://www.w3.org/TR/xml/#sec-comments */ if ( $doc_length > $at + 3 && '-' === $xml[ $at + 2 ] && '-' === $xml[ $at + 3 ] ) { $closer_at = $at + 4; // If it's not possible to close the comment then there is nothing more to scan. if ( $doc_length <= $closer_at ) { $this->mark_incomplete_input( 'The document ends with a comment opener.' ); return false; } /* * Comments may only be closed by a --> sequence. */ --$closer_at; // Pre-increment inside condition below reduces risk of accidental infinite looping. while ( ++$closer_at < $doc_length ) { $closer_at = strpos( $xml, '--', $closer_at ); if ( false === $closer_at || $closer_at + 2 === $doc_length ) { $this->mark_incomplete_input( 'Unclosed comment.' ); return false; } /* * The string " -- " (double-hyphen) must not occur within comments * See https://www.w3.org/TR/xml/#sec-comments */ if ( '>' !== $xml[ $closer_at + 2 ] ) { $this->bail( 'Invalid comment syntax', self::ERROR_SYNTAX ); } $this->parser_state = self::STATE_COMMENT; $this->token_length = $closer_at + 3 - $this->token_starts_at; $this->text_starts_at = $this->token_starts_at + 4; $this->text_length = $closer_at - $this->text_starts_at; $this->bytes_already_parsed = $closer_at + 3; return true; } } /* * Identify CDATA sections. * * Within a CDATA section, everything until the ]]> string is treated * as data, not markup. Left angle brackets and ampersands may occur in * their literal form; they need not (and cannot) be escaped using "<" * and "&". CDATA sections cannot nest. * * See https://www.w3.org/TR/xml11.xml/#sec-cdata-sect */ if ( $doc_length > $this->token_starts_at + 8 && '[' === $xml[ $this->token_starts_at + 2 ] && 'C' === $xml[ $this->token_starts_at + 3 ] && 'D' === $xml[ $this->token_starts_at + 4 ] && 'A' === $xml[ $this->token_starts_at + 5 ] && 'T' === $xml[ $this->token_starts_at + 6 ] && 'A' === $xml[ $this->token_starts_at + 7 ] && '[' === $xml[ $this->token_starts_at + 8 ] ) { $closer_at = strpos( $xml, ']]>', $at + 1 ); if ( false === $closer_at ) { $this->mark_incomplete_input( 'Unclosed CDATA section' ); return false; } $this->parser_state = self::STATE_CDATA_NODE; $this->token_length = $closer_at + 1 - $this->token_starts_at; $this->text_starts_at = $this->token_starts_at + 9; $this->text_length = $closer_at - $this->text_starts_at; $this->bytes_already_parsed = $closer_at + 3; return true; } /* * Identify DOCTYPE nodes. * * doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' * ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral * SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") * PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'" * PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%] * See https://www.w3.org/TR/xml11.html/#dtd */ if ( $doc_length > $this->token_starts_at + 8 && 'D' === $xml[ $at + 2 ] && 'O' === $xml[ $at + 3 ] && 'C' === $xml[ $at + 4 ] && 'T' === $xml[ $at + 5 ] && 'Y' === $xml[ $at + 6 ] && 'P' === $xml[ $at + 7 ] && 'E' === $xml[ $at + 8 ] ) { $at += 9; // Skip whitespace. $at += strspn( $this->xml, " \t\f\r\n", $at ); if ( $doc_length <= $at ) { $this->mark_incomplete_input( 'Unclosed DOCTYPE declaration.' ); return false; } // @TODO: Expose the "name" value instead of skipping it like that. $name_length = $this->parse_name( $at ); if ( false === $name_length ) { $this->mark_incomplete_input( 'Unclosed DOCTYPE declaration.' ); return false; } $this->doctype_name = new WP_HTML_Span( $at, $name_length ); $at += $name_length; // Skip whitespace. $at += strspn( $this->xml, " \t\f\r\n", $at ); if ( $doc_length <= $at ) { $this->mark_incomplete_input( 'Unclosed DOCTYPE declaration.' ); return false; } // Check for SYSTEM or PUBLIC identifiers. if ( $doc_length > $at + 6 && 'S' === $this->xml[ $at ] && 'Y' === $this->xml[ $at + 1 ] && 'S' === $this->xml[ $at + 2 ] && 'T' === $this->xml[ $at + 3 ] && 'E' === $this->xml[ $at + 4 ] && 'M' === $this->xml[ $at + 5 ] ) { $at += 6; // Skip whitespace. $at += strspn( $this->xml, " \t\f\r\n", $at ); // Parse the SystemLiteral token. $quoted_string_length = $this->parse_quoted_string( $at ); if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { $this->mark_incomplete_input( 'Unclosed SYSTEM literal.' ); return false; } $this->system_literal = new WP_HTML_Span( // Start after the opening quote. $at + 1, // Exclude the closing quote. $quoted_string_length - 2 ); $at += $quoted_string_length; } elseif ( $doc_length > $at + 6 && 'P' === $this->xml[ $at ] && 'U' === $this->xml[ $at + 1 ] && 'B' === $this->xml[ $at + 2 ] && 'L' === $this->xml[ $at + 3 ] && 'I' === $this->xml[ $at + 4 ] && 'C' === $this->xml[ $at + 5 ] ) { $at += 6; // Skip whitespace. $at += strspn( $this->xml, " \t\f\r\n", $at ); /* * PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'" * PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%] */ $opening_quote_char = $this->xml[ $at ]; if ( "'" !== $opening_quote_char && '"' !== $opening_quote_char ) { $this->bail( 'Unsupported DOCTYPE syntax. PUBLIC identifiers must be enclosed in double quotes.' ); return false; } $pubid_char = " \r\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-()+,./:=?;!*#@\$_%"; if ( "'" === $opening_quote_char ) { $pubid_char .= "'"; } $pubid_literal_length = strspn( $this->xml, $pubid_char, $at + 1 ); $this->pubid_literal = new WP_HTML_Span( $at + 1, $pubid_literal_length ); $at += $pubid_literal_length + 2; // Skip whitespace. $at += strspn( $this->xml, " \t\f\r\n", $at ); // Parse the SystemLiteral token. $quoted_string_length = $this->parse_quoted_string( $at ); if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { $this->mark_incomplete_input( 'Unclosed SYSTEM literal.' ); return false; } $this->system_literal = new WP_HTML_Span( // Start after the opening quote. $at + 1, // Exclude the closing quote. $quoted_string_length - 2 ); $at += $quoted_string_length; } elseif ( '[' === $this->xml[ $at ] ) { $this->bail( 'Inline entity declarations are not yet supported in DOCTYPE declarations.', self::ERROR_SYNTAX ); } // Skip whitespace. $at += strspn( $this->xml, " \t\f\r\n", $at ); if ( '>' !== $this->xml[ $at ] ) { $this->bail( sprintf( 'Syntax error in DOCTYPE declaration. Unexpected character "%s" at position %d.', $this->xml[ $at ], $at ), self::ERROR_SYNTAX ); } $closer_at = $at; $this->parser_state = self::STATE_DOCTYPE_NODE; $this->token_length = $closer_at + 1 - $this->token_starts_at; $this->bytes_already_parsed = $closer_at + 1; return true; } /* * Anything else here is either unsupported at this point or invalid * syntax. See the class-level @TODO annotations for more information. */ $this->mark_incomplete_input( 'Unsupported <! syntax.' ); return false; } /* * An `<?xml` token at the beginning of the document marks a start of an * xml declaration. * See https://www.w3.org/TR/xml/#sec-prolog-dtd */ if ( 0 === $at && 0 === $this->upstream_bytes_forgotten && ! $this->is_closing_tag && '?' === $xml[ $at + 1 ] && 'x' === $xml[ $at + 2 ] && 'm' === $xml[ $at + 3 ] && 'l' === $xml[ $at + 4 ] ) { // Setting the parser state early for the get_attribute_by_qualified_name() calls later in this // branch. $this->parser_state = self::STATE_XML_DECLARATION; $at += 5; // Skip whitespace. $at += strspn( $this->xml, " \t\f\r\n", $at ); $this->bytes_already_parsed = $at; /* * Reuse parse_next_attribute() to parse the XML declaration attributes. * Technically, only "version", "encoding", and "standalone" are accepted * and, unlike regular tag attributes, their values can contain any character * other than the opening quote. However, the "<" and "&" characters are very * unlikely to be encountered and cause trouble, so this code path liberally * does not provide a dedicated parsing logic. */ while ( false !== $this->parse_next_attribute() ) { $this->skip_whitespace(); // Parse until the XML declaration closer. if ( '?' === $xml[ $this->bytes_already_parsed ] ) { break; } } if ( null !== $this->last_error ) { return false; } foreach ( $this->qualified_attributes as $name => $attribute ) { if ( 'version' !== $name && 'encoding' !== $name && 'standalone' !== $name ) { $this->bail( 'Invalid attribute found in XML declaration.', self::ERROR_SYNTAX ); return false; } } if ( '1.0' !== $this->get_qualified_attribute( 'version' ) ) { $this->bail( 'Unsupported XML version declared', self::ERROR_UNSUPPORTED ); return false; } /** * Standalone XML documents have no external dependencies, * including predefined entities like ` ` and `©`. * * See https://www.w3.org/TR/xml/#sec-predefined-ent. */ if ( null !== $this->get_qualified_attribute( 'encoding' ) && 'UTF-8' !== strtoupper( $this->get_qualified_attribute( 'encoding' ) ) ) { $this->bail( 'Unsupported XML encoding declared, only UTF-8 is supported.', self::ERROR_UNSUPPORTED ); return false; } if ( null !== $this->get_qualified_attribute( 'standalone' ) && 'YES' !== strtoupper( $this->get_qualified_attribute( 'standalone' ) ) ) { $this->bail( 'Standalone XML documents are not supported.', self::ERROR_UNSUPPORTED ); return false; } $at = $this->bytes_already_parsed; // Skip whitespace. $at += strspn( $this->xml, " \t\f\r\n", $at ); // Consume the closer. if ( ! ( $at + 2 <= $doc_length && '?' === $xml[ $at ] && '>' === $xml[ $at + 1 ] ) ) { $this->bail( 'XML declaration closer not found.', self::ERROR_SYNTAX ); return false; } $this->token_length = $at + 2 - $this->token_starts_at; $this->text_starts_at = $this->token_starts_at + 2; $this->text_length = $at - $this->text_starts_at; $this->bytes_already_parsed = $at + 2; $this->parser_state = self::STATE_XML_DECLARATION; // Processing instructions don't have namespaces. We can just // copy the qualified attributes to the attributes array without // resolving anything. $this->attributes = $this->qualified_attributes; $this->qualified_attributes = array(); return true; } /* * `<?` denotes a processing instruction. * See https://www.w3.org/TR/xml/#sec-pi */ if ( ! $this->is_closing_tag && '?' === $xml[ $at + 1 ] ) { if ( $at + 4 >= $doc_length ) { $this->mark_incomplete_input(); return false; } if ( ! ( ( 'x' === $xml[ $at + 2 ] || 'X' === $xml[ $at + 2 ] ) && ( 'm' === $xml[ $at + 3 ] || 'M' === $xml[ $at + 3 ] ) && ( 'l' === $xml[ $at + 4 ] || 'L' === $xml[ $at + 4 ] ) ) ) { $this->bail( 'Invalid processing instruction target.', self::ERROR_SYNTAX ); } $at += 5; // Skip whitespace. $this->skip_whitespace(); /* * Find the closer. * * We could, at this point, only consume the bytes allowed by the specification, that is: * * [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] // any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. * * However, that would require running a slow regular-expression engine for, seemingly, * little benefit. For now, we are going to pretend that all bytes are allowed until the * closing ?> is found. Some failures may pass unnoticed. That may not be a problem in practice, * but if it is then this code path will require a stricter implementation. */ $closer_at = strpos( $xml, '?>', $at ); if ( false === $closer_at ) { $this->mark_incomplete_input(); return false; } $this->parser_state = self::STATE_PI_NODE; $this->token_length = $closer_at + 5 - $this->token_starts_at; $this->text_starts_at = $this->token_starts_at + 5; $this->text_length = $closer_at - $this->text_starts_at; $this->bytes_already_parsed = $closer_at + 2; return true; } ++$at; } // There's no more tag openers and we're not expecting more data – // this mist be a trailing text node. if ( ! $this->expecting_more_input ) { $this->parser_state = self::STATE_TEXT_NODE; $this->token_starts_at = $was_at; $this->token_length = $doc_length - $was_at; $this->text_starts_at = $was_at; $this->text_length = $doc_length - $was_at; $this->bytes_already_parsed = $doc_length; return true; } /* * This does not imply an incomplete parse; it indicates that there * can be nothing left in the document other than a #text node. */ $this->mark_incomplete_input(); $this->token_starts_at = $was_at; $this->token_length = $doc_length - $was_at; $this->text_starts_at = $was_at; $this->text_length = $doc_length - $was_at; return false; } /** * Parses the next attribute. * * @return bool Whether an attribute was found before the end of the document. * @since WP_VERSION */ private function parse_next_attribute() { // Skip whitespace and slashes. $this->bytes_already_parsed += strspn( $this->xml, " \t\f\r\n/", $this->bytes_already_parsed ); if ( $this->bytes_already_parsed >= strlen( $this->xml ) ) { $this->mark_incomplete_input(); return false; } // No more attributes to parse. if ( '>' === $this->xml[ $this->bytes_already_parsed ] ) { return false; } $attribute_start = $this->bytes_already_parsed; $attribute_qname_length = $this->parse_name( $this->bytes_already_parsed ); if ( 0 === $attribute_qname_length ) { $this->bail( 'Invalid attribute name encountered.', self::ERROR_SYNTAX ); } $this->bytes_already_parsed += $attribute_qname_length; $attribute_qname = substr( $this->xml, $attribute_start, $attribute_qname_length ); $this->skip_whitespace(); // Parse attribute value. ++$this->bytes_already_parsed; $this->skip_whitespace(); if ( $this->bytes_already_parsed >= strlen( $this->xml ) ) { $this->mark_incomplete_input(); return false; } switch ( $this->xml[ $this->bytes_already_parsed ] ) { case "'": case '"': $quote = $this->xml[ $this->bytes_already_parsed ]; $value_start = $this->bytes_already_parsed + 1; /** * XML attributes cannot contain the characters "<" or "&". * * This only checks for "<" because it's reasonably fast. * Ampersands are actually allowed when used as the start * of an entity reference, but enforcing that would require * an expensive and complex check. It doesn't seem to be * worth it. * * @TODO: Discuss enforcing or abandoning the ampersand rule * and document the rationale. */ $value_length = strcspn( $this->xml, "<$quote", $value_start ); $attribute_end = $value_start + $value_length + 1; if ( $attribute_end - 1 >= strlen( $this->xml ) ) { $this->mark_incomplete_input(); return false; } if ( $this->xml[ $attribute_end - 1 ] !== $quote ) { $this->bail( 'A disallowed character encountered in an attribute value (either < or &).', self::ERROR_SYNTAX ); } $this->bytes_already_parsed = $attribute_end; break; default: $this->bail( 'Unquoted attribute value encountered.', self::ERROR_SYNTAX ); } if ( $attribute_end >= strlen( $this->xml ) ) { $this->mark_incomplete_input(); return false; } if ( $this->is_closing_tag ) { return true; } if ( array_key_exists( $attribute_qname, $this->qualified_attributes ) ) { $this->bail( 'Duplicate attribute found in an XML tag.', self::ERROR_SYNTAX ); } /** * Confirm the tag name is valid with respect to XML namespaces. * * @see https://www.w3.org/TR/2006/REC-xml-names11-20060816/#Conformance */ if ( false === $this->validate_qualified_name( $attribute_qname ) ) { return false; } /** * We must compute the namespace prefix and local name for each attribute * to assert there are no duplicate (local name, namespace) pairs in any * element. Note we must still keep track of string indices to support * replacements. */ list( $namespace_prefix, $local_name ) = $this->parse_qualified_name( $attribute_qname ); $this->qualified_attributes[ $attribute_qname ] = new XMLAttributeToken( $value_start, $value_length, $attribute_start, $attribute_end - $attribute_start, $namespace_prefix, $local_name /** * The full namespace is resolved in parse_next_token() once * all the attributes have been consumed. */ ); return true; } private function parse_quoted_string( $at = null ) { if ( null === $at ) { $at = $this->bytes_already_parsed; } $quote = $this->xml[ $at ]; if ( "'" !== $quote && '"' !== $quote ) { $this->bail( 'Invalid quote character encountered in an attribute value.', self::ERROR_SYNTAX ); } $value_length = strcspn( $this->xml, $quote, $at + 1 ); if ( $at + $value_length + 1 >= strlen( $this->xml ) ) { $this->mark_incomplete_input(); return false; } if ( $this->xml[ $at + $value_length + 1 ] !== $quote ) { $this->bail( 'A disallowed character encountered in an attribute value (either < or &).', self::ERROR_SYNTAX ); } return $value_length + 2; } /** * Move the internal cursor past any immediate successive whitespace. * * @since WP_VERSION */ private function skip_whitespace() { $this->bytes_already_parsed += strspn( $this->xml, " \t\f\r\n", $this->bytes_already_parsed ); } /** * Parses a Name token starting at $offset * * Name ::= NameStartChar (NameChar)* * * @param int $offset * * @return int */ private function parse_name( $offset ) { $name_byte_length = 0; $at = $offset; // Fast path: consume any ASCII NameStartChar bytes. $name_byte_length += strspn( $this->xml, ':ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz', $offset + $name_byte_length, 1 ); while ( true ) { /** * Parse the next unicode codepoint. * * We use a the `_wp_scan_utf8` UTF-8 decoder introduced in WordPress 6.9. No other method * is reliable and available enough to depend on it in WordPress core: * * – mb_ord() – is available on 99.5%+ or more of hosts, but not on all hosts. * – iconv_substr() – is not available on all hosts. * – preg_match() – can fail with PREG_BAD_UTF8_ERROR when the input * contains an incomplete UTF-8 byte sequence – even * when that sequence comes after a valid match. This * failure mode cannot be reproduced with just any string. * The runtime must be in a specific state. It's unclear * how to reliably reproduce this failure mode in a * unit test. * * Performance-wise, character-by-character processing via _wp_scan_utf8 * is pretty slow. The ASCII fast path below enables skipping most of the * UTF-8 decoder calls. * * If the UTF-8 decoder performance ever becomes a bottleneck, there are a * few ways to significantly improve it: * * – Call a native grapheme_ function when available. * – Introduce a custom UTF-8 decoder optimized for codepoint-by-codepoint processing. * It could be the streaming version of the UTF-8 decoder, such as `_wp_iterate_utf8`, * that avoids the repeated strspn() calls. Alternatively, the older `utf8_codepoint_at` * function could be restored if its codepoint-by-codepoint decoding performance is * better than the _wp_scan_utf8. */ /** * The ASCII speedup includes all ASCII NameStartChar, which are also valid * NameChar, making it possible to quickly scan past these bytes without * further processing. */ $name_byte_length += strspn( $this->xml, ":ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-.0123456789\u{B7}", $offset + $name_byte_length ); /* * Quickly check if the next byte is an ASCII byte that is not allowed in XML * NameStartChar. If so, we can break out of the loop without calling the UTF-8 decoder. * * Even though this does not seem to be different from the ASCII fast path in the * _wp_scan_utf8 function, skipping that function call still provides a ~50% speed * improvement. */ $is_non_name_ascii_byte = strspn( $this->xml, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" . "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" . " !\"#$%&'()*+,./;<=>?@[\\]^`{|}~\x7f", $offset + $name_byte_length, 1 ) > 0; if ( $is_non_name_ascii_byte ) { break; } // EOF. if ( $offset + $name_byte_length >= strlen( $this->xml ) ) { break; } // The next byte sequence is, very likely, a UTF-8 codepoint. Let's // try to decode it. $at = $offset + $name_byte_length; $new_at = $at; $invalid_length = 0; if ( 1 !== _wp_scan_utf8( $this->xml, $new_at, $invalid_length, null, 1 ) ) { // EOF or invalid utf-8 byte sequence. break; } $codepoint_byte_length = $new_at - $at; $codepoint = utf8_ord( substr( $this->xml, $at, $codepoint_byte_length ) ); // The codepoint is not a valid part of an XML NameChar or NameStartChar. if ( ! $this->is_valid_name_codepoint( $codepoint, 0 === $name_byte_length ) ) { break; } $name_byte_length += $codepoint_byte_length; $at = $new_at; } return $name_byte_length; } private function is_valid_name_codepoint( $codepoint, $is_first_character = false ) { // Test against the NameStartChar pattern: // NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] // See `https://www.w3.org/TR/xml/#NT-Name`. if ( // :. ( 0x3A <= $codepoint && $codepoint <= 0x3A ) || // _. ( 0x5F <= $codepoint && $codepoint <= 0x5F ) || // A-Z. ( 0x41 <= $codepoint && $codepoint <= 0x5A ) || // a-z. ( 0x61 <= $codepoint && $codepoint <= 0x7A ) || // [#xC0-#xD6]. ( 0xC0 <= $codepoint && $codepoint <= 0xD6 ) || // [#xD8-#xF6]. ( 0xD8 <= $codepoint && $codepoint <= 0xF6 ) || // [#xF8-#x2FF]. ( 0xF8 <= $codepoint && $codepoint <= 0x2FF ) || // [#x370-#x37D]. ( 0x370 <= $codepoint && $codepoint <= 0x37D ) || // [#x37F-#x1FFF]. ( 0x37F <= $codepoint && $codepoint <= 0x1FFF ) || // [#x200C-#x200D]. ( 0x200C <= $codepoint && $codepoint <= 0x200D ) || // [#x2070-#x218F]. ( 0x2070 <= $codepoint && $codepoint <= 0x218F ) || // [#x2C00-#x2FEF]. ( 0x2C00 <= $codepoint && $codepoint <= 0x2FEF ) || // [#x3001-#xD7FF]. ( 0x3001 <= $codepoint && $codepoint <= 0xD7FF ) || // [#xF900-#xFDCF]. ( 0xF900 <= $codepoint && $codepoint <= 0xFDCF ) || // [#xFDF0-#xFFFD]. ( 0xFDF0 <= $codepoint && $codepoint <= 0xFFFD ) || // [#x10000-#xEFFFF]. ( 0x10000 <= $codepoint && $codepoint <= 0xEFFFF ) ) { return true; } if ( $is_first_character ) { return false; } // Test against the NameChar pattern: // NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] // See `https://www.w3.org/TR/xml/#NT-Name`. return ( // "-". 45 === $codepoint || // ".". 46 === $codepoint || // [0-9]. ( 48 <= $codepoint && 57 >= $codepoint ) || // #xB7. 183 === $codepoint || // [#x0300-#x036F]. ( 0x0300 <= $codepoint && $codepoint <= 0x036F ) || // [#x203F-#x2040]. ( 0x203F <= $codepoint && $codepoint <= 0x2040 ) ); } /** * Applies attribute updates and cleans up once a tag is fully parsed. * * @since WP_VERSION */ private function after_tag() { /* * Purge updates if there are too many. The actual count isn't * scientific, but a few values from 100 to a few thousand were * tests to find a practically-useful limit. * * If the update queue grows too big, then the Tag Processor * will spend more time iterating through them and lose the * efficiency gains of deferring applying them. */ if ( 1000 < count( $this->lexical_updates ) ) { $this->get_updated_xml(); } foreach ( $this->lexical_updates as $name => $update ) { /* * Any updates appearing after the cursor should be applied * before proceeding, otherwise they may be overlooked. */ if ( $update->start >= $this->bytes_already_parsed ) { $this->get_updated_xml(); break; } if ( is_int( $name ) ) { continue; } $this->lexical_updates[] = $update; unset( $this->lexical_updates[ $name ] ); } $this->element = null; $this->token_starts_at = null; $this->token_length = null; $this->tag_name_starts_at = null; $this->tag_name_length = null; $this->text_starts_at = null; $this->text_length = null; $this->is_closing_tag = null; $this->pubid_literal = null; $this->system_literal = null; $this->attributes = array(); $this->qualified_attributes = array(); } /** * Applies lexical updates to XML document. * * @param int $shift_this_point Accumulate and return shift for this position. * * @return int How many bytes the given pointer moved in response to the updates. * @since WP_VERSION */ private function apply_lexical_updates( $shift_this_point = 0 ) { if ( ! count( $this->lexical_updates ) ) { return 0; } $accumulated_shift_for_given_point = 0; /* * Attribute updates can be enqueued in any order but updates * to the document must occur in lexical order; that is, each * replacement must be made before all others which follow it * at later string indices in the input document. * * Sorting avoid making out-of-order replacements which * can lead to mangled output, partially-duplicated * attributes, and overwritten attributes. */ usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) ); $bytes_already_copied = 0; $output_buffer = ''; foreach ( $this->lexical_updates as $diff ) { $shift = strlen( $diff->text ) - $diff->length; // Adjust the cursor position by however much an update affects it. if ( $diff->start < $this->bytes_already_parsed ) { $this->bytes_already_parsed += $shift; } // Accumulate shift of the given pointer within this function call. if ( $diff->start <= $shift_this_point ) { $accumulated_shift_for_given_point += $shift; } $output_buffer .= substr( $this->xml, $bytes_already_copied, $diff->start - $bytes_already_copied ); $output_buffer .= $diff->text; $bytes_already_copied = $diff->start + $diff->length; } $this->xml = $output_buffer . substr( $this->xml, $bytes_already_copied ); /* * Adjust bookmark locations to account for how the text * replacements adjust offsets in the input document. */ foreach ( $this->bookmarks as $bookmark_name => $bookmark ) { $bookmark_end = $bookmark->start + $bookmark->length; /* * Each lexical update which appears before the bookmark's endpoints * might shift the offsets for those endpoints. Loop through each change * and accumulate the total shift for each bookmark, then apply that * shift after tallying the full delta. */ $head_delta = 0; $tail_delta = 0; foreach ( $this->lexical_updates as $diff ) { $diff_end = $diff->start + $diff->length; if ( $bookmark->start < $diff->start && $bookmark_end < $diff->start ) { break; } if ( $bookmark->start >= $diff->start && $bookmark_end < $diff_end ) { $this->release_bookmark( $bookmark_name ); continue 2; } $delta = strlen( $diff->text ) - $diff->length; if ( $bookmark->start >= $diff->start ) { $head_delta += $delta; } if ( $bookmark_end >= $diff_end ) { $tail_delta += $delta; } } $bookmark->start += $head_delta; $bookmark->length += $tail_delta - $head_delta; } $this->lexical_updates = array(); return $accumulated_shift_for_given_point; } /** * Checks whether a bookmark with the given name exists. * * @param string $bookmark_name Name to identify a bookmark that potentially exists. * * @return bool Whether that bookmark exists. * @since WP_VERSION */ public function has_bookmark( $bookmark_name ) { return array_key_exists( $bookmark_name, $this->bookmarks ); } /** * Move the internal cursor in the Tag Processor to a given bookmark's location. * * Be careful! Seeking backwards to a previous location resets the parser to the * start of the document and reparses the entire contents up until it finds the * sought-after bookmarked location. * * In order to prevent accidental infinite loops, there's a * maximum limit on the number of times seek() can be called. * * @param string $bookmark_name Jump to the place in the document identified by this bookmark name. * * @return bool Whether the internal cursor was successfully moved to the bookmark's location. * @since WP_VERSION */ public function seek( $bookmark_name ) { if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) { _doing_it_wrong( __METHOD__, __( 'Unknown bookmark name.' ), 'WP_VERSION' ); return false; } if ( ++$this->seek_count > static::MAX_SEEK_OPS ) { _doing_it_wrong( __METHOD__, __( 'Too many calls to seek() - this can lead to performance issues.' ), 'WP_VERSION' ); return false; } // Flush out any pending updates to the document. $this->get_updated_xml(); // Point this tag processor before the sought tag opener and consume it. $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; $this->parser_state = self::STATE_READY; return $this->parse_next_token(); } /** * Compare two WP_HTML_Text_Replacement objects. * * @param WP_HTML_Text_Replacement $a First attribute update. * @param WP_HTML_Text_Replacement $b Second attribute update. * * @return int Comparison value for string order. * @since WP_VERSION */ private static function sort_start_ascending( $a, $b ) { $by_start = $a->start - $b->start; if ( 0 !== $by_start ) { return $by_start; } $by_text = isset( $a->text, $b->text ) ? strcmp( $a->text, $b->text ) : 0; if ( 0 !== $by_text ) { return $by_text; } /* * This code should be unreachable, because it implies the two replacements * start at the same location and contain the same text. */ return $a->length - $b->length; } /** * Return the enqueued value for a given attribute, if one exists. * * Enqueued updates can take different data types: * - If an update is enqueued and is boolean, the return will be `true` * - If an update is otherwise enqueued, the return will be the string value of that update. * - If an attribute is enqueued to be removed, the return will be `null` to indicate that. * - If no updates are enqueued, the return will be `false` to differentiate from "removed." * * @param string $comparable_name The attribute name in its comparable form. * * @return string|boolean|null Value of enqueued update if present, otherwise false. * @since WP_VERSION */ private function get_enqueued_attribute_value( $comparable_name ) { if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { return false; } if ( ! isset( $this->lexical_updates[ $comparable_name ] ) ) { return false; } $enqueued_text = $this->lexical_updates[ $comparable_name ]->text; // Removed attributes erase the entire span. if ( '' === $enqueued_text ) { return null; } /* * Boolean attribute updates are just the attribute name without a corresponding value. * * This value might differ from the given comparable name in that there could be leading * or trailing whitespace, and that the casing follows the name given in `set_attribute`. * * Example: * * $p->set_attribute( 'data-TEST-id', 'update' ); * 'update' === $p->get_enqueued_attribute_value( 'data-test-id' ); * * Detect this difference based on the absence of the `=`, which _must_ exist in any * attribute containing a value, e.g. `<input type="text" enabled />`. * ¹ ² * 1. Attribute with a string value. * 2. Boolean attribute whose value is `true`. */ $equals_at = strpos( $enqueued_text, '=' ); if ( false === $equals_at ) { return true; } /* * Finally, a normal update's value will appear after the `=` and * be double-quoted, as performed incidentally by `set_attribute`. * * e.g. `type="text"` * ¹² ³ * 1. Equals is here. * 2. Double-quoting starts one after the equals sign. * 3. Double-quoting ends at the last character in the update. */ $enqueued_value = substr( $enqueued_text, $equals_at + 2, - 1 ); /* * We're deliberately not decoding entities in attribute values: * * Attribute values must not contain direct or indirect entity references to external entities. * * See https://www.w3.org/TR/xml/#sec-starttags. */ return $enqueued_value; } /** * Returns the value of a requested attribute from a matched tag opener if that attribute exists. * * Example: * * $p = new XMLProcessor( * '<root xmlns:wp="http://www.w3.org/1999/xhtml"> * <content enabled="true" class="test" data-test-id="14" wp:enabled="true">Test</content> * </root>' * ); * $p->next_tag( array( 'class_name' => 'test' ) ) === true; * $p->get_attribute( '', 'data-test-id' ) === '14'; * $p->get_attribute( '', 'enabled' ) === "true"; * $p->get_attribute( 'wp', 'enabled' ) === null; * $p->get_attribute( 'http://www.w3.org/1999/xhtml', 'enabled' ) === "true"; * $p->get_attribute( 'aria-label' ) === null; * * @param string $namespace_reference Full namespace of the requested attribute, e.g. "http://wordpress.org/export/1.2/" * @param string $local_name Name of attribute whose value is requested, e.g. data-test-id * * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`. * @since WP_VERSION */ public function get_attribute( $namespace_reference, $local_name ) { if ( self::STATE_MATCHED_TAG !== $this->parser_state && self::STATE_XML_DECLARATION !== $this->parser_state ) { return null; } $full_name = $namespace_reference ? '{' . $namespace_reference . '}' . $local_name : $local_name; // Return any enqueued attribute value updates if they exist. $enqueued_value = $this->get_enqueued_attribute_value( $full_name ); if ( false !== $enqueued_value ) { return $enqueued_value; } if ( ! isset( $this->attributes[ $full_name ] ) ) { return null; } $attribute = $this->attributes[ $full_name ]; $raw_value = substr( $this->xml, $attribute->value_starts_at, $attribute->value_length ); $decoded = XMLDecoder::decode( $raw_value ); if ( ! isset( $decoded ) ) { /** * If the attribute contained an invalid value, it's * a fatal error. * * @see WP_XML_Decoder::decode() */ $this->last_error = self::ERROR_SYNTAX; _doing_it_wrong( __METHOD__, __( 'Invalid attribute value encountered.' ), 'WP_VERSION' ); return false; } return $decoded; } /** * Gets a value from a qualified attribute name if it exists in the * matched tag. * * It's for internal use only to source values of raw attributes * after they're parsed but before the namespaces are resolved. * * @param string $qname The qualified attribute name. * @return string|null The attribute value, or null if not found. */ private function get_qualified_attribute( $qname ) { if ( ! isset( $this->qualified_attributes[ $qname ] ) ) { return null; } $attribute = $this->qualified_attributes[ $qname ]; $raw_value = substr( $this->xml, $attribute->value_starts_at, $attribute->value_length ); $decoded = XMLDecoder::decode( $raw_value ); if ( ! isset( $decoded ) ) { /** * If the attribute contained an invalid value, it's * a fatal error. * * @see WP_XML_Decoder::decode() */ $this->last_error = self::ERROR_SYNTAX; _doing_it_wrong( __METHOD__, __( 'Invalid attribute value encountered.' ), 'WP_VERSION' ); return false; } return $decoded; } /** * Gets names of all attributes matching a given namespace prefix and local name prefix in the current tag. * * This method allows you to filter attributes by both their namespace prefix (e.g., 'wp') and the start of their local name (e.g., 'data-'). * Matching is case-sensitive for both namespace and local name prefixes, in accordance with the XML specification. * * Each returned attribute is represented as a two-element array: [namespace_prefix, local_name]. * * Examples: * * // No namespace, local name prefix only * $p = new XMLProcessor( '<content data-ENABLED="1" class="test" DATA-test-id="14">Test</content>' ); * $p->next_tag( array( 'class_name' => 'test' ) ) === true; * $p->get_attribute_names_with_prefix( '', 'data-' ); * // Returns: array( array( '', 'data-ENABLED' ), array( '', 'DATA-test-id' ) ) * * // With namespace prefix * $p = new XMLProcessor( '<content xmlns:wp="http://wordpress.org/export/1.2/" wp:data-foo="bar" wp:data-bar="baz" data-no-namespace="true" />' ); * $p->next_tag(); * $p->get_attribute_names_with_prefix( 'http://wordpress', 'data-' ); * // Returns: array( array( 'http://wordpress.org/export/1.2/', 'data-foo' ), array( 'http://wordpress.org/export/1.2/', 'data-bar' ) ) * * // Empty string namespace prefix matches all attributes. * $p->get_attribute_names_with_prefix( '', 'data-' ); * // Returns: array( array( 'http://wordpress.org/export/1.2/', 'data-foo' ), array( 'http://wordpress.org/export/1.2/', 'data-bar' ), array( '', 'data-no-namespace' ) ) * * // Null namespace prefix matches attributes with no namespace. * $p->get_attribute_names_with_prefix( null, 'data-' ); * // Returns: array( array( '', 'data-no-namespace' ) ) * * // No match for wrong namespace prefix * $p->get_attribute_names_with_prefix( 'other', 'data-' ); * // Returns: array() * * @param string $full_namespace_prefix Prefix of the fully qualified namespace to match on (e.g., 'http://wordpress.org/'). Use '' for no namespace prefix. * @param string $local_name_prefix Local name prefix to match (e.g., 'data-'). * * @return array|null List of [namespace, local_name] pairs, or `null` when no tag opener is matched. * @since WP_VERSION */ public function get_attribute_names_with_prefix( $full_namespace_prefix, $local_name_prefix ) { if ( self::STATE_MATCHED_TAG !== $this->parser_state || $this->is_closing_tag ) { return null; } $matches = array(); foreach ( $this->attributes as $attr ) { if ( 0 === strncmp( $attr->local_name, $local_name_prefix, strlen( $local_name_prefix ) ) && ( // Distinguish between no namespace and empty namespace. ( null === $full_namespace_prefix && '' === $attr->namespace ) || ( null !== $full_namespace_prefix && 0 === strncmp( $attr->namespace, $full_namespace_prefix, strlen( $full_namespace_prefix ) ) ) ) ) { $matches[] = array( $attr->namespace, $attr->local_name ); } } return $matches; } /** * Returns the local name of the matched tag. * * Example without namespaces: * * $p = new XMLProcessor( '<content class="test">Test</content>' ); * $p->next_tag() === true; * $p->get_tag_local_name() === 'content'; * * Example with namespaces: * * $p = new XMLProcessor( '<root xmlns:wp="http://www.w3.org/1999/xhtml"><wp:content>Test</wp:content></root>' ); * $p->next_tag() === true; * $p->get_tag_local_name() === 'content'; * * @return string|null Name of currently matched tag in input XML, or `null` if none found. * @since WP_VERSION */ public function get_tag_local_name() { if ( null !== $this->element ) { // Return cached name if we already have it. return $this->element->local_name; } $qualified_tag_name = $this->get_tag_name_qualified(); if ( null === $qualified_tag_name ) { return null; } list( $_, $local_name ) = $this->parse_qualified_name( $qualified_tag_name ); return $local_name; } /** * Returns the namespace prefix and the local name of the matched tag. * * Example without namespaces: * * $p = new XMLProcessor( '<content>Test</content>' ); * $p->next_tag() === true; * $p->get_tag_name_qualified() === 'content'; * * Example with namespaces: * * $p = new XMLProcessor( '<root xmlns:wp="http://www.w3.org/1999/xhtml"><wp:content>Test</wp:content></root>' ); * $p->next_tag() === true; * $p->get_tag_name_qualified() === 'wp:content'; * * @return string|null The namespace prefix and the local name of the matched tag, or null if not available. */ private function get_tag_name_qualified() { if ( null !== $this->element ) { // Return cached name if we already have it. return $this->element->qualified_name; } if ( null === $this->tag_name_starts_at ) { return null; } $tag_name = substr( $this->xml, $this->tag_name_starts_at, $this->tag_name_length ); if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { return null; } return $tag_name; } /** * Returns a string with the fully qualified namespace and local name of the matched tag * in the following format: "{namespace}local_name". * * Example: * * $p = new XMLProcessor( '<root xmlns:wp="http://www.w3.org/1999/xhtml"><wp:content>Test</wp:content></root>' ); * $p->next_tag() === true; * $p->get_tag_namespace_and_local_name() === '{http://www.w3.org/1999/xhtml"}content'; * * @return string|null The namespace and local name of the matched tag, or null if not available. */ public function get_tag_namespace_and_local_name() { $namespace = $this->get_tag_namespace(); if ( ! $namespace ) { return $this->get_tag_local_name(); } return '{' . $namespace . '}' . $this->get_tag_local_name(); } /** * Returns the namespace reference of the matched tag. * * Example: * * $p = new XMLProcessor( '<root xmlns:wp="http://www.w3.org/1999/xhtml"><wp:content>Test</wp:content></root>' ); * $p->next_tag() === true; * $p->next_tag() === true; * $p->get_tag_namespace() === 'http://www.w3.org/1999/xhtml'; * * @return string|null The namespace reference of the matched tag, or null if not available. */ public function get_tag_namespace() { if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { return null; } return $this->element->namespace; } /** * Returns the name from the DOCTYPE declaration. * * ``` * doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' * ^^^^ * ``` * * @return string|null The name from the DOCTYPE declaration, or null if not available. * @since WP_VERSION */ public function get_doctype_name() { if ( null === $this->doctype_name ) { return null; } return substr( $this->xml, $this->doctype_name->start, $this->doctype_name->length ); } /** * Returns the system literal value from the DOCTYPE declaration. * * Example: * * <!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> * * In this example, the system_literal would be: * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" * * @return string|null The system literal value, or null if not available. * @since WP_VERSION */ public function get_system_literal() { if ( null === $this->system_literal ) { return null; } return substr( $this->xml, $this->system_literal->start, $this->system_literal->length ); } /** * Returns the public identifier value from the DOCTYPE declaration. * * Example: * * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> * * In this example, the pubid_literal would be: * "-//W3C//DTD XHTML 1.0 Strict//EN" * * @return string|null The public identifier value, or null if not available. * @since WP_VERSION */ public function get_pubid_literal() { if ( null === $this->pubid_literal ) { return null; } return substr( $this->xml, $this->pubid_literal->start, $this->pubid_literal->length ); } /** * Indicates if the currently matched tag is expected to be closed. * Returns true for tag openers (<div>) and false for empty elements (<img />) and tag closers (</div>). * * This method exists to provide a consistent interface with WP_HTML_Processor. * * @return bool Whether the tag is expected to be closed. */ public function expects_closer() { if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { return false; } return $this->is_tag_opener() && ! $this->is_empty_element(); } /** * Indicates if the currently matched tag is an empty element tag. * * XML tags ending with a solidus ("/") are parsed as empty elements. They have no * content and no matching closer is expected. * * @return bool Whether the currently matched tag is an empty element tag. * @since WP_VERSION */ public function is_empty_element() { if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { return false; } /* * An empty element tag is defined by the solidus at the _end_ of the tag, not the beginning. * * Example: * * <figure /> * ^ this appears one character before the end of the closing ">". */ return '/' === $this->xml[ $this->token_starts_at + $this->token_length - 2 ]; } /** * Indicates if the current tag token is a tag closer. * * Example: * * $p = new XMLProcessor( '<content></content>' ); * $p->next_tag( array( 'tag_name' => 'content', 'tag_closers' => 'visit' ) ); * $p->is_tag_closer() === false; * * $p->next_tag( array( 'tag_name' => 'content', 'tag_closers' => 'visit' ) ); * $p->is_tag_closer() === true; * * @return bool Whether the current tag is a tag closer. * @since WP_VERSION */ public function is_tag_closer() { return ( self::STATE_MATCHED_TAG === $this->parser_state && $this->is_closing_tag ); } /** * Indicates if the current tag token is a tag opener. * * Example: * * $p = new XMLProcessor( '<content></content>' ); * $p->next_token(); * $p->is_tag_opener() === true; * * $p->next_token(); * $p->is_tag_opener() === false; * * @return bool Whether the current tag is a tag closer. * @since WP_VERSION */ public function is_tag_opener() { return ( self::STATE_MATCHED_TAG === $this->parser_state && ! $this->is_closing_tag && ! $this->is_empty_element() ); } /** * Indicates the kind of matched token, if any. * * This differs from `get_token_name()` in that it always * returns a static string indicating the type, whereas * `get_token_name()` may return values derived from the * token itself, such as a tag name or processing * instruction tag. * * Possible values: * - `#tag` when matched on a tag. * - `#text` when matched on a text node. * - `#cdata-section` when matched on a CDATA node. * - `#comment` when matched on a comment. * - `#presumptuous-tag` when matched on an empty tag closer. * * @return string|null What kind of token is matched, or null. * @since WP_VERSION */ public function get_token_type() { switch ( $this->parser_state ) { case self::STATE_MATCHED_TAG: return '#tag'; default: return $this->get_token_name(); } } /** * Returns the node name represented by the token. * * This matches the DOM API value `nodeName`. Some values * are static, such as `#text` for a text node, while others * are dynamically generated from the token itself. * * Dynamic names: * - Uppercase tag name for tag matches. * * Note that if the Tag Processor is not matched on a token * then this function will return `null`, either because it * hasn't yet found a token or because it reached the end * of the document without matching a token. * * @return string|null Name of the matched token. * @since WP_VERSION */ public function get_token_name() { switch ( $this->parser_state ) { case self::STATE_MATCHED_TAG: return $this->get_tag_local_name(); case self::STATE_TEXT_NODE: return '#text'; case self::STATE_CDATA_NODE: return '#cdata-section'; case self::STATE_DOCTYPE_NODE: return '#doctype'; case self::STATE_XML_DECLARATION: return '#xml-declaration'; case self::STATE_PI_NODE: return '#processing-instructions'; case self::STATE_COMMENT: return '#comment'; case self::STATE_COMPLETE: return '#complete'; case self::STATE_INVALID_DOCUMENT: return '#error'; default: return '#none'; } } /** * Returns the modifiable text for a matched token, or an empty string. * * Modifiable text is text content that may be read and changed without * changing the XML structure of the document around it. This includes * the contents of `#text` and `#cdata-section` nodes in the XML as well * as the inner contents of XML comments, Processing Instructions, and others. * * If a token has no modifiable text then an empty string is returned to * avoid needless crashing or type errors. An empty string does not mean * that a token has modifiable text, and a token with modifiable text may * have an empty string (e.g. a comment with no contents). * * @return string * @since WP_VERSION */ public function get_modifiable_text() { if ( null === $this->text_starts_at ) { return ''; } $text = substr( $this->xml, $this->text_starts_at, $this->text_length ); /* * > the XML processor must behave as if it normalized all line breaks in external parsed * > entities (including the document entity) on input, before parsing, by translating both * > the two-character sequence #xD #xA and any #xD that is not followed by #xA to a single * > #xA character. * * See https://www.w3.org/TR/xml/#sec-line-ends */ $text = str_replace( array( "\r\n", "\r" ), "\n", $text ); // Comment data and CDATA sections contents are not decoded any further. if ( self::STATE_CDATA_NODE === $this->parser_state || self::STATE_COMMENT === $this->parser_state ) { return $text; } $decoded = XMLDecoder::decode( $text ); if ( ! isset( $decoded ) ) { /** * If the attribute contained an invalid value, it's * a fatal error. * * @see WP_XML_Decoder::decode() */ $this->last_error = self::ERROR_SYNTAX; _doing_it_wrong( __METHOD__, __( 'Invalid text content encountered.' ), 'WP_VERSION' ); return false; } return $decoded; } /** * Updates the modifiable text for a matched token. * * Modifiable text is text content that may be read and changed without * changing the XML structure of the document around it. This includes * the contents of `#text` and `#cdata-section` nodes in the XML as well * as the inner contents of XML comments, Processing Instructions, and others. * * If a token has no modifiable text then false is returned to avoid needless * crashing or type errors. * * @param $new_value New modifiable text for the current node. * @return string */ public function set_modifiable_text( $new_value ) { switch ( $this->parser_state ) { case self::STATE_TEXT_NODE: case self::STATE_COMMENT: $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->text_starts_at, $this->text_length, // @TODO: Audit this in details. Is this too naive? Or is it actually safe? htmlspecialchars( $new_value, ENT_XML1, 'UTF-8' ) ); return true; case self::STATE_CDATA_NODE: $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->text_starts_at, $this->text_length, // @TODO: Audit this in details. Is this too naive? Or is it actually safe? str_replace( ']]>', ']]>', $new_value ) ); return true; default: _doing_it_wrong( __METHOD__, __( 'Cannot set text content on a non-text node.' ), 'WP_VERSION' ); return false; } } /** * Updates or creates a new attribute on the currently matched tag with the passed value. * * For boolean attributes special handling is provided: * - When `true` is passed as the value, then only the attribute name is added to the tag. * - When `false` is passed, the attribute gets removed if it existed before. * * For string attributes, the value is escaped using the `esc_attr` function. * * @param string $xml_namespace The attribute's namespace. * @param string $local_name The attribute name to target. * @param string|bool $value The new attribute value. * * @return bool Whether an attribute value was set. * @since WP_VERSION */ public function set_attribute( $xml_namespace, $local_name, $value ) { if ( ! is_string( $value ) ) { _doing_it_wrong( __METHOD__, __( 'Non-string attribute values cannot be passed to set_attribute().' ), 'WP_VERSION' ); return false; } if ( 'xmlns' === $xml_namespace ) { $this->bail( __( 'Setting attributes in the xmlns namespace is not yet supported by set_attribute().' ), $xml_namespace ); return false; } if ( self::STATE_MATCHED_TAG !== $this->parser_state || $this->is_closing_tag ) { return false; } $value = htmlspecialchars( $value, ENT_XML1, 'UTF-8' ); if ( '' !== $xml_namespace ) { $prefix = $this->get_tag_namespace_prefix( $xml_namespace ); if ( false === $prefix ) { $this->bail( // Translators: 1: The XML namespace. __( 'The namespace "%1$s" is not in the current element\'s scope.' ), $xml_namespace ); return false; } $name = $prefix . ':' . $local_name; } else { $name = $local_name; } $updated_attribute = "{$name}=\"{$value}\""; /* * > An attribute name must not appear more than once * > in the same start-tag or empty-element tag. * - XML 1.0 spec * * @see https://www.w3.org/TR/xml/#sec-starttags */ if ( isset( $this->attributes[ $name ] ) ) { /* * Update an existing attribute. * * Example – set attribute id to "new" in <content id="initial_id" />: * * <content id="initial_id"/> * ^-------------^ * start end * replacement: `id="new"` * * Result: <content id="new"/> */ $existing_attribute = $this->attributes[ $name ]; $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement( $existing_attribute->start, $existing_attribute->length, $updated_attribute ); } else { /* * Create a new attribute at the tag's name end. * * Example – add attribute id="new" to <content />: * * <content /> * ^ * start and end * replacement: ` id="new"` * * Result: <content id="new"/> */ $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement( $this->tag_name_starts_at + $this->tag_name_length, 0, ' ' . $updated_attribute ); } return true; } /** * Remove an attribute from the currently-matched tag. * * @param string $xml_namespace The attribute's namespace. * @param string $local_name The attribute name to remove. * * @return bool Whether an attribute was removed. * @since WP_VERSION */ public function remove_attribute( $xml_namespace, $local_name ) { if ( self::STATE_MATCHED_TAG !== $this->parser_state || $this->is_closing_tag ) { return false; } $name = $xml_namespace ? '{' . $xml_namespace . '}' . $local_name : $local_name; /* * If updating an attribute that didn't exist in the input * document, then remove the enqueued update and move on. * * For example, this might occur when calling `remove_attribute()` * after calling `set_attribute()` for the same attribute * and when that attribute wasn't originally present. */ if ( ! isset( $this->attributes[ $name ] ) ) { if ( isset( $this->lexical_updates[ $name ] ) ) { unset( $this->lexical_updates[ $name ] ); } return false; } /* * Removes an existing tag attribute. * * Example – remove the attribute id from <content id="main"/>: * <content id="initial_id"/> * ^-------------^ * start end * replacement: `` * * Result: <content /> */ $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement( $this->attributes[ $name ]->start, $this->attributes[ $name ]->length, '' ); return true; } /** * Returns the string representation of the XML Tag Processor. * * @return string The processed XML. * @see XMLProcessor::get_updated_xml() * * @since WP_VERSION */ public function __toString() { return $this->get_updated_xml(); } /** * Returns the string representation of the XML Tag Processor. * * @return string The processed XML. * @since WP_VERSION */ public function get_updated_xml() { $requires_no_updating = 0 === count( $this->lexical_updates ); /* * When there is nothing more to update and nothing has already been * updated, return the original document and avoid a string copy. */ if ( $requires_no_updating ) { return $this->xml; } /* * Keep track of the position right before the current token. This will * be necessary for reparsing the current token after updating the XML. */ $before_current_token = isset( $this->token_starts_at ) ? $this->token_starts_at : 0; /* * 1. Apply the enqueued edits and update all the pointers to reflect those changes. */ $before_current_token += $this->apply_lexical_updates( $before_current_token ); /* * 2. Rewind to before the current tag and reparse to get updated attributes. * * At this point the internal cursor points to the end of the tag name. * Rewind before the tag name starts so that it's as if the cursor didn't * move; a call to `next_tag()` will reparse the recently-updated attributes * and additional calls to modify the attributes will apply at this same * location, but in order to avoid issues with subclasses that might add * behaviors to `next_tag()`, the internal methods should be called here * instead. * * It's important to note that in this specific place there will be no change * because the processor was already at a tag when this was called and it's * rewinding only to the beginning of this very tag before reprocessing it * and its attributes. * * <p>Previous XML<em>More XML</em></p> * ↑ │ back up by the length of the tag name plus the opening < * └←─┘ back up by strlen("em") + 1 ==> 3 */ $this->bytes_already_parsed = $before_current_token; $this->parse_next_token(); return $this->xml; } /** * Finds the next token in the XML document. * * An XML document can be viewed as a stream of tokens, * where tokens are things like XML tags, XML comments, * text nodes, etc. This method finds the next token in * the XML document and returns whether it found one. * * If it starts parsing a token and reaches the end of the * document then it will seek to the start of the last * token and pause, returning `false` to indicate that it * failed to find a complete token. * * Possible token types, based on the XML specification: * * - an XML tag * - a text node - the plaintext inside tags. * - a CData section * - an XML comment. * - a DOCTYPE declaration. * - a processing instruction, e.g. `<?xml mode="WordPress" ?>`. * * @return bool Whether a token was parsed. */ public function next_token() { return $this->step(); } /** * Moves the internal cursor to the next token in the XML document * according to the XML specification. * * It considers the current XML context (prolog, element, or misc) * and only expects the nodes that are allowed in that context. * * @param int $node_to_process Whether to process the next node or * reprocess the current node, e.g. using another parser context. * * @return bool Whether a token was parsed. * @since WP_VERSION * * @access private */ private function step( $node_to_process = self::PROCESS_NEXT_NODE ) { // Refuse to proceed if there was a previous error. if ( null !== $this->last_error ) { return false; } // Finish stepping when there are no more tokens in the document. if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state || self::STATE_COMPLETE === $this->parser_state ) { return false; } if ( self::PROCESS_NEXT_NODE === $node_to_process ) { if ( $this->is_empty_element() ) { array_pop( $this->stack_of_open_elements ); } } try { switch ( $this->parser_context ) { case self::IN_PROLOG_CONTEXT: return $this->step_in_prolog( $node_to_process ); case self::IN_ELEMENT_CONTEXT: return $this->step_in_element( $node_to_process ); case self::IN_MISC_CONTEXT: return $this->step_in_misc( $node_to_process ); default: $this->last_error = self::ERROR_UNSUPPORTED; return false; } } catch ( XMLUnsupportedException $e ) { /* * Exceptions are used in this class to escape deep call stacks that * otherwise might involve messier calling and return conventions. */ return false; } } /** * Parses the next node in the 'prolog' part of the XML document. * * @return bool Whether a node was found. * @see https://www.w3.org/TR/xml/#NT-document. * @see XMLProcessor::step * * @since WP_VERSION */ private function step_in_prolog( $node_to_process = self::PROCESS_NEXT_NODE ) { if ( self::PROCESS_NEXT_NODE === $node_to_process ) { $has_next_node = $this->parse_next_token(); if ( false === $has_next_node && ! $this->expecting_more_input ) { $this->bail( 'The root element was not found.', self::ERROR_SYNTAX ); } } // XML requires a root element. If we've reached the end of data in the prolog stage, // before finding a root element, then the document is incomplete. if ( self::STATE_COMPLETE === $this->parser_state ) { $this->mark_incomplete_input(); return false; } // Do not step if we paused due to an incomplete input. if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { return false; } switch ( $this->get_token_type() ) { case '#text': $text = $this->get_modifiable_text(); $whitespaces = strspn( $text, " \t\n\r" ); if ( strlen( $text ) !== $whitespaces ) { // @TODO: Only look for this in the 2 initial bytes of the document:. if ( "\xFF\xFE" === substr( $text, 0, 2 ) ) { $this->bail( 'Unexpected UTF-16 BOM byte sequence (0xFFFE) in the document. XMLProcessor only supports UTF-8.', self::ERROR_SYNTAX ); } $this->bail( 'Unexpected non-whitespace text token in prolog stage.', self::ERROR_SYNTAX ); } return $this->step(); // @TODO: Fail if there's more than one <!DOCTYPE> or if <!DOCTYPE> was found before the XML declaration token. case '#doctype': case '#comment': case '#xml-declaration': case '#processing-instructions': return true; case '#tag': $this->parser_context = self::IN_ELEMENT_CONTEXT; return $this->step( self::PROCESS_CURRENT_NODE ); default: $this->bail( 'Unexpected token type in prolog stage.', self::ERROR_SYNTAX ); } } /** * Parses the next node in the 'element' part of the XML document. * * @return bool Whether a node was found. * @see https://www.w3.org/TR/xml/#NT-document. * @see XMLProcessor::step * * @since WP_VERSION */ private function step_in_element( $node_to_process = self::PROCESS_NEXT_NODE ) { if ( self::PROCESS_NEXT_NODE === $node_to_process ) { $has_next_node = $this->parse_next_token(); if ( false === $has_next_node && ! $this->expecting_more_input ) { $this->bail( 'A tag was not closed.', self::ERROR_SYNTAX ); } } // Do not step if we paused due to an incomplete input. if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { return false; } switch ( $this->get_token_type() ) { case '#text': case '#cdata-section': case '#comment': case '#processing-instructions': return true; case '#tag': // Update the stack of open elements. $tag_qname = $this->get_tag_name_qualified(); if ( $this->is_tag_closer() ) { if ( ! count( $this->stack_of_open_elements ) ) { $this->bail( // Translators: 1: The closing tag name. 2: The opening tag name. __( 'The closing tag "%1$s" did not match the opening tag "%2$s".' ), $tag_qname, $tag_qname ); return false; } $this->element = array_pop( $this->stack_of_open_elements ); $popped_qname = $this->element->qualified_name; if ( $popped_qname !== $tag_qname ) { $this->bail( sprintf( // translators: %1$s is the name of the closing HTML tag, %2$s is the name of the opening HTML tag. __( 'The closing tag "%1$s" did not match the opening tag "%2$s".' ), $tag_qname, $popped_qname ), self::ERROR_SYNTAX ); } if ( 0 === count( $this->stack_of_open_elements ) ) { $this->parser_context = self::IN_MISC_CONTEXT; } } else { array_push( $this->stack_of_open_elements, $this->element ); $this->element = $this->top_element(); } return true; default: $this->bail( sprintf( // translators: %1$s is the unexpected token type. __( 'Unexpected token type "%1$s" in element stage.', 'data-liberation' ), $this->get_token_type() ), self::ERROR_SYNTAX ); } } /** * Parses the next node in the 'misc' part of the XML document. * * @return bool Whether a node was found. * @see https://www.w3.org/TR/xml/#NT-document. * @see XMLProcessor::step * * @since WP_VERSION */ private function step_in_misc( $node_to_process = self::PROCESS_NEXT_NODE ) { if ( self::PROCESS_NEXT_NODE === $node_to_process ) { $has_next_node = $this->parse_next_token(); if ( false === $has_next_node && ! $this->expecting_more_input ) { // Parsing is complete. $this->parser_state = self::STATE_COMPLETE; return true; } } // Do not step if we paused due to an incomplete input. if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { return false; } if ( self::STATE_COMPLETE === $this->parser_state ) { return true; } switch ( $this->get_token_type() ) { case '#comment': case '#processing-instructions': return true; case '#text': $text = $this->get_modifiable_text(); $whitespaces = strspn( $text, " \t\n\r" ); if ( strlen( $text ) !== $whitespaces ) { $this->bail( 'Unexpected token type "' . $this->get_token_type() . '" in misc stage.', self::ERROR_SYNTAX ); } return $this->step(); default: $this->bail( 'Unexpected token type "' . $this->get_token_type() . '" in misc stage.', self::ERROR_SYNTAX ); } } /** * Computes the XML breadcrumbs for the currently-matched element, if matched. * * Breadcrumbs start at the outermost parent and descend toward the matched element. * They always include the entire path from the root XML node to the matched element. * Example * * $processor = XMLProcessor::create_fragment( '<p><strong><em><img/></em></strong></p>' ); * $processor->next_tag( 'img' ); * $processor->get_breadcrumbs() === array( 'p', 'strong', 'em', 'img' ); * * @return string[]|null Array of tag names representing path to matched node, if matched, otherwise NULL. * @since WP_VERSION */ public function get_breadcrumbs() { return array_map( function ( $element ) { return array( $element->namespace, $element->local_name ); }, $this->stack_of_open_elements ); } /** * Indicates if the currently-matched tag matches the given breadcrumbs. * * A "*" represents a single tag wildcard, where any tag matches, but not no tags. * * At some point this function _may_ support a `**` syntax for matching any number * of unspecified tags in the breadcrumb stack. This has been intentionally left * out, however, to keep this function simple and to avoid introducing backtracking, * which could open up surprising performance breakdowns. * * Example: * * $processor = new XMLProcessor( '<root><post><content><image /></content></post></root>' ); * $processor->next_tag( 'img' ); * true === $processor->matches_breadcrumbs( array( 'content', 'image' ) ); * true === $processor->matches_breadcrumbs( array( 'post', 'content', 'image' ) ); * false === $processor->matches_breadcrumbs( array( 'post', 'image' ) ); * true === $processor->matches_breadcrumbs( array( 'post', '*', 'image' ) ); * * @param string[] $breadcrumbs DOM sub-path at which element is found, e.g. `array( 'content', 'image' )`. * May also contain the wildcard `*` which matches a single element, e.g. `array( 'post', '*' )`. * * @return bool Whether the currently-matched tag is found at the given nested structure. * @since WP_VERSION */ public function matches_breadcrumbs( $breadcrumbs ) { // Everything matches when there are zero constraints. if ( 0 === count( $breadcrumbs ) ) { return true; } // Start at the last crumb. $crumb = end( $breadcrumbs ); if ( '#tag' !== $this->get_token_type() ) { return false; } $open_elements = $this->stack_of_open_elements; $crumb_count = count( $breadcrumbs ); $elem_count = count( $open_elements ); // Walk backwards through both arrays, matching each crumb to the corresponding open element. for ( $j = 1; $j <= $crumb_count; $j++ ) { $crumb = $breadcrumbs[ $crumb_count - $j ]; $element = isset( $open_elements[ $elem_count - $j ] ) ? $open_elements[ $elem_count - $j ] : null; if ( ! $element ) { return false; } // Normalize crumb to [namespace, local_name]. if ( ! is_array( $crumb ) ) { if ( '*' === $crumb ) { $crumb = array( '*', '*' ); } else { $crumb = array( '*', $crumb ); } } list( $namespace, $local_name ) = $crumb; // Match local name, respecting wildcard '*'. if ( '*' !== $local_name && $local_name !== $element->local_name ) { return false; } // Match namespace, respecting wildcard '*'. if ( '*' !== $namespace && $namespace !== $element->namespace ) { return false; } } return true; } /** * Returns the nesting depth of the current location in the document. * * Example: * * $processor = new XMLProcessor( '<?xml version="1.0" ?><root><text></text></root>' ); * 0 === $processor->get_current_depth(); * * // Opening the root element increases the depth. * $processor->next_tag(); * 1 === $processor->get_current_depth(); * * // Opening the text element increases the depth. * $processor->next_tag(); * 2 === $processor->get_current_depth(); * * // The text element is closed during `next_token()` so the depth is decreased to reflect that. * $processor->next_token(); * 1 === $processor->get_current_depth(); * * @return int Nesting-depth of current location in the document. * @since WP_VERSION */ public function get_current_depth() { return count( $this->stack_of_open_elements ); } /** * Parses a qualified name into a namespace prefix and local name. * * Example: * * $this->parse_qualified_name( 'wp:post' ); // Returns array( 'wp.org', 'post' ) * $this->parse_qualified_name( 'image' ); // Returns array( '', 'image' ) * * @param string $qualified_name The qualified name to parse. * * @return array<string, string> The namespace prefix and local name. */ private function parse_qualified_name( $qualified_name ) { $namespace_prefix = ''; $local_name = $qualified_name; $prefix_length = strcspn( $qualified_name, ':' ); if ( null !== $prefix_length && strlen( $qualified_name ) !== $prefix_length ) { $namespace_prefix = substr( $qualified_name, 0, $prefix_length ); $local_name = substr( $qualified_name, $prefix_length + 1 ); } return array( $namespace_prefix, $local_name ); } /** * Asserts a qualified tag name is syntactically valid according to the * XML specification. * * @param string $qualified_name The qualified name to validate. * @return bool Whether the qualified name is syntactically valid. */ private function validate_qualified_name( $qualified_name ) { if ( substr_count( $qualified_name, ':' ) > 1 ) { $this->bail( sprintf( 'Invalid identifier "%s" – more than one ":" in tag name. Every tag name must contain either zero or one colon.', $qualified_name ), self::ERROR_SYNTAX ); return false; } $prefix_length = strcspn( $qualified_name, ':' ); if ( 0 === $prefix_length && strlen( $qualified_name ) > 0 ) { $this->bail( sprintf( 'Invalid identifier "%s" – namespace qualifier must not have zero length.', $qualified_name ), self::ERROR_SYNTAX ); return false; } return true; } private function mark_incomplete_input( $error_message = 'Unexpected syntax encountered.' ) { if ( $this->expecting_more_input ) { $this->parser_state = self::STATE_INCOMPLETE_INPUT; return; } $this->parser_state = self::STATE_INVALID_DOCUMENT; $this->last_error = self::ERROR_SYNTAX; _doing_it_wrong( __METHOD__, $error_message, 'WP_VERSION' ); } /** * Returns context for why the parser aborted due to unsupported XML, if it did. * * This is meant for debugging purposes, not for production use. * * @return XMLUnsupportedException|null */ public function get_exception() { return $this->exception; } /** * Stops the parser and terminates its execution when encountering unsupported markup. * * @param string $message Explains support is missing in order to parse the current node. * * @throws XMLUnsupportedException Halts execution of the parser. */ private function bail( $message, $reason = self::ERROR_UNSUPPORTED ) { $starts_at = isset( $this->token_starts_at ) ? $this->token_starts_at : strlen( $this->xml ); $length = isset( $this->token_length ) ? $this->token_length : 0; $token = substr( $this->xml, $starts_at, $length ); $this->last_error = $reason; $this->exception = new XMLUnsupportedException( $message, $this->get_token_type(), $starts_at, $token, $this->get_breadcrumbs() ); throw $this->exception; } /** * Parser Ready State. * * Indicates that the parser is ready to run and waiting for a state transition. * It may not have started yet, or it may have just finished parsing a token and * is ready to find the next one. * * @since WP_VERSION * * @access private */ const STATE_READY = 'STATE_READY'; /** * Parser Complete State. * * Indicates that the parser has reached the end of the document and there is * nothing left to scan. It finished parsing the last token completely. * * @since WP_VERSION * * @access private */ const STATE_COMPLETE = 'STATE_COMPLETE'; /** * Parser Incomplete Input State. * * Indicates that the parser has reached the end of the document before finishing * a token. It started parsing a token but there is a possibility that the input * XML document was truncated in the middle of a token. * * The parser is reset at the start of the incomplete token and has paused. There * is nothing more than can be scanned unless provided a more complete document. * * @since WP_VERSION * * @access private */ const STATE_INCOMPLETE_INPUT = 'STATE_INCOMPLETE_INPUT'; /** * Parser Invalid Input State. * * Indicates that the parsed xml document contains malformed input and cannot be parsed. * * @since WP_VERSION * * @access private */ const STATE_INVALID_DOCUMENT = 'STATE_INVALID_DOCUMENT'; /** * Parser Matched Tag State. * * Indicates that the parser has found an XML tag and it's possible to get * the tag name and read or modify its attributes (if it's not a closing tag). * * @since WP_VERSION * * @access private */ const STATE_MATCHED_TAG = 'STATE_MATCHED_TAG'; /** * Parser Text Node State. * * Indicates that the parser has found a text node and it's possible * to read and modify that text. * * @since WP_VERSION * * @access private */ const STATE_TEXT_NODE = 'STATE_TEXT_NODE'; /** * Parser CDATA Node State. * * Indicates that the parser has found a CDATA node and it's possible * to read and modify its modifiable text. Note that in XML there are * no CDATA nodes outside of foreign content (SVG and MathML). Outside * of foreign content, they are treated as XML comments. * * @since WP_VERSION * * @access private */ const STATE_CDATA_NODE = 'STATE_CDATA_NODE'; /** * Parser DOCTYPE Node State. * * Indicates that the parser has found a DOCTYPE declaration and it's possible * to read and modify its modifiable text. * * @since WP_VERSION * * @access private */ const STATE_DOCTYPE_NODE = 'STATE_DOCTYPE_NODE'; /** * Indicates that the parser has found an XML processing instruction. * * @since WP_VERSION * * @access private */ const STATE_PI_NODE = 'STATE_PI_NODE'; /** * Indicates that the parser has found an XML declaration * * @since WP_VERSION * * @access private */ const STATE_XML_DECLARATION = 'STATE_XML_DECLARATION'; /** * Indicates that the parser has found an XML comment and it's * possible to read and modify its modifiable text. * * @since WP_VERSION * * @access private */ const STATE_COMMENT = 'STATE_COMMENT'; /** * Indicates that the parser encountered unsupported syntax and has bailed. * * @since WP_VERSION * * @var string */ const ERROR_SYNTAX = 'syntax'; /** * Indicates that the provided XML document contains a declaration that is * unsupported by the parser. * * @since WP_VERSION * * @var string */ const ERROR_UNSUPPORTED = 'unsupported'; /** * Indicates that the parser encountered more XML tokens than it * was able to process and has bailed. * * @since WP_VERSION * * @var string */ const ERROR_EXCEEDED_MAX_BOOKMARKS = 'exceeded-max-bookmarks'; /** * Indicates that we're parsing the `prolog` part of the XML * document. * * @since WP_VERSION * * @access private */ const IN_PROLOG_CONTEXT = 'prolog'; /** * Indicates that we're parsing the `element` part of the XML * document. * * @since WP_VERSION * * @access private */ const IN_ELEMENT_CONTEXT = 'element'; /** * Indicates that we're parsing the `misc` part of the XML * document. * * @since WP_VERSION * * @access private */ const IN_MISC_CONTEXT = 'misc'; /** * Indicates that the next HTML token should be parsed and processed. * * @since WP_VERSION * * @var string */ const PROCESS_NEXT_NODE = 'process-next-node'; /** * Indicates that the current HTML token should be processed without advancing the parser. * * @since WP_VERSION * * @var string */ const PROCESS_CURRENT_NODE = 'process-current-node'; /** * Unlock code that must be passed into the constructor to create this class. * * This class extends the WP_HTML_Tag_Processor, which has a public class * constructor. Therefore, it's not possible to have a private constructor here. * * This unlock code is used to ensure that anyone calling the constructor is * doing so with a full understanding that it's intended to be a private API. * * @access private */ const CONSTRUCTOR_UNLOCK_CODE = 'Use WP_HTML_Processor::create_fragment() instead of calling the class constructor directly.'; } php-toolkit/DataLiberation/class-importentity.php 0000777 00000002367 15251156632 0016310 0 ustar 00 <?php namespace WordPress\DataLiberation; /** * Represents a single entity, whether a WordPress post, post meta, * a single SQL record, or something entirely different. */ class ImportEntity { const TYPE_POST = 'post'; const TYPE_POST_META = 'post_meta'; const TYPE_COMMENT = 'comment'; const TYPE_COMMENT_META = 'comment_meta'; const TYPE_TERM = 'term'; const TYPE_TAG = 'tag'; const TYPE_CATEGORY = 'category'; const TYPE_USER = 'user'; const TYPE_SITE_OPTION = 'site_option'; const POST_FIELDS = array( 'post_title', 'link', 'guid', 'post_excerpt', 'post_published_at', 'post_author', 'post_content', 'post_excerpt', 'post_id', 'post_status', 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt', 'comment_status', 'ping_status', 'post_name', 'post_parent', 'menu_order', 'post_type', 'post_password', 'is_sticky', 'attachment_url', ); private $type; private $data; public function __construct( $type, $data ) { $this->type = $type; $this->data = $data; } public function get_type() { return $this->type; } public function get_data() { return $this->data; } public function set_data( $data ) { $this->data = $data; } } php-toolkit/DataLiberation/EntityReader/interface-entity-reader.php 0000777 00000002402 15251156632 0021535 0 ustar 00 <?php namespace WordPress\DataLiberation\EntityReader; /** * The Entity Reader ingests content from a source and breaks it down into * individual "entities" that WordPress understands - posts, comments, metadata, etc. */ interface EntityReader { /** * Gets the current entity being processed. * * @return ImportedEntity|false The current entity, or false if none available */ public function get_entity(); /** * Advances to the next entity in the source content. * * This is where each data source implements its own logic for parsing the bytes * and extracting the next meaningful piece of content. * * @return bool Whether we successfully moved to the next entity */ public function next_entity(); /** * Checks if we've processed everything from the source. * * @return bool Whether we've processed everything from the source */ public function is_finished(): bool; /** * Returns a cursor position that can be used to resume processing later. * * This allows for processing large imports in chunks without losing your place. * Not all readers support this yet. * * @TODO: Define a general interface for entity readers. * @return string Position marker for resuming later */ public function get_reentrancy_cursor(); } php-toolkit/DataLiberation/EntityReader/class-wxrentityreader.php 0000777 00000074276 15251156632 0021410 0 ustar 00 <?php namespace WordPress\DataLiberation\EntityReader; use WordPress\ByteStream\ReadStream\ByteReadStream; use WordPress\DataLiberation\ImportEntity; use WordPress\XML\XMLProcessor; use WordPress\XML\XMLUnsupportedException; /** * Data Liberation API: WP_WXR_Entity_Reader class * * Reads WordPress eXtended RSS (WXR) files and emits entities like posts, * comments, users, and terms. Enables efficient processing of large WXR * files without loading everything into memory. * * Note this is just a reader. It doesn't import any data into WordPress. It * only reads meaningful entities from the WXR file. * * ## Design goals * * WP_WXR_Entity_Reader is built with the following characteristics in mind: * * * Speed – it should be as fast as possible * * No PHP extensions required – it can run on any PHP installation * * Reliability – no random crashes when encountering malformed XML or UTF-8 sequences * * Low, predictable memory footprint to support 1000GB+ WXR files * * Ability to pause, finish execution, and resume later, e.g. after a fatal error * * ## Implementation * * `WP_WXR_Entity_Reader` uses the `WP_XML_Processor` to find XML tags representing meaningful * WordPress entities. The reader knows the WXR schema and only looks for relevant elements. * For example, it knows that posts are stored in `rss > channel > item` and comments are * stored in `rss > channel > item > `wp:comment`. * * The `$wxr->next_entity()` method stream-parses the next entity from the WXR document and * exposes it to the API consumer via `$wxr->get_entity_type()` and `$wxr->get_entity_date()`. * The next call to `$wxr->next_entity()` remembers where the parsing has stopped and parses * the next entity after that point. * * Example: * * $reader = WP_WXR_Entity_Reader::create_for_streaming(); * * // Add data as it becomes available * $reader->append_bytes( fread( $file_handle, 65536 ) ); * * // Process entities * while ( $reader->next_entity() ) { * switch ( $wxr_reader->get_entity_type() ) { * case 'post': * // ... process post ... * break; * * case 'comment': * // ... process comment ... * break; * * case 'site_option': * // ... process site option ... * break; * * // ... process other entity types ... * } * } * * // Check if we need more input * if ( $reader->is_paused_at_incomplete_input() ) { * // Add more data and continue processing * $reader->append_bytes( fread( $file_handle, 65536 ) ); * } * * The next_entity() -> fread -> break usage pattern may seem a bit tedious. This is expected. Even * if the WXR parsing part of the WP_WXR_Entity_Reader offers a high-level API, working with byte streams * requires reasoning on a much lower level. The StreamChain class shipped in this repository will * make the API consumption easier with its transformation–oriented API for chaining data processors. * * Similarly to `WP_XML_Processor`, the `WP_WXR_Entity_Reader` enters a paused state when it doesn't * have enough XML bytes to parse the entire entity. * * ## Caveats * * ### Extensibility * * `WP_WXR_Entity_Reader` ignores any XML elements it doesn't recognize. The WXR format is extensible * so in the future the reader may start supporting registration of custom handlers for unknown * tags in the future. * * ### Nested entities intertwined with data * * `WP_WXR_Entity_Reader` flushes the current entity whenever another entity starts. The upside is * simplicity and a tiny memory footprint. The downside is that it's possible to craft a WXR * document where some information would be lost. For example: * * ```xml * <rss> * <channel> * <item> * <title>Page with comments</title> * <link>http://wpthemetestdata.wordpress.com/about/page-with-comments/</link> * <wp:postmeta> * <wp:meta_key>_wp_page_template</wp:meta_key> * <wp:meta_value><![CDATA[default]]></wp:meta_value> * </wp:postmeta> * <wp:post_id>146</wp:post_id> * </item> * </channel> * </rss> * ``` * * `WP_WXR_Entity_Reader` would accumulate post data until the `wp:post_meta` tag. Then it would emit a * `post` entity and accumulate the meta information until the `</wp:postmeta>` closer. Then it * would advance to `<wp:post_id>` and **ignore it**. * * This is not a problem in all the `.wxr` files I saw. Still, it is important to note this limitation. * It is possible there is a `.wxr` generator somewhere out there that intertwines post fields with post * meta and comments. If this ever comes up, we could: * * * Emit the `post` entity first, then all the nested entities, and then emit a special `post_update` entity. * * Do multiple passes over the WXR file – one for each level of nesting, e.g. 1. Insert posts, 2. Insert Comments, 3. Insert comment meta * * Buffering all the post meta and comments seems like a bad idea – there might be gigabytes of data. * * ## Remaining work * * @TODO: * * - Revisit the need to implement the Iterator interface. * * @since WP_VERSION */ class WXREntityReader implements EntityReader { /** * The XML processor used to parse the WXR file. * * @since WP_VERSION * @var WP_XML_Processor */ private $xml; /** * The name of the XML tag containing information about the WordPress entity * currently being extracted from the WXR file. * * @since WP_VERSION * @var string|null */ private $entity_tag; /** * The name of the current WordPress entity, such as 'post' or 'comment'. * * @since WP_VERSION * @var string|null */ private $entity_type; /** * The data accumulated for the current entity. * * @since WP_VERSION * @var array */ private $entity_data; /** * The byte offset of the current entity in the original input stream. * * @since WP_VERSION * @var int */ private $entity_opener_byte_offset; /** * Whether the current entity has been emitted. * * @since WP_VERSION * @var bool */ private $entity_finished = false; /** * The number of entities read so far. * * @since WP_VERSION * @var int */ private $entities_read_so_far = 0; /** * The attributes from the last opening tag. * * @since WP_VERSION * @var array */ private $last_opener_attributes = array(); /** * The ID of the last processed post. * * @since WP_VERSION * @var int|null */ private $last_post_id = null; /** * The ID of the last processed comment. * * @since WP_VERSION * @var int|null */ private $last_comment_id = null; /** * Buffer for accumulating text content between tags. * * @since WP_VERSION * @var string */ private $text_buffer = ''; /** * Stream to pull bytes from when the input bytes are exhausted. * * @var WP_Byte_Producer */ private $upstream; /** * Whether the reader has finished processing the input stream. * * @var bool */ private $is_finished = false; /** * Mapping of WXR tags representing site options to their WordPress options names. * These tags are only matched if they are children of the <channel> element. * * @since WP_VERSION * @var array */ private $known_site_options = array(); /** * Mapping of WXR tags to their corresponding entity types and field mappings. * * @since WP_VERSION * @var array */ private $known_entities = array(); public static function create( ?ByteReadStream $upstream = null, $cursor = null, $options = array() ) { $xml_cursor = null; if ( null !== $cursor ) { $cursor = json_decode( $cursor, true ); if ( false === $cursor ) { _doing_it_wrong( __METHOD__, 'Invalid cursor provided for WP_WXR_Entity_Reader::create().', null ); return false; } $xml_cursor = $cursor['xml']; } $xml = XMLProcessor::create_for_streaming( '', $xml_cursor ); $reader = new WXREntityReader( $xml, $options ); if ( null !== $cursor ) { $reader->last_post_id = $cursor['last_post_id']; $reader->last_comment_id = $cursor['last_comment_id']; } if ( null !== $upstream ) { $reader->connect_upstream( $upstream ); if ( null !== $cursor ) { if ( ! isset( $cursor['upstream'] ) ) { // No upstream cursor means we've processed the // entire input stream. $xml->input_finished(); $xml->next_token(); } else { $upstream->seek( $cursor['upstream'] ); } } } return $reader; } /** * Constructor. * * @param XMLProcessor $xml The XML processor to use. * * @since WP_VERSION */ protected function __construct( XMLProcessor $xml, $options = array() ) { $this->xml = $xml; if ( isset( $options['known_site_options'] ) || isset( $options['known_entities'] ) ) { $this->known_site_options = isset( $options['known_site_options'] ) ? $options['known_site_options'] : array(); $this->known_entities = isset( $options['known_entities'] ) ? $options['known_entities'] : array(); return; } // Every XML element is a combination of a long-form namespace and a // local element name, e.g. a syntax <wp:post_id> could actually refer // to a (https://wordpress.org/export/1.0/, post_id) element. // // Namespaces are paramount for parsing XML and cannot be ignored. Elements // element must be matched based on both their namespace and local name. // // Unfortunately, different WXR files defined the `wp` namespace in a different way. // Folks use a mixture of HTTP vs HTTPS protocols and version numbers. We must // account for all possible options to parse these documents correctly. $wxr_namespaces = array( 'http://wordpress.org/export/1.0/', 'https://wordpress.org/export/1.0/', 'http://wordpress.org/export/1.1/', 'https://wordpress.org/export/1.1/', 'http://wordpress.org/export/1.2/', 'https://wordpress.org/export/1.2/', ); $this->known_entities = array( 'item' => array( 'type' => 'post', 'fields' => array( 'title' => 'post_title', 'link' => 'link', 'guid' => 'guid', 'description' => 'post_excerpt', 'pubDate' => 'post_published_at', '{http://purl.org/dc/elements/1.1/}creator' => 'post_author', '{http://purl.org/rss/1.0/modules/content/}encoded' => 'post_content', '{http://wordpress.org/export/1.0/excerpt/}encoded' => 'post_excerpt', '{http://wordpress.org/export/1.1/excerpt/}encoded' => 'post_excerpt', '{http://wordpress.org/export/1.2/excerpt/}encoded' => 'post_excerpt', ), ), ); foreach ( $wxr_namespaces as $wxr_namespace ) { $this->known_site_options = array_merge( $this->known_site_options, array( '{' . $wxr_namespace . '}base_blog_url' => 'home', '{' . $wxr_namespace . '}base_site_url' => 'siteurl', 'title' => 'blogname', ) ); $this->known_entities['item']['fields'] = array_merge( $this->known_entities['item']['fields'], array( '{' . $wxr_namespace . '}post_id' => 'post_id', '{' . $wxr_namespace . '}status' => 'post_status', '{' . $wxr_namespace . '}post_date' => 'post_date', '{' . $wxr_namespace . '}post_date_gmt' => 'post_date_gmt', '{' . $wxr_namespace . '}post_modified' => 'post_modified', '{' . $wxr_namespace . '}post_modified_gmt' => 'post_modified_gmt', '{' . $wxr_namespace . '}comment_status' => 'comment_status', '{' . $wxr_namespace . '}ping_status' => 'ping_status', '{' . $wxr_namespace . '}post_name' => 'post_name', '{' . $wxr_namespace . '}post_parent' => 'post_parent', '{' . $wxr_namespace . '}menu_order' => 'menu_order', '{' . $wxr_namespace . '}post_type' => 'post_type', '{' . $wxr_namespace . '}post_password' => 'post_password', '{' . $wxr_namespace . '}is_sticky' => 'is_sticky', '{' . $wxr_namespace . '}attachment_url' => 'attachment_url', ) ); $this->known_entities = array_merge( $this->known_entities, array( '{' . $wxr_namespace . '}comment' => array( 'type' => 'comment', 'fields' => array( '{' . $wxr_namespace . '}comment_id' => 'comment_id', '{' . $wxr_namespace . '}comment_author' => 'comment_author', '{' . $wxr_namespace . '}comment_author_email' => 'comment_author_email', '{' . $wxr_namespace . '}comment_author_url' => 'comment_author_url', '{' . $wxr_namespace . '}comment_author_IP' => 'comment_author_IP', '{' . $wxr_namespace . '}comment_date' => 'comment_date', '{' . $wxr_namespace . '}comment_date_gmt' => 'comment_date_gmt', '{' . $wxr_namespace . '}comment_content' => 'comment_content', '{' . $wxr_namespace . '}comment_approved' => 'comment_approved', '{' . $wxr_namespace . '}comment_type' => 'comment_type', '{' . $wxr_namespace . '}comment_parent' => 'comment_parent', '{' . $wxr_namespace . '}comment_user_id' => 'comment_user_id', ), ), '{' . $wxr_namespace . '}commentmeta' => array( 'type' => 'comment_meta', 'fields' => array( '{' . $wxr_namespace . '}meta_key' => 'meta_key', '{' . $wxr_namespace . '}meta_value' => 'meta_value', ), ), '{' . $wxr_namespace . '}author' => array( 'type' => 'user', 'fields' => array( '{' . $wxr_namespace . '}author_id' => 'ID', '{' . $wxr_namespace . '}author_login' => 'user_login', '{' . $wxr_namespace . '}author_email' => 'user_email', '{' . $wxr_namespace . '}author_display_name' => 'display_name', '{' . $wxr_namespace . '}author_first_name' => 'first_name', '{' . $wxr_namespace . '}author_last_name' => 'last_name', ), ), '{' . $wxr_namespace . '}postmeta' => array( 'type' => 'post_meta', 'fields' => array( '{' . $wxr_namespace . '}meta_key' => 'meta_key', '{' . $wxr_namespace . '}meta_value' => 'meta_value', ), ), '{' . $wxr_namespace . '}term' => array( 'type' => 'term', 'fields' => array( '{' . $wxr_namespace . '}term_id' => 'term_id', '{' . $wxr_namespace . '}term_taxonomy' => 'taxonomy', '{' . $wxr_namespace . '}term_slug' => 'slug', '{' . $wxr_namespace . '}term_parent' => 'parent', '{' . $wxr_namespace . '}term_name' => 'name', ), ), '{' . $wxr_namespace . '}tag' => array( 'type' => 'tag', 'fields' => array( '{' . $wxr_namespace . '}term_id' => 'term_id', '{' . $wxr_namespace . '}tag_slug' => 'slug', '{' . $wxr_namespace . '}tag_name' => 'name', '{' . $wxr_namespace . '}tag_description' => 'description', ), ), '{' . $wxr_namespace . '}category' => array( 'type' => 'category', 'fields' => array( '{' . $wxr_namespace . '}category_nicename' => 'slug', '{' . $wxr_namespace . '}category_parent' => 'parent', '{' . $wxr_namespace . '}cat_name' => 'name', '{' . $wxr_namespace . '}category_description' => 'description', ), ), ) ); } } public function get_reentrancy_cursor() { /** * @TODO: Instead of adjusting the XML cursor internals, adjust the get_reentrancy_cursor() * call to support $bookmark_name, e.g. $this->xml->get_reentrancy_cursor( 'last_entity' ); * If the cursor internal data was a part of every bookmark, this would have worked * even after evicting the actual bytes where $last_entity is stored. */ $xml_cursor = $this->xml->get_reentrancy_cursor(); $xml_cursor = json_decode( base64_decode( $xml_cursor ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $xml_cursor['upstream_bytes_forgotten'] = $this->entity_opener_byte_offset; $xml_cursor = base64_encode( json_encode( $xml_cursor ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode return json_encode( array( 'xml' => $xml_cursor, 'upstream' => $this->entity_opener_byte_offset, 'last_post_id' => $this->last_post_id, 'last_comment_id' => $this->last_comment_id, ) ); } /** * Gets the data for the current entity. * * @return ImportEntity The entity. * @since WP_VERSION */ public function get_entity() { if ( ! $this->get_entity_type() ) { return false; } return new ImportEntity( $this->get_entity_type(), $this->entity_data ); } /** * Gets the type of the current entity. * * @return string|false The entity type, or false if no entity is being processed. * @since WP_VERSION */ private function get_entity_type() { if ( null !== $this->entity_type ) { return $this->entity_type; } if ( null === $this->entity_tag ) { return false; } if ( ! array_key_exists( $this->entity_tag, $this->known_entities ) ) { return false; } return $this->known_entities[ $this->entity_tag ]['type']; } /** * Gets the ID of the last processed post. * * @return int|null The post ID, or null if no posts have been processed. * @since WP_VERSION */ public function get_last_post_id() { return $this->last_post_id; } /** * Gets the ID of the last processed comment. * * @return int|null The comment ID, or null if no comments have been processed. * @since WP_VERSION */ public function get_last_comment_id() { return $this->last_comment_id; } /** * Appends bytes to the input stream. * * @param string $bytes The bytes to append. * * @since WP_VERSION */ public function append_bytes( string $bytes ): void { $this->xml->append_bytes( $bytes ); } /** * Marks the input as finished. * * @since WP_VERSION */ public function input_finished(): void { $this->xml->input_finished(); } /** * Checks if processing is finished. * * @return bool Whether processing is finished. * @since WP_VERSION */ public function is_finished(): bool { return $this->is_finished; } /** * Checks if processing is paused waiting for more input. * * @return bool Whether processing is paused. * @since WP_VERSION */ public function is_paused_at_incomplete_input(): bool { return $this->xml->is_paused_at_incomplete_input(); } /** * Gets the last error that occurred. * * @return string|null The error message, or null if no error occurred. * @since WP_VERSION */ public function get_last_error(): ?string { return $this->xml->get_last_error(); } public function get_xml_exception(): ?XMLUnsupportedException { return $this->xml->get_exception(); } /** * Advances to the next entity in the WXR file. * * @return bool Whether another entity was found. * @since WP_VERSION */ public function next_entity() { if ( $this->is_finished ) { return false; } while ( true ) { if ( $this->read_next_entity() ) { return true; } // If the read failed because of incomplete input data, // try pulling more bytes from upstream before giving up. if ( $this->is_paused_at_incomplete_input() ) { if ( $this->pull_upstream_bytes() ) { continue; } else { break; } } $this->is_finished = true; break; } return false; } /** * Advances to the next entity in the WXR file. * * @return bool Whether another entity was found. * @since WP_VERSION */ private function read_next_entity() { if ( $this->xml->is_finished() ) { $this->after_entity(); return false; } if ( $this->xml->is_paused_at_incomplete_input() ) { return false; } /** * This is the first call after emitting an entity. * Remove the previous entity details from the internal state * and prepare for the next entity. */ if ( $this->entity_type && $this->entity_finished ) { $this->after_entity(); // If we finished processing the entity on a closing tag, advance the XML processor to. // the next token. Otherwise the array_key_exists( $tag, static::known_entities ) branch. // below will cause an infinite loop. if ( $this->xml->is_tag_closer() ) { if ( false === $this->xml->next_token() ) { return false; } } } /** * Main parsing loop. It advances the XML parser state until a full entity * is available. */ do { $breadcrumbs = $this->xml->get_breadcrumbs(); // Don't process anything outside the <rss> <channel> hierarchy. if ( count( $breadcrumbs ) < 2 || array( '', 'rss' ) !== $breadcrumbs[0] || array( '', 'channel' ) !== $breadcrumbs[1] ) { continue; } /* * Buffer text and CDATA sections until we find the next tag. * Each tag may contain multiple text or CDATA sections so we can't * just assume that a single `get_modifiable_text()` call would get * the entire text content of an element. */ if ( '#text' === $this->xml->get_token_type() || '#cdata-section' === $this->xml->get_token_type() ) { $this->text_buffer .= $this->xml->get_modifiable_text(); continue; } // We're only interested in tags after this point. if ( '#tag' !== $this->xml->get_token_type() ) { continue; } if ( count( $breadcrumbs ) <= 2 && $this->xml->is_tag_opener() ) { $this->entity_opener_byte_offset = $this->xml->get_token_byte_offset_in_the_input_stream(); } $tag_with_namespace = $this->xml->get_tag_namespace_and_local_name(); /** * Custom adjustment: the Accessibility WXR file uses a non-standard * wp:wp_author tag. * * @TODO: Should WP_WXR_Entity_Reader care about such non-standard tags when * the regular WXR importer would ignore them? Perhaps a warning * and an upstream PR would be a better solution. */ if ( '{http://wordpress.org/export/1.2/}wp_author' === $tag_with_namespace ) { $tag_with_namespace = '{http://wordpress.org/export/1.2/}author'; } /** * If the tag is a known entity root, assume the previous entity is * finished, emit it, and start processing the new entity the next * time this function is called. */ if ( array_key_exists( $tag_with_namespace, $this->known_entities ) ) { if ( $this->entity_type && ! $this->entity_finished ) { $this->emit_entity(); return true; } $this->after_entity(); // Only tag openers indicate a new entity. Closers just mean // the previous entity is finished. if ( $this->xml->is_tag_opener() ) { $this->set_entity_tag( $tag_with_namespace ); $this->entity_opener_byte_offset = $this->xml->get_token_byte_offset_in_the_input_stream(); } continue; } /** * We're inside of an entity tag at this point. * * The following code assumes that we'll only see three types of tags: * * * Empty elements – such as <wp:comment_content />, that we'll ignore * * XML element openers with only text nodes inside them. * * XML element closers. * * Specifically, we don't expect to see any nested XML elements such as: * * <wp:comment_content> * <title>Pygmalion</title> * Long time ago... * </wp:comment_content> * * The semantics of such a structure is not clear. The WP_WXR_Entity_Reader will * enter an error state when it encounters such a structure. * * Such nesting wasn't found in any WXR files analyzed when building * this class. If it actually is a part of the WXR standard, every * supported nested element will need a custom handler. */ /** * Buffer the XML tag opener attributes for later use. * * In WXR files, entity attributes come from two sources: * * XML attributes on the tag itself * * Text content between the opening and closing tags * * We store the XML attributes when encountering an opening tag, * but wait until the closing tag to process the entity attributes. * Why? Because only at that point we have both the attributes * and all the related text nodes. */ if ( $this->xml->is_tag_opener() ) { $this->last_opener_attributes = array(); // Get non-namespaced attributes. $names = $this->xml->get_attribute_names_with_prefix( '', '' ); foreach ( $names as list($namespace, $name) ) { $this->last_opener_attributes[ $name ] = $this->xml->get_attribute( $namespace, $name ); } $this->text_buffer = ''; $is_site_option_opener = ( 3 === count( $this->xml->get_breadcrumbs() ) && $this->xml->matches_breadcrumbs( array( 'rss', 'channel', '*' ) ) && array_key_exists( $this->xml->get_tag_namespace_and_local_name(), $this->known_site_options ) ); if ( $is_site_option_opener ) { $this->entity_opener_byte_offset = $this->xml->get_token_byte_offset_in_the_input_stream(); } continue; } /** * At this point we're looking for the nearest tag closer so we can * turn the buffered data into an entity attribute. */ if ( ! $this->xml->is_tag_closer() ) { continue; } if ( ! $this->entity_finished && array( array( '', 'rss' ), array( '', 'channel' ) ) === $this->xml->get_breadcrumbs() ) { // Look for site options in children of the <channel> tag. if ( $this->parse_site_option() ) { return true; } else { // Keep looking for an entity if none was found in the current tag. continue; } } /** * Special handling to accumulate categories stored inside the <category> * tag found inside <item> tags. * * For example, we want to convert this: * * <category><![CDATA[Uncategorized]]></category> * <category domain="category" nicename="wordpress"> * <![CDATA[WordPress]]> * </category> * * Into this: * * 'terms' => [ * [ 'taxonomy' => 'category', 'slug' => '', 'description' => 'Uncategorized' ], * [ 'taxonomy' => 'category', 'slug' => 'WordPress', 'description' => 'WordPress' ], * ] */ if ( 'post' === $this->entity_type && 'category' === $this->xml->get_tag_local_name() && array_key_exists( 'domain', $this->last_opener_attributes ) && array_key_exists( 'nicename', $this->last_opener_attributes ) ) { $this->entity_data['terms'][] = array( 'taxonomy' => $this->last_opener_attributes['domain'], 'slug' => $this->last_opener_attributes['nicename'], 'description' => $this->text_buffer, ); $this->text_buffer = ''; continue; } /** * Store the text content of known tags as the value of the corresponding * entity attribute as defined by the $known_entities mapping. * * Ignores tags unlisted in the $known_entities mapping. * * The WXR format is extensible so this reader could potentially * support registering custom handlers for unknown tags in the future. */ if ( ! isset( $this->known_entities[ $this->entity_tag ]['fields'][ $tag_with_namespace ] ) ) { continue; } $key = $this->known_entities[ $this->entity_tag ]['fields'][ $tag_with_namespace ]; $this->entity_data[ $key ] = $this->text_buffer; $this->text_buffer = ''; } while ( $this->xml->next_token() ); if ( $this->is_paused_at_incomplete_input() ) { return false; } /** * Emit the last unemitted entity after parsing all the data. */ if ( $this->is_finished() && $this->entity_type && ! $this->entity_finished ) { $this->emit_entity(); return true; } return false; } /** * Emits a site option entity from known children of the <channel> * tag, e.g. <wp:base_blog_url> or <title>. * * @return bool Whether a site_option entity was emitted. */ private function parse_site_option() { if ( ! array_key_exists( $this->xml->get_tag_namespace_and_local_name(), $this->known_site_options ) ) { return false; } $this->entity_type = 'site_option'; $this->entity_data = array( 'option_name' => $this->known_site_options[ $this->xml->get_tag_namespace_and_local_name() ], 'option_value' => $this->text_buffer, ); $this->emit_entity(); return true; } /** * Connects a byte stream to automatically pull bytes from once * the last input chunk have been processed. * * @param ByteReadStream $stream The upstream stream. */ public function connect_upstream( ByteReadStream $stream ) { $this->upstream = $stream; } /** * Appends another chunk of bytes from upstream if available. */ private function pull_upstream_bytes() { if ( ! $this->upstream ) { return false; } if ( $this->upstream->reached_end_of_data() ) { $this->input_finished(); return false; } $available_bytes = $this->upstream->pull( 65536 ); $this->append_bytes( $this->upstream->consume( $available_bytes ) ); return true; } /** * Marks the current entity as emitted and updates tracking variables. * * @since WP_VERSION */ private function emit_entity() { if ( 'post' === $this->entity_type ) { // Not all posts have a `<wp:post_id>` tag. $this->last_post_id = isset( $this->entity_data['post_id'] ) ? $this->entity_data['post_id'] : null; } elseif ( 'post_meta' === $this->entity_type ) { $this->entity_data['post_id'] = $this->last_post_id; } elseif ( 'comment' === $this->entity_type ) { $this->last_comment_id = $this->entity_data['comment_id']; $this->entity_data['post_id'] = $this->last_post_id; } elseif ( 'comment_meta' === $this->entity_type ) { $this->entity_data['comment_id'] = $this->last_comment_id; } elseif ( 'tag' === $this->entity_type ) { $this->entity_data['taxonomy'] = 'post_tag'; } elseif ( 'category' === $this->entity_type ) { $this->entity_data['taxonomy'] = 'category'; } $this->entity_finished = true; ++$this->entities_read_so_far; } /** * Sets the current entity tag and type. * * @param string $tag_with_namespace The entity tag name. * * @since WP_VERSION */ private function set_entity_tag( string $tag_with_namespace ) { $this->entity_tag = $tag_with_namespace; if ( array_key_exists( $tag_with_namespace, $this->known_entities ) ) { $this->entity_type = $this->known_entities[ $tag_with_namespace ]['type']; } } /** * Resets the state after processing an entity. * * @since WP_VERSION */ private function after_entity() { $this->entity_tag = null; $this->entity_type = null; $this->entity_data = array(); $this->entity_finished = false; $this->text_buffer = ''; $this->last_opener_attributes = array(); } } php-toolkit/DataLiberation/EntityReader/entity-reader.php 0000777 00000002402 15251156632 0017577 0 ustar 00 <?php namespace WordPress\DataLiberation\EntityReader; /** * The Entity Reader ingests content from a source and breaks it down into * individual "entities" that WordPress understands - posts, comments, metadata, etc. */ interface EntityReader { /** * Gets the current entity being processed. * * @return ImportedEntity|false The current entity, or false if none available */ public function get_entity(); /** * Advances to the next entity in the source content. * * This is where each data source implements its own logic for parsing the bytes * and extracting the next meaningful piece of content. * * @return bool Whether we successfully moved to the next entity */ public function next_entity(); /** * Checks if we've processed everything from the source. * * @return bool Whether we've processed everything from the source */ public function is_finished(): bool; /** * Returns a cursor position that can be used to resume processing later. * * This allows for processing large imports in chunks without losing your place. * Not all readers support this yet. * * @TODO: Define a general interface for entity readers. * @return string Position marker for resuming later */ public function get_reentrancy_cursor(); } php-toolkit/DataLiberation/BlockMarkup/class-blockmarkupprocessor.php 0000777 00000040745 15251156632 0022227 0 ustar 00 <?php namespace WordPress\DataLiberation\BlockMarkup; use WP_Error; use WP_HTML_Tag_Processor; /** * A processor class capable of reading and rewriting block markup. * * This class provides functionality to parse, traverse and modify WordPress block markup. * It extends WP_HTML_Tag_Processor to add block-specific capabilities like: * - Parsing block comments into name and attributes * - Tracking block nesting depth * - Modifying block attributes * - Validating block structure * * Contrary to WP_HTML_Tag_Processor, this class does not support streaming. * It assumes block markup blobs are small enough to fit into memory, otherwise * WordPress won't be able to render them anyway. * * @see WP_HTML_Tag_Processor */ class BlockMarkupProcessor extends WP_HTML_Tag_Processor { /** * The name of the current block (e.g. 'core/paragraph') * * @var string|null */ private $block_name; /** * The attributes of the current block as an associative array * * @var array<string, mixed>|null */ protected $block_attributes; /** * Whether the current block's attributes have been modified and need to be serialized * * @var bool */ private $block_attributes_updated; /** * Whether the current block token is a closing tag (e.g. <!-- /wp:paragraph -->) * * @var bool */ private $block_closer; /** * Whether the current block is self-closing (e.g. <!-- wp:spacer /-->) * * @var bool */ private $self_closing_flag; /** * Stack tracking the names of currently open blocks for validation * * @var array<string> */ private $stack_of_open_blocks = array(); /** * The most recent error encountered while parsing blocks * * @var string|null */ private $last_block_error; /** * A flattened list of paths (arrays of keys) to every attribute found in * $block_attributes. This is used by next_block_attribute() to traverse * attributes without relying on PHP iterator classes. * * @var array<int, array<int|string>>|null */ private $block_attribute_paths = null; /** * The index of the current attribute inside $block_attribute_paths. * Starts at -1 so that the first call to next_block_attribute() positions * the cursor at index 0. * * @var int */ private $block_attribute_index = -1; /** * Gets the type of the current token, adding a special '#block-comment' type * for WordPress block delimiters. * * @return string|null The token type or null if no token */ public function get_token_type(): ?string { switch ( $this->parser_state ) { case self::STATE_COMMENT: if ( null !== $this->block_name ) { return '#block-comment'; } return '#comment'; default: return parent::get_token_type(); } } /** * Gets the most recent error encountered while parsing blocks * * @return WP_Error|null The error message or null if no error */ public function get_last_error(): ?WP_Error { return $this->last_block_error; } /** * Advances past the block closer of the currently matched block and returns * the HTML content found between the block's opener and closer. * * @return string|false The inner HTML content of the block or false if not a block opener. */ public function skip_and_get_block_inner_html() { if ( '#block-comment' !== $this->get_token_type() ) { return false; } if ( $this->is_block_closer() ) { return false; } if ( false === WP_HTML_Tag_Processor::set_bookmark( 'block-start' ) ) { return false; } $starting_block_depth = $this->get_block_depth(); while ( $this->next_token() ) { if ( '#block-comment' === $this->get_token_type() && $this->is_block_closer() && $this->get_block_depth() === $starting_block_depth - 1 ) { break; } } if ( false === WP_HTML_Tag_Processor::set_bookmark( 'block-end' ) ) { WP_HTML_Tag_Processor::release_bookmark( 'block-start' ); return false; } $inner_html_start = $this->bookmarks['block-start']->start + $this->bookmarks['block-start']->length; $inner_html_end = $this->bookmarks['block-end']->start - $inner_html_start; WP_HTML_Tag_Processor::release_bookmark( 'block-start' ); WP_HTML_Tag_Processor::release_bookmark( 'block-end' ); return substr( $this->html, $inner_html_start, $inner_html_end ); } /** * Gets the depth of the currently matched block on the block stack. It only * considers the parent blocks and not HTML elements. * * For example, the paragraph block in the following markup has a depth of 1: * * <!-- wp:core/blockquote --> * <blockquote> * <!-- wp:core/paragraph --> * <p>Hello, there</p> * <!-- /wp:core/paragraph --> * </blockquote> * <!-- /wp:core/blockquote --> * * @return int The number of ancestor blocks */ public function get_block_depth() { return count( $this->stack_of_open_blocks ); } /** * Gets the names of all currently open blocks from outermost to innermost * * @return array List of block names in nesting order */ public function get_block_breadcrumbs() { return $this->stack_of_open_blocks; } /** * Returns the name of the block if the current token is a block comment. * * @return string|false The block name (e.g. 'core/paragraph') or false if not at a block */ public function get_block_name() { if ( null === $this->block_name ) { return false; } return $this->block_name; } /** * Gets all attributes of the current block * * @return array|false The block attributes or false if not at a block */ public function get_block_attributes() { if ( null === $this->block_attributes ) { return false; } return $this->block_attributes; } /** * Gets a specific attribute value from the current block * * @param string $attribute_name The name of the attribute to get * * @return mixed|false The attribute value or false if not found */ public function get_block_attribute( $attribute_name ) { if ( null === $this->block_attributes ) { return false; } return $this->block_attributes[ $attribute_name ] ?? false; } /** * Overwrites all the block attributes of the currently matched block * opener. * * @param array $attributes The new attributes to set * * @return bool Whether the attributes were successfully set */ public function set_block_attributes( $attributes ) { if ( '#block-comment' !== $this->get_token_type() ) { return false; } if ( $this->is_block_closer() ) { return false; } $this->block_attributes = $attributes; $this->block_attributes_updated = true; return true; } /** * Checks if the currently matched token is a block closer, * e.g. <!-- /wp:paragraph -->. * * @return bool True if at a block closer. */ public function is_block_closer() { return null !== $this->block_name && true === $this->block_closer; } /** * Checks if the currently matched token is a self-closing block, * e.g. <!-- wp:spacer /-->. * * @return bool True if at a self-closing block. */ public function is_self_closing_block() { return null !== $this->block_name && true === $this->self_closing_flag; } /** * Advances to the next token in the HTML stream. Matches: * - The regular HTML tokens * - WordPress block openers * - WordPress block closers * - WordPress self-closing blocks * * @return bool Whether a token was parsed. */ public function next_token(): bool { if ( $this->has_bookmark( 'block-delimiter' ) ) { $this->release_bookmark( 'block-delimiter' ); } $this->get_updated_html(); $this->block_name = null; $this->block_attributes = null; $this->block_attribute_paths = null; $this->block_attribute_index = -1; $this->block_closer = false; $this->self_closing_flag = false; $this->block_attributes_updated = false; while ( true ) { if ( false === parent::next_token() ) { return false; } if ( '#tag' === $this->get_token_type() && ( 'HTML' === $this->get_tag() || 'HEAD' === $this->get_tag() || 'BODY' === $this->get_tag() ) ) { continue; } break; } if ( '#comment' !== parent::get_token_type() ) { return true; } $text = parent::get_modifiable_text(); /** * Try to parse as a block. The block parser won't cut it because * while it can parse blocks, it has no semantics for rewriting the * block markup. Let's do our best here: */ $at = strspn( $text, " \t\f\r\n" ); // Whitespace. if ( $at >= strlen( $text ) ) { // This is an empty comment. Not a block. return true; } // Blocks closers start with the solidus character (`/`). if ( '/' === $text[ $at ] ) { $this->block_closer = true; ++$at; } // Blocks start with wp. if ( ! ( $at + 3 < strlen( $text ) && 'w' === $text[ $at ] && 'p' === $text[ $at + 1 ] && ':' === $text[ $at + 2 ] ) ) { return true; } $name_starts_at = $at; // Skip wp. $at += 3; // Parse the actual block name after wp. $name_length = strspn( $text, 'abcdefghijklmnopqrstuwxvyzABCDEFGHIJKLMNOPRQSTUWXVYZ0123456789_-', $at ); if ( 0 === $name_length ) { // This wasn't a block after all, just a regular comment. $this->last_block_error = new WP_Error( 'suspicious-delimiter', sprintf( 'An HTML comment started with "wp:" that was not followed by a valid block name: %s', $text ) ); return true; } $name = substr( $text, $name_starts_at, $name_length + 3 ); $at += $name_length; // Assume no attributes by default. $attributes = array(); // Skip the whitespace that follows the block name. $at += strspn( $text, " \t\f\r\n", $at ); if ( $at < strlen( $text ) ) { // It may be a self-closing block or a block with attributes. // However, block closers can be neither – let's short-circuit. if ( $this->block_closer ) { return true; } // The rest of the comment can only consist of block attributes // and an optional solidus character. $rest = ltrim( substr( $text, $at ) ); $at = strlen( $text ); // Inspect our potential JSON for the self-closing solidus (`/`) character. $json_maybe = $rest; if ( '/' === substr( $json_maybe, - 1 ) ) { // Self-closing block (<!-- wp:image /-->). $this->self_closing_flag = true; $json_maybe = substr( $json_maybe, 0, - 1 ); } // Let's try to parse attributes as JSON. if ( strlen( $json_maybe ) > 0 ) { $attributes = json_decode( trim( $json_maybe ), true ); if ( null === $attributes || ! is_array( $attributes ) ) { // This comment looked like a block comment, but the attributes didn't // parse as a JSON array. This means it wasn't a block after all. $this->last_block_error = new WP_Error( 'suspicious-delimiter', sprintf( '%s could be parsed as a delimiter but JSON attributes were malformed: %s.', $name, $json_maybe ) ); return true; } } } // We have a block name and a valid attributes array. We may not find a block // closer, but let's assume is a block and process it as such // @TODO: Confirm that WordPress block parser would have parsed this as a block. $this->block_name = $name; $this->block_attributes = $attributes; if ( $this->block_closer ) { $popped = array_pop( $this->stack_of_open_blocks ); if ( $popped !== $name ) { $this->last_block_error = new WP_Error( 'mismatched-closer', sprintf( 'Block closer %s does not match the last opened block %s.', $name, $popped ) ); return false; } } elseif ( ! $this->self_closing_flag ) { array_push( $this->stack_of_open_blocks, $name ); } $this->set_bookmark( 'block-delimiter' ); return true; } public function get_block_delimiter_span() { if ( ! $this->has_bookmark( 'block-delimiter' ) ) { return false; } return $this->bookmarks['block-delimiter']; } public function next_block_delimiter() { while ( $this->next_token() ) { if ( '#block-comment' === $this->get_token_type() ) { break; } } if ( '#block-comment' !== $this->get_token_type() ) { return false; } return true; } /** * @inheritDoc */ public function get_updated_html(): string { $this->block_attribute_updates_to_modifiable_text_updates(); return parent::get_updated_html(); } /** * Converts block attribute updates into lexical updates. * * @return bool Whether any lexical updates were created */ private function block_attribute_updates_to_modifiable_text_updates() { // Apply block attribute updates, if any. if ( ! $this->block_attributes_updated ) { return false; } $encoded_attributes = json_encode( $this->block_attributes, JSON_HEX_TAG | // Convert < and > to \u003C and \u003E. JSON_HEX_AMP // Convert & to \u0026. ); if ( '[]' === $encoded_attributes ) { $encoded_attributes = ''; } else { $encoded_attributes .= ' '; } $this->set_modifiable_text( ' ' . $this->block_name . ' ' . $encoded_attributes . ( $this->is_self_closing_block() ? '/' : '' ) ); return true; } /** * Advances to the next block attribute when a block is matched. * * @return bool Whether we successfully advanced to the next attribute. */ public function next_block_attribute() { if ( '#block-comment' !== $this->get_token_type() ) { return false; } if ( null === $this->block_attribute_paths ) { $block_attributes = $this->get_block_attributes(); if ( ! is_array( $block_attributes ) ) { return false; } $this->block_attribute_paths = $this->build_block_attribute_paths( $block_attributes ); $this->block_attribute_index = -1; } ++$this->block_attribute_index; return isset( $this->block_attribute_paths[ $this->block_attribute_index ] ); } protected function get_block_attribute_path() { if ( null === $this->block_attribute_paths || ! isset( $this->block_attribute_paths[ $this->block_attribute_index ] ) ) { return false; } return $this->block_attribute_paths[ $this->block_attribute_index ]; } /** * Gets the key of the currently matched block attribute. * * @return string|false The attribute key or false if no attribute was matched */ public function get_block_attribute_key() { if ( null === $this->block_attribute_paths || ! isset( $this->block_attribute_paths[ $this->block_attribute_index ] ) ) { return false; } $path = $this->block_attribute_paths[ $this->block_attribute_index ]; return $path[ count( $path ) - 1 ]; } /** * Gets the value of the currently matched block attribute. * * @return mixed|false The attribute value or false if no attribute was matched */ public function get_block_attribute_value() { if ( null === $this->block_attribute_paths || ! isset( $this->block_attribute_paths[ $this->block_attribute_index ] ) ) { return false; } $path = $this->block_attribute_paths[ $this->block_attribute_index ]; $value = $this->block_attributes; foreach ( $path as $segment ) { if ( ! is_array( $value ) || ! array_key_exists( $segment, $value ) ) { return false; } $value = $value[ $segment ]; } return $value; } /** * Sets the value of the currently matched block attribute. * * @param mixed $new_value The new value to set * * @return bool Whether the value was successfully set */ public function set_block_attribute_value( $new_value ) { if ( null === $this->block_attribute_paths || ! isset( $this->block_attribute_paths[ $this->block_attribute_index ] ) ) { return false; } $path = $this->block_attribute_paths[ $this->block_attribute_index ]; $ref =& $this->block_attributes; $depth = count( $path ); for ( $i = 0; $i < $depth - 1; $i++ ) { $segment = $path[ $i ]; if ( ! is_array( $ref ) || ! array_key_exists( $segment, $ref ) ) { return false; // Path is invalid. } $ref =& $ref[ $segment ]; } $last_key = $path[ $depth - 1 ]; $ref[ $last_key ] = $new_value; $this->block_attributes_updated = true; return true; } /** * Builds a list of attribute paths, using a depth-first, SELF_FIRST order * that matches the previous iterator behaviour. * * @param array<int|string, mixed> $attributes * @param array<int|string> $base_path * @return array<int, array<int|string>> */ private function build_block_attribute_paths( $attributes, $base_path = array() ) { $paths = array(); foreach ( $attributes as $key => $value ) { $current_path = array_merge( $base_path, array( $key ) ); $paths[] = $current_path; // SELF_FIRST: include parent before children. if ( is_array( $value ) ) { $paths = array_merge( $paths, $this->build_block_attribute_paths( $value, $current_path ) ); } } return $paths; } } php-toolkit/DataLiberation/BlockMarkup/class-blockobject.php 0000777 00000000523 15251156632 0020224 0 ustar 00 <?php namespace WordPress\DataLiberation\BlockMarkup; class BlockObject { public $block_name; public $attrs; public $inner_blocks; public function __construct( $block_name, $attrs = array(), $inner_blocks = array() ) { $this->block_name = $block_name; $this->attrs = $attrs; $this->inner_blocks = $inner_blocks; } } php-toolkit/DataLiberation/BlockMarkup/class-blockmarkupurlprocessor.php 0000777 00000042045 15251156632 0022745 0 ustar 00 <?php namespace WordPress\DataLiberation\BlockMarkup; use Rowbot\URL\URL; use WordPress\DataLiberation\URL\URLInTextProcessor; use WordPress\DataLiberation\URL\CSSURLProcessor; use WordPress\DataLiberation\URL\WPURL; /** * Reports all the URLs in the imported post and enables rewriting them. */ class BlockMarkupUrlProcessor extends BlockMarkupProcessor { private $raw_url; /** * @var URL */ private $parsed_url; private $base_url_string; private $base_url_object; private $url_in_text_processor; private $url_in_text_node_updated; private $css_url_processor; private $css_url_processor_updated; /** * The list of names of URL-related HTML attributes that may be available on * the current token. They will be inspected by next_url_attribute(). * * Possible values: * * - null: We haven't inspected any attribute yet. * - array: The first element is the currently inspected attribute * and the rest of the list are elements yet to be inspected on * the upcoming next_url_attribute() call. * - empty array: We've already inspected all the URL-related attributes. * * @var array<string>|null */ private $inspecting_html_attributes; public function __construct( $html, ?string $base_url_string = null ) { parent::__construct( $html ); $this->base_url_string = $base_url_string; $this->base_url_object = $base_url_string ? WPURL::parse( $base_url_string ) : null; } public function get_updated_html(): string { if ( $this->url_in_text_node_updated ) { $this->set_modifiable_text( $this->url_in_text_processor->get_updated_text() ); $this->url_in_text_node_updated = false; } if ( $this->css_url_processor_updated ) { if ( null !== $this->css_url_processor ) { $updated_css = $this->css_url_processor->get_updated_css(); $this->set_attribute( 'style', $updated_css ); } $this->css_url_processor_updated = false; } return parent::get_updated_html(); } public function get_raw_url() { return $this->raw_url; } public function get_parsed_url() { return $this->parsed_url; } public function next_token(): bool { $this->get_updated_html(); $this->raw_url = null; $this->parsed_url = null; $this->inspecting_html_attributes = null; $this->url_in_text_processor = null; $this->css_url_processor = null; /* * Do not reset url_in_text_node_updated or css_url_processor_updated – they're reset * in get_updated_html() which is called in parent::next_token(). */ return parent::next_token(); } public function next_url() { do { if ( $this->next_url_in_current_token() ) { return true; } } while ( false !== $this->next_token() ); return false; } public function next_url_in_current_token() { $this->raw_url = null; switch ( parent::get_token_type() ) { case '#tag': return $this->next_url_attribute(); case '#block-comment': return $this->next_url_block_attribute(); case '#text': return $this->next_url_in_text_node(); default: return false; } } private function next_url_in_text_node() { if ( '#text' !== $this->get_token_type() ) { return false; } if ( null === $this->url_in_text_processor ) { /* * Use the base URL for URLs matched in text nodes. This is the only * way to recognize a substring "WordPress.org" as a URL. We might * get some false positives this way, e.g. in this string: * * > And that's how you build a theme. Now let's take a look at..." * * `theme.Now` would be recognized as a URL. It's up to the API consumer * to filter out such false positives e.g. by checking the domain against * a list of accepted domains, or the TLD against a list of public suffixes. */ $this->url_in_text_processor = new URLInTextProcessor( $this->get_modifiable_text(), $this->base_url_string ); } while ( $this->url_in_text_processor->next_url() ) { $this->raw_url = $this->url_in_text_processor->get_raw_url(); $this->parsed_url = $this->url_in_text_processor->get_parsed_url(); return true; } return false; } /** * Advances to the next CSS URL in the `style` attribute of the current tag token. * * @return bool Whether a CSS URL was found. */ private function next_url_in_css() { if ( '#tag' !== $this->get_token_type() ) { return false; } if ( null === $this->css_url_processor ) { $css_value = $this->get_attribute( 'style' ); if ( ! is_string( $css_value ) ) { return false; } $this->css_url_processor = new CSSURLProcessor( $css_value ); } while ( $this->css_url_processor->next_url() ) { /** * Skip data URIs. They may be really large and they don't * have a hostname to migrate. */ if ( $this->css_url_processor->is_data_uri() ) { continue; } $this->raw_url = $this->css_url_processor->get_raw_url(); $this->parsed_url = WPURL::parse( $this->raw_url, $this->base_url_string ); if ( false === $this->parsed_url ) { continue; } return true; } return false; } private function next_url_attribute() { $tag = $this->get_tag(); // Check if we have a style attribute with CSS URLs to process. if ( null !== $this->css_url_processor ) { if ( $this->next_url_in_css() ) { return true; } // Done with CSS URLs in this attribute, apply any pending updates and move on. $this->get_updated_html(); $this->css_url_processor = null; } if ( null === $this->inspecting_html_attributes ) { if ( array_key_exists( $tag, self::HTML_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM ) ) { /** * Initialize the list on the first call to next_url_attribute() * for the current token. The last element is the attribute we'll * inspect in the while() loop below. */ $this->inspecting_html_attributes = self::HTML_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM[ $tag ]; // Add style attribute to the list if it exists. if ( null !== $this->get_attribute( 'style' ) ) { $this->inspecting_html_attributes[] = 'style'; } } elseif ( null !== $this->get_attribute( 'style' ) ) { $this->inspecting_html_attributes = array( 'style' ); } else { return false; } } else { /** * Forget the attribute we've inspected on the previous call to * next_url_attribute(). */ array_pop( $this->inspecting_html_attributes ); } while ( count( $this->inspecting_html_attributes ) > 0 ) { $attr = $this->inspecting_html_attributes[ count( $this->inspecting_html_attributes ) - 1 ]; $url_maybe = $this->get_attribute( $attr ); if ( ! is_string( $url_maybe ) ) { array_pop( $this->inspecting_html_attributes ); continue; } // Rewrite any CSS `url()` declarations in the `style` attribute. if ( 'style' === $attr ) { $this->css_url_processor = new CSSURLProcessor( $url_maybe ); if ( $this->next_url_in_css() ) { return true; } // No CSS URLs found, move to next attribute. $this->css_url_processor = null; array_pop( $this->inspecting_html_attributes ); continue; } /* * Use base URL to resolve known URI attributes as we are certain we're * dealing with URI values. * With a base URL, the string "plugins.php" in <a href="plugins.php"> will * be correctly recognized as a URL. * Without a base URL, this Processor would incorrectly skip it. */ $parsed_url = WPURL::parse( $url_maybe, $this->base_url_string ); if ( false === $parsed_url ) { array_pop( $this->inspecting_html_attributes ); continue; } $this->raw_url = $url_maybe; $this->parsed_url = $parsed_url; return true; } return false; } private function next_url_block_attribute() { while ( $this->next_block_attribute() ) { $url_maybe = $this->get_block_attribute_value(); if ( ! is_string( $url_maybe ) || count( $this->get_block_attribute_path() ) > 1 ) { // @TODO: support arrays, objects, and other non-string data structures. continue; } /** * Decide whether the current block attribute holds a URL. * * Known URL attributes can be assumed to hold a URL and be * parsed with the base URL. For example, a "/about-us" value * in a wp:navigation-link block's `url` attribute is a * relative URL to the `/about-us` page. * * Other attributes may or may not contain URLs, but we cannot assume * they do. A value `/about-us` could be a relative URL or a class name. * In those cases, we'll let go of relative URLs and only detect * absolute URLs to avoid treating every string as a URL. This requires * parsing without a base URL. */ $is_relative_url_block_attribute = ( isset( self::BLOCK_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM[ $this->get_block_name() ] ) && in_array( $this->get_block_attribute_key(), self::BLOCK_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM[ $this->get_block_name() ], true ) ); /** * Filters whether a block attribute is known to contain a relative URL. * * This filter allows extending the list of block attributes that are * recognized as containing URLs. When a block attribute is marked as * a known URL attribute, it will be parsed with the base URL, allowing * relative URLs to be properly resolved. * * @since 6.8.0 * * @param bool $is_relative_url_block_attribute Whether the block attribute is known to contain a relative URL. * @param array $context { * Context information about the block attribute. * * @type string $block_name The name of the block (e.g., 'wp:image', 'wp:button'). * @type string $attribute_name The name of the attribute (e.g., 'url', 'href'). * } */ $is_relative_url_block_attribute = apply_filters( 'url_processor_is_relative_url_block_attribute', $is_relative_url_block_attribute, array( 'block_name' => $this->get_block_name(), 'attribute_name' => $this->get_block_attribute_key(), ) ); $parsed_url = false; if ( $is_relative_url_block_attribute ) { // Known relative URL attribute – let's parse with the base URL. $parsed_url = WPURL::parse( $url_maybe, $this->base_url_string ); } else { // Other attributes – let's parse without a base URL (and only detect absolute URLs). $parsed_url = WPURL::parse( $url_maybe ); } if ( false === $parsed_url ) { continue; } $this->raw_url = $url_maybe; $this->parsed_url = $parsed_url; return true; } return false; } /** * Replaces the currently matched URL with a new one. * * @param string $raw_url The raw URL. * @param URL $parsed_url The parsed version of the raw URL. It is required * as $raw_url might be a relative URL pointing to a different * host than this processor's base URL. * * @return bool True if the URL was set, false otherwise. */ public function set_url( $raw_url, $parsed_url ) { if ( null === $this->raw_url ) { return false; } $this->raw_url = $raw_url; $this->parsed_url = $parsed_url; switch ( parent::get_token_type() ) { case '#tag': // Check if we're processing a CSS URL. if ( null !== $this->css_url_processor ) { $this->css_url_processor_updated = true; return $this->css_url_processor->set_raw_url( $raw_url ); } $attr = $this->get_inspected_attribute_name(); if ( false === $attr ) { return false; } $this->set_attribute( $attr, $raw_url ); return true; case '#block-comment': return $this->set_block_attribute_value( $raw_url ); case '#text': if ( null === $this->url_in_text_processor ) { return false; } $this->url_in_text_node_updated = true; return $this->url_in_text_processor->set_raw_url( $raw_url ); } } /** * Rewrites the components of the currently matched URL from ones * provided in $from_url to ones specified in $to_url. * * It preserves the relative nature of the matched URL. * * @TODO: Should this method live in this class? It's specific to the import process * and the URL rewriting logic and has knowledge about the quirks of detecting * relative URLs in text nodes. On the other hand, the detection is performed * by this WPURL_In_Text_Processor class so maybe the two do go hand in hand? */ public function replace_base_url( URL $to_url, ?URL $base_url = null ) { $base_url = $base_url ?? $this->base_url_object; if ( ! $base_url ) { return false; } $result = WPURL::replace_base_url( $this->get_parsed_url(), array( 'old_base_url' => $base_url, 'new_base_url' => $to_url, 'raw_url' => $this->get_raw_url(), 'is_relative' => ( /** * In text nodes, the only detected URLs are absolute. The tricky part * is they may start without a protocol, e.g. `wordpress.org`. Therefore, * we need to tell WPURL::replace_base_url what's our intention regarding * the URL's relativity. It cannot just infer it from the URL itself. */ '#text' !== $this->get_token_type() && ! WPURL::can_parse( $this->get_raw_url() ) ), ) ); if ( false === $result ) { return false; } $this->set_url( $result . '', $result->new_url ); return true; } /** * Returns true if the currently matched URL is absolute. * * @return bool Whether the currently matched URL is absolute. */ public function is_url_absolute() { return WPURL::can_parse( $this->get_raw_url() ); } public function get_inspected_attribute_name() { if ( '#tag' !== $this->get_token_type() ) { return false; } if ( null === $this->inspecting_html_attributes ) { return false; } if ( empty( $this->inspecting_html_attributes ) ) { return false; } return $this->inspecting_html_attributes[ count( $this->inspecting_html_attributes ) - 1 ]; } /** * A list of block attributes that are known to contain URLs. * * It covers WordPress core blocks as of WordPress version 6.9. It can be * extended by plugins and themes via the "url_processor_is_relative_url_block_attribute" * filter. * * @var array */ public const BLOCK_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM = array( 'wp:button' => array( 'url', 'linkTarget' ), 'wp:cover' => array( 'url' ), 'wp:embed' => array( 'url' ), 'wp:gallery' => array( 'url', 'fullUrl' ), 'wp:image' => array( 'url', 'src', 'href' ), 'wp:media-text' => array( 'mediaUrl', 'href' ), 'wp:navigation-link' => array( 'url' ), 'wp:navigation-submenu' => array( 'url' ), 'wp:rss' => array( 'feedURL' ), ); /** * A list of HTML attributes meant to contain URLs, as defined in the HTML specification. * It includes some deprecated attributes like `lowsrc` and `highsrc` for the `IMG` element. * * See https://html.spec.whatwg.org/multipage/indices.html#attributes-1. * See https://stackoverflow.com/questions/2725156/complete-list-of-html-tag-attributes-which-have-a-url-value. */ public const HTML_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM = array( 'A' => array( 'href' ), 'APPLET' => array( 'codebase', 'archive' ), 'AREA' => array( 'href' ), 'AUDIO' => array( 'src' ), 'BASE' => array( 'href' ), 'BLOCKQUOTE' => array( 'cite' ), 'BODY' => array( 'background' ), 'BUTTON' => array( 'formaction' ), 'COMMAND' => array( 'icon' ), 'DEL' => array( 'cite' ), 'EMBED' => array( 'src' ), 'FORM' => array( 'action' ), 'FRAME' => array( 'longdesc', 'src' ), 'HEAD' => array( 'profile' ), 'HTML' => array( 'manifest' ), 'IFRAME' => array( 'longdesc', 'src' ), // SVG <image> element. 'IMAGE' => array( 'href' ), 'IMG' => array( 'longdesc', 'src', 'usemap', 'lowsrc', 'highsrc' ), 'INPUT' => array( 'src', 'usemap', 'formaction' ), 'INS' => array( 'cite' ), 'LINK' => array( 'href' ), 'OBJECT' => array( 'classid', 'codebase', 'data', 'usemap' ), 'Q' => array( 'cite' ), 'SCRIPT' => array( 'src' ), 'SOURCE' => array( 'src' ), 'TRACK' => array( 'src' ), 'VIDEO' => array( 'poster', 'src' ), ); /** * @TODO: Either explicitly support these attributes, or explicitly drop support for * handling their subsyntax. A generic URL matcher might be good enough. */ public const HTML_ATTRIBUTES_WITH_SUBSYNTAX_TO_ACCEPT_RELATIVE_URLS_FROM = array( '*' => array( 'style' ), // background(), background-image(). 'APPLET' => array( 'archive' ), 'IMG' => array( 'srcset' ), 'META' => array( 'content' ), 'SOURCE' => array( 'srcset' ), 'OBJECT' => array( 'archive' ), ); /** * Also <style> and <script> tag content can contain URLs. * <style> has specific syntax rules we can use for matching, but perhaps a generic matcher would be good enough? * * <style> * #domID { background:url(https://mysite.com/wp-content/uploads/image.png) } * </style> * * @TODO: Either explicitly support these tags, or explicitly drop support for * handling their subsyntax. A generic URL matcher might be good enough. */ public const HTML_TAGS_WITH_SUBSYNTAX_TO_ACCEPT_RELATIVE_URLS_FROM = array( 'STYLE', 'SCRIPT', ); } php-toolkit/DataLiberation/CSS/class-cssprocessor.php 0000777 00000157674 15251156632 0016735 0 ustar 00 <?php namespace WordPress\DataLiberation\CSS; use function WordPress\Encoding\codepoint_to_utf8_bytes; use function WordPress\Encoding\compat\_wp_scan_utf8; use function WordPress\Encoding\utf8_ord; use function WordPress\Encoding\wp_scrub_utf8; /** * Tokenizes CSS according to the CSS Syntax Level 3 specification. * * This class follows the algorithm in https://www.w3.org/TR/css-syntax-3/ and * exposes a pull-based API so callers can stream over large stylesheets without * allocating every token up front. Each call to next_token() advances the cursor * and fills in metadata (type, value, raw slice, byte offsets) that you can read * through the getter methods. * * ## Design choices * * ### On-the-fly normalization * * The CSS Spec requires the following normalization step: * * > Replace any U+000D CARRIAGE RETURN (CR) code points, U+000C FORM FEED (FF) * > code points, or pairs of U+000D CARRIAGE RETURN (CR) followed by U+000A LINE * > FEED (LF) in input by a single U+000A LINE FEED (LF) code point. * > Replace any U+0000 NULL or surrogate code points in input with U+FFFD REPLACEMENT * > CHARACTER (�). * * This processor delays normalization as much as possible. That keeps the raw byte * positions intact for accurate rewrites while still letting consumers ask for a * normalized token when they need one. * * ### No EOF token * * The EOF token is a CSS parsing concept, not CSS tokenization concept. Therefore, * this processor does not produce it. * * ### UTF-8 handling * * Only UTF-8 strings are supported. Invalid sequences are replaced with U+FFFD (�) * using the maximal subpart approach described in * https://www.unicode.org/versions/Unicode9.0.0/ch03.pdf, section 3.9 Best Practices * for Using U+FFFD. * * ## Usage * * Basic iteration: * * ```php * $css = 'width: 10px;'; * $processor = CSSProcessor::create( $css ); * while ( $processor->next_token() ) { * echo $processor->get_normalized_token(); * } * // Outputs: * // width: 10px; * ``` * * Rewriting a URL while keeping the rest of the stylesheet intact: * * ```php * $css = 'background: url(old.jpg) center / cover;'; * $processor = CSSProcessor::create( $css ); * while ( $processor->next_token() ) { * if ( CSSProcessor::TOKEN_URL === $processor->get_token_type() ) { * $processor->set_value( 'uploads/new.jpg' ); * } * } * $result = $processor->get_updated_css(); * // background: url(uploads/new.jpg) center / cover; * ``` * * Gathering diagnostics with byte offsets: * * ```php * $css = "color: red;\ncolor: re\nd;"; * $processor = CSSProcessor::create( $css ); * $bad_strings = array(); * while ( $processor->next_token() ) { * if ( CSSProcessor::TOKEN_BAD_STRING === $processor->get_token_type() ) { * $bad_strings[] = array( * 'start' => $processor->get_token_start(), * 'length' => $processor->get_token_length(), * 'value' => $processor->get_unnormalized_token(), * ); * } * } * ``` * * @see https://www.w3.org/TR/css-syntax-3/#tokenization */ class CSSProcessor { /** * Token type constants matching the CSS Syntax Level 3 specification. * * @see https://www.w3.org/TR/css-syntax-3/#tokenization */ public const TOKEN_WHITESPACE = 'whitespace-token'; public const TOKEN_COMMENT = 'comment'; public const TOKEN_STRING = 'string-token'; /** * BAD-STRING tokens occur when a string contains an unescaped newline. * * Valid strings: "hello", 'world', "line1\Aline2" (escaped newline) * Invalid (produces bad-string): "hello * world" (literal newline breaks the string) * * The processor stops at the newline and produces a bad-string token for error recovery. * * @see https://www.w3.org/TR/css-syntax-3/#typedef-bad-string-token */ public const TOKEN_BAD_STRING = 'bad-string-token'; public const TOKEN_HASH = 'hash-token'; public const TOKEN_DELIM = 'delim-token'; public const TOKEN_NUMBER = 'number-token'; public const TOKEN_PERCENTAGE = 'percentage-token'; public const TOKEN_DIMENSION = 'dimension-token'; public const TOKEN_AT_KEYWORD = 'at-keyword-token'; public const TOKEN_COLON = 'colon-token'; public const TOKEN_SEMICOLON = 'semicolon-token'; public const TOKEN_COMMA = 'comma-token'; public const TOKEN_LEFT_PAREN = '(-token'; public const TOKEN_RIGHT_PAREN = ')-token'; public const TOKEN_LEFT_BRACKET = '[-token'; public const TOKEN_RIGHT_BRACKET = ']-token'; public const TOKEN_LEFT_BRACE = '{-token'; public const TOKEN_RIGHT_BRACE = '}-token'; public const TOKEN_FUNCTION = 'function-token'; /** * URL tokens represent unquoted URLs in url() notation. * * Valid: url(image.jpg), url(https://example.com) * Quoted URLs are parsed as url( + string-token + ), not url-token. * * @see https://www.w3.org/TR/css-syntax-3/#typedef-url-token */ public const TOKEN_URL = 'url-token'; /** * BAD-URL tokens occur when a URL contains invalid characters. * * Invalid characters: quotes ("), apostrophes ('), parentheses (() * Example invalid: url(image(.jpg) or url(image".jpg) * * When detected, the processor consumes everything up to ) or EOF. * This prevents the bad URL from breaking subsequent tokens. * * @see https://www.w3.org/TR/css-syntax-3/#typedef-bad-url-token */ public const TOKEN_BAD_URL = 'bad-url-token'; /** * Identifier tokens, such as `color`, `margin-top`, `red`, * `inherit`, `--my-var`, `\escaped`, `über` (Unicode), etc. * * They can contain: letters, digits, hyphens, underscores, non-ASCII, escapes * and cannot start with a digit (unless preceded by a hyphen). * * @see https://www.w3.org/TR/css-syntax-3/#typedef-ident-token */ public const TOKEN_IDENT = 'ident-token'; /** * CDC (Comment Delimiter Close) token: --> * * Legacy token from when CSS was embedded in HTML <style> tags * and needed to be hidden from old browsers using HTML comments: * * <style> * <!-- * body { color: red; } * --> * </style> * * Modern CSS no longer needs these, but they're preserved for compatibility. * In stylesheets, they're typically treated like whitespace. * * @see https://www.w3.org/TR/css-syntax-3/#typedef-CDC-token */ public const TOKEN_CDC = 'CDC-token'; /** * CDO (Comment Delimiter Open) token: <!-- * * Legacy token from when CSS was embedded in HTML <style> tags. * See TOKEN_CDC for full explanation of HTML comment compatibility. * * @see https://www.w3.org/TR/css-syntax-3/#typedef-CDO-token */ public const TOKEN_CDO = 'CDO-token'; /** * @var string */ private $css; /** * @var int */ private $length = 0; /** * @var int */ private $at = 0; /** * The type of the current token. One of the self::TOKEN_* constants. * * @var string|null */ private $token_type = null; /** * The byte offset at which the current token starts. * * Example: * * background-image: url(https://example.com/image.jpg); * ^ token_starts_at * * @var int|null */ private $token_starts_at = null; /** * The byte length of the current token. * * Example: * * background-image: url(https://example.com/image.jpg); * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * token_length * * @var int|null */ private $token_length = null; /** * The byte offset at which the value of the current token starts. * * It is used for STRING and URL tokens. For example: * * background-image: url(https://example.com/image.jpg); * ^ token_value_starts_at * * @var int|null */ private $token_value_starts_at = null; /** * The byte offset at which the value of the current token starts. * * It is relevant for STRING and URL tokens. For example: * * background-image: url(https://example.com/image.jpg); * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * token_value_length * * @var int|null */ private $token_value_length = null; /** * The string value of the current token. * * For numbers, this is a float. * For identifiers/functions/strings/URLs with escapes, this is a decoded string. * Otherwise, it's null and the value is computed from token indices. * * @var string|float|null */ private $token_value = null; /** * The unit of the current token, e.g. "px", "em", "deg", etc. * * @var string|null */ private $token_unit = null; /** * Lexical replacements to apply to input CSS document. * * Tracks modifications to be applied to the CSS, such as changing URL values. * Each entry is an associative array with 'start', 'length', and 'text' keys. * * @var array[] */ private $lexical_updates = array(); /** * Constructor for the CSS processor. * * Do not instantiate directly. Use CSSProcessor::create() instead. * * @param string $css CSS source to tokenize. */ private function __construct( string $css ) { $this->css = $css; $this->length = strlen( $css ); } /** * Creates a CSS processor for the given CSS string. * * Use this method to create a CSS processor instance. * * ## Current Support * * - The only supported document encoding is `UTF-8`, which is the default value. * * @param string $css CSS source to tokenize. * @param string $encoding Text encoding of the document; must be default of 'UTF-8'. * @return static|null The created processor if successful, otherwise null. */ public static function create( string $css, string $encoding = 'UTF-8' ) { if ( 'UTF-8' !== $encoding ) { return null; } return new static( $css ); } /** * Moves to the next token in the CSS stream. * * Implements the main tokenization loop, consuming the next token from the input stream. * * @see https://www.w3.org/TR/css-syntax-3/#consume-token * * @return bool Whether a token was found. */ public function next_token(): bool { $this->after_token(); // Bale out once we reach the end. if ( $this->at >= $this->length ) { return false; } /* * CSS comments. They are not preserved as tokens in the specification, but we * still track them. * * @see https://www.w3.org/TR/css-syntax-3/#consume-comment */ if ( $this->at + 1 < $this->length && '/' === $this->css[ $this->at ] && '*' === $this->css[ $this->at + 1 ] ) { $this->token_type = self::TOKEN_COMMENT; $this->token_starts_at = $this->at; $this->token_value_starts_at = $this->at; $end = strpos( $this->css, '*/', $this->at + 2 ); $this->at = false !== $end ? $end + 2 : $this->length; $this->token_length = $this->at - $this->token_starts_at; $this->token_value_length = $this->token_length - 4; return true; } /* * Whitespace tokens. * * We consider U+000A LINE FEED, U+0009 CHARACTER TABULATION, and U+0020 SPACE bytes covered by the spec. * In addition, we also capture U+000D CARRIAGE RETURN and U+000C FORM FEED that are normally converted to * U+000A LINE FEED during the preprocessing phase. * * @see https://www.w3.org/TR/css-syntax-3/#newline * @see https://www.w3.org/TR/css-syntax-3/#whitespace */ $whitespace_length = strspn( $this->css, "\t\n\f\r ", $this->at ); if ( $whitespace_length > 0 ) { $this->token_type = self::TOKEN_WHITESPACE; $this->token_length = $whitespace_length; $this->token_starts_at = $this->at; $this->at += $whitespace_length; return true; } /* * String tokens with either " or ' as delimiters. * * @see https://www.w3.org/TR/css-syntax-3/#consume-string-token */ if ( '"' === $this->css[ $this->at ] || "'" === $this->css[ $this->at ] ) { return $this->consume_string(); } $char = $this->css[ $this->at ]; $this->token_starts_at = $this->at; /* * U+0023 NUMBER SIGN (#) * * A hash token is created when # is followed by an ident code point or valid escape. * This is commonly used for hex colors (#fff) or ID selectors (#header). * * @see https://www.w3.org/TR/css-syntax-3/#consume-token */ if ( '#' === $char ) { if ( $this->at + 1 < $this->length ) { if ( $this->consume_ident_codepoint( $this->at + 1 ) > 0 || // The next two input code points are a valid escape. $this->is_valid_escape( $this->at + 1 ) ) { // Create a <hash-token>. ++$this->at; // We skip this check as we don't track the type flag: // > If the next 3 input code points would start an ident sequence, // > set the <hash-token>'s type flag to "id". // Consume an ident sequence, and set the <hash-token>'s value to the returned string. $this->consume_ident_sequence(); $this->token_type = self::TOKEN_HASH; $this->token_length = $this->at - $this->token_starts_at; return true; } } // Otherwise, return a <delim-token> with its value set to the current input code point. ++$this->at; $this->token_type = self::TOKEN_DELIM; $this->token_length = 1; return true; } /* * Simple single-byte tokens * * These characters form their own tokens when encountered. * Note: ( tokens here are not function tokens - those are handled * in consume_ident_like() when ( follows an identifier. * * @see https://www.w3.org/TR/css-syntax-3/#tokenization */ $simple = array( '(' => self::TOKEN_LEFT_PAREN, ')' => self::TOKEN_RIGHT_PAREN, ',' => self::TOKEN_COMMA, ':' => self::TOKEN_COLON, ';' => self::TOKEN_SEMICOLON, '[' => self::TOKEN_LEFT_BRACKET, ']' => self::TOKEN_RIGHT_BRACKET, '{' => self::TOKEN_LEFT_BRACE, '}' => self::TOKEN_RIGHT_BRACE, ); if ( isset( $simple[ $char ] ) ) { ++$this->at; $this->token_type = $simple[ $char ]; $this->token_length = 1; return true; } /* * U+0040 COMMERCIAL AT (@) * * An at-keyword is @ followed by an identifier, used for at-rules like * @media, @import, @keyframes, etc. * * @see https://www.w3.org/TR/css-syntax-3/#consume-token */ if ( '@' === $char ) { ++$this->at; // If the next 3 input code points after the @ would start an ident sequence, // consume an ident sequence, create an <at-keyword-token> with its value set to the returned value, // and return it. if ( $this->check_if_3_code_points_start_an_ident_sequence( $this->at ) ) { $this->consume_ident_sequence(); $this->token_type = self::TOKEN_AT_KEYWORD; $this->token_length = $this->at - $this->token_starts_at; return true; } else { // Otherwise, return a <delim-token> with its value set to the current input code point. $this->token_type = self::TOKEN_DELIM; $this->token_length = 1; return true; } } /* * Numbers start with digits, the plus sign, minus sign, and decimal point. * * @see https://www.w3.org/TR/css-syntax-3/#starts-with-a-number */ if ( $this->would_next_3_code_points_start_a_number() ) { return $this->consume_numeric(); } /* * U+002D HYPHEN-MINUS (-) */ if ( '-' === $char ) { // This case is covered above: // > If the input stream starts with a number. /* * If followed by another hyphen and >, this is a CDC token (-->) * * Comment Delimiter Close - legacy HTML comment syntax in CSS. * * @see https://www.w3.org/TR/css-syntax-3/#CDC-token-diagram */ if ( $this->at + 2 < $this->length && '-' === $this->css[ $this->at + 1 ] && '>' === $this->css[ $this->at + 2 ] ) { // Consume them and return a <CDC-token>. $this->at += 3; $this->token_type = self::TOKEN_CDC; $this->token_length = 3; return true; } // Otherwise, if the input stream starts with an ident sequence, // reconsume the current input code point, consume an ident-like // token, and return it. if ( $this->check_if_3_code_points_start_an_ident_sequence( $this->at ) ) { return $this->consume_ident_like(); } // Otherwise, return a <delim-token> with its value set to the current input code point. ++$this->at; $this->token_type = self::TOKEN_DELIM; $this->token_length = 1; return true; } /* * U+003C LESS-THAN SIGN (<) * If followed by !--, this is a CDO token (<!--) * * Comment Delimiter Open - legacy HTML comment syntax in CSS. * * @see https://www.w3.org/TR/css-syntax-3/#CDO-token-diagram */ if ( '<' === $char && $this->at + 3 < $this->length && '!' === $this->css[ $this->at + 1 ] && '-' === $this->css[ $this->at + 2 ] && '-' === $this->css[ $this->at + 3 ] ) { // Consume them and return a <CDO-token>. $this->at += 4; $this->token_type = self::TOKEN_CDO; $this->token_length = 4; return true; } /* * Ident-start code point * * If the input stream starts with an ident sequence, reconsume the current * input code point, consume an ident-like token, and return it. * * Could be an identifier, function, or url() token. * * @see https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token */ if ( $this->check_if_3_code_points_start_an_ident_sequence( $this->at ) ) { return $this->consume_ident_like(); } /* * Delim token (delimiter) * * Any code point that doesn't match above rules becomes a delim token. * Handle multi-byte UTF-8 characters properly. * * @see https://www.w3.org/TR/css-syntax-3/#delim-token-diagram */ if ( ord( $char ) >= 0x80 ) { $new_at = $this->at; $invalid_length = 0; if ( 1 !== _wp_scan_utf8( $this->css, $new_at, $invalid_length, null, 1 ) ) { /** * Trouble ahead! * Bytes at $at are not a valid UTF-8 sequence. * * We'll move forward by $invalid_length bytes and continue processing. * Later on, during the string decoding, we'll replace the invalid bytes with U+FFFD * via maximal subpart”replacement. */ $matched_bytes = $invalid_length; } else { $matched_bytes = $new_at - $this->at; } $this->at += $matched_bytes; $this->token_type = self::TOKEN_DELIM; $this->token_length = $matched_bytes; return true; } // Single ASCII delim. ++$this->at; $this->token_type = self::TOKEN_DELIM; $this->token_length = 1; return true; } /** * Gets the current token type. * * @return string|null */ public function get_token_type(): ?string { return $this->token_type; } /** * Gets the normalized token text from the CSS source. * * Returns the token with CSS normalization and escape decoding applied: * - CSS escapes decoded (e.g., \6c → l, \2f → /, \A → newline) * - \r\n, \r, \f → \n * - \x00 → U+FFFD (�) * * This is different from get_token_value() which returns the semantic value * (e.g., for strings: content without quotes; for numbers: numeric value). * * @return string|null */ public function get_normalized_token(): ?string { if ( null === $this->token_starts_at || null === $this->token_length ) { return null; } return $this->decode_string_or_url( $this->token_starts_at, $this->token_length ); } /** * Gets the raw, unnormalized token text from the CSS source. * * Returns the exact bytes from the source without any normalization. * This preserves original line endings (\r\n, \r, \f) and null bytes. * * @return string|null */ public function get_unnormalized_token(): ?string { if ( null === $this->token_starts_at || null === $this->token_length ) { return null; } return substr( $this->css, $this->token_starts_at, $this->token_length ); } /** * Gets the current token value as a normalized and decoded string. This is * a slight divergence from the CSS Syntax Level 3 spec, where all the numberic * values are parsed as numbers. This processor is only concerned with their * textual representation. * * Returns the semantic value of the token per CSS Syntax Level 3 spec: * * - For delimiters: the single code point * - For numbers/percentages: the string representation of the number * - For dimensions: the string representation of the number (use get_token_unit() for the unit) * - For identifiers/functions/hash/at-keywords: the decoded identifier string * - For strings/URLs: the decoded string value * - For other tokens: null * * @see https://www.w3.org/TR/css-syntax-3/#token-value * @return string|null */ public function get_token_value() { if ( null === $this->token_value ) { if ( null === $this->token_starts_at || null === $this->token_length ) { return null; } switch ( $this->token_type ) { case self::TOKEN_HASH: // Hash value starts after the # character. $this->token_value = $this->decode_string_or_url( $this->token_starts_at + 1, $this->token_length - 1 ); break; case self::TOKEN_AT_KEYWORD: // At-keyword value starts after the @ character. $this->token_value = $this->decode_string_or_url( $this->token_starts_at + 1, $this->token_length - 1 ); break; case self::TOKEN_FUNCTION: // Function name is everything except the final (. $this->token_value = $this->decode_string_or_url( $this->token_starts_at, $this->token_length - 1 ); break; case self::TOKEN_IDENT: // Identifier is the entire token. $this->token_value = $this->decode_string_or_url( $this->token_starts_at, $this->token_length ); break; case self::TOKEN_STRING: case self::TOKEN_BAD_STRING: case self::TOKEN_URL: // Decode and cache the string/URL value. if ( null !== $this->token_value_starts_at && null !== $this->token_value_length ) { $this->token_value = $this->decode_string_or_url( $this->token_value_starts_at, $this->token_value_length ); $this->token_value = $this->token_value; } else { $this->token_value = null; } break; case self::TOKEN_DELIM: // Delim value is the single code point. $this->token_value = $this->decode_string_or_url( $this->token_starts_at, $this->token_length ); break; case self::TOKEN_NUMBER: // Return the string representation of the number (not parsed to float). $this->token_value = substr( $this->css, $this->token_starts_at, $this->token_length ); break; case self::TOKEN_PERCENTAGE: // Return the string representation of the number (without the %). $this->token_value = substr( $this->css, $this->token_starts_at, $this->token_length - 1 ); break; case self::TOKEN_DIMENSION: // Return the string representation of the number (without the unit). $this->token_value = substr( $this->get_normalized_token(), 0, -strlen( $this->token_unit ) ); break; default: $this->token_value = null; break; } } return $this->token_value; } /** * Determines whether the current token is a data URI. * * Only meaningful for URL and STRING tokens. Returns false for all other token types. * * @return bool Whether the current token value starts with "data:" (case-insensitive). */ public function is_data_uri(): bool { if ( null === $this->token_value_starts_at || null === $this->token_value_length ) { return false; } if ( $this->token_value_length < 5 ) { return false; } $offset = $this->token_value_starts_at; return ( ( 'd' === $this->css[ $offset ] || 'D' === $this->css[ $offset ] ) && ( 'a' === $this->css[ $offset + 1 ] || 'A' === $this->css[ $offset + 1 ] ) && ( 't' === $this->css[ $offset + 2 ] || 'T' === $this->css[ $offset + 2 ] ) && ( 'a' === $this->css[ $offset + 3 ] || 'A' === $this->css[ $offset + 3 ] ) && ':' === $this->css[ $offset + 4 ] ); } /** * Gets the token start at. * * @return int|null */ public function get_token_start(): ?int { return $this->token_starts_at; } /** * Gets the token length. * * @return int|null */ public function get_token_length(): ?int { return $this->token_length; } /** * Gets the unit for dimension tokens. * * @return string|null */ public function get_token_unit(): ?string { return $this->token_unit; } /** * Gets the byte at where the token value starts (for STRING and URL tokens). * * @return int|null */ public function get_token_value_start(): ?int { return $this->token_value_starts_at; } /** * Gets the byte length of the token value (for STRING and URL tokens). * * @return int|null */ public function get_token_value_length(): ?int { return $this->token_value_length; } /** * Sets the value of the current URL token. * * This method allows modifying the URL value in url() tokens. The new value * will be properly escaped according to CSS URL syntax rules. * * Currently only URL tokens are supported. Attempting to set the value on * other token types will return false. * * Example: * * $css = 'background: url(old.jpg);'; * $processor = CSSProcessor::create( $css ); * while ( $processor->next_token() ) { * if ( CSSProcessor::TOKEN_URL === $processor->get_token_type() ) { * $processor->set_token_value( 'new.jpg' ); * } * } * echo $processor->get_updated_css(); * // Outputs: background: url(new.jpg); * * @param string $new_value The new URL value (should not include url() wrapper). * @return bool Whether the value was successfully updated. */ public function set_token_value( string $new_value ): bool { // Only URL and string tokens are currently supported. switch ( $this->token_type ) { case self::TOKEN_URL: $this->lexical_updates[] = array( 'start' => $this->token_value_starts_at, 'length' => $this->token_value_length, 'text' => $this->escape_url_value( $new_value ), ); return true; case self::TOKEN_STRING: $this->lexical_updates[] = array( 'start' => $this->token_starts_at, 'length' => $this->token_length, 'text' => $this->escape_url_value( $new_value ), ); return true; default: _doing_it_wrong( __METHOD__, 'set_token_value() only supports URL and string tokens. Got token type: ' . $this->token_type, '1.0.0' ); return false; } } /** * Escapes a URL value for use in quoted url() syntax. * * Always returns a quoted URL string since they're easier * to escape. Quoted URLs are consumed using the string token * rules, and the only values we need to escape in strings, are: * * * Trailing quote. * * Newlines. That amounts to \n, \r, \f, \r\n when preprocessing is considered. * * U+005C REVERSE SOLIDUS (\) * * @see https://www.w3.org/TR/css-syntax-3/#consume-url-token */ private function escape_url_value( string $unescaped ): string { $escaped = ''; $at = 0; while ( $at < strlen( $unescaped ) ) { $safe_len = strcspn( $unescaped, "\n\r\f\\\"", $at ); if ( $safe_len > 0 ) { $escaped .= substr( $unescaped, $at, $safe_len ); $at += $safe_len; continue; } $unsafe_char = $unescaped[ $at ]; switch ( $unsafe_char ) { case "\r": ++$at; /** * Add a trailing space to prevent accidentally creating a * wrong escape sequence. This is a valid CSS syntax and * CSS parsers will ignore that whitespace. * * Without the space, "carriage\return" would be encoded as "carriage\aeturn", * making `e` a part of the escape sequence `\ae` which is not * what the caller intended. */ $escaped .= '\\a '; if ( strlen( $unescaped ) > $at + 1 && "\n" === $unescaped[ $at + 1 ] ) { ++$at; } break; case "\f": case "\n": ++$at; $escaped .= '\\a '; break; case '\\': ++$at; $escaped .= '\\5C '; break; case '"': ++$at; $escaped .= '\\22 '; break; default: _doing_it_wrong( __METHOD__, 'Unexpected character in URL value: ' . $unsafe_char, '1.0.0' ); break; } } return '"' . $escaped . '"'; } /** * Returns the CSS with all modifications applied. * * This method applies all queued lexical updates and returns the modified CSS. * If no modifications were made, returns the original CSS. * * Example: * * $css = 'background: url(old.jpg);'; * $processor = CSSProcessor::create( $css ); * while ( $processor->next_token() ) { * if ( CSSProcessor::TOKEN_URL === $processor->get_token_type() ) { * $processor->set_token_value( 'new.jpg' ); * } * } * echo $processor->get_updated_css(); * // Outputs: background: url(new.jpg); * * @return string The modified CSS. */ public function get_updated_css(): string { if ( empty( $this->lexical_updates ) ) { return $this->css; } // Sort updates by start position in ascending order. usort( $this->lexical_updates, function ( $a, $b ) { return $a['start'] - $b['start']; } ); // Build the output by concatenating original CSS fragments with replacements. $bytes_already_copied = 0; $output = ''; foreach ( $this->lexical_updates as $update ) { $output .= substr( $this->css, $bytes_already_copied, $update['start'] - $bytes_already_copied ); $output .= $update['text']; $bytes_already_copied = $update['start'] + $update['length']; } // Copy remaining CSS after last update. $output .= substr( $this->css, $bytes_already_copied ); return $output; } /** * Clears token state between tokens. */ private function after_token(): void { $this->token_type = null; $this->token_starts_at = null; $this->token_length = null; $this->token_value = null; $this->token_unit = null; $this->token_value_starts_at = null; $this->token_value_length = null; } /** * Consumes a string token. * * Strings are quoted with either " or ' and can contain escape sequences. * Newlines inside strings (without escaping) make the string invalid. * * @see https://www.w3.org/TR/css-syntax-3/#consume-string-token * * @return bool */ private function consume_string(): bool { // Initially create a <string-token> with its value set to the empty string. $this->token_starts_at = $this->at; $ending_char = $this->css[ $this->at ]; // Skip past the opening quote. ++$this->at; $value_starts_at = $this->at; // Characters that need special handling: the ending quote, newlines, backslashes. $special_chars = "'" === $ending_char ? "'\n\f\r\\" : "\"\n\f\r\\"; while ( $this->at < $this->length ) { // Consume normal characters until we hit a special character. $normal_len = strcspn( $this->css, $special_chars, $this->at ); if ( $normal_len > 0 ) { $this->at += $normal_len; } if ( $this->at >= $this->length ) { break; // EOF. } $char = $this->css[ $this->at ]; switch ( $char ) { case $ending_char: // Ending quote. // Return the <string-token>. ++$this->at; $this->token_type = self::TOKEN_STRING; $this->token_length = $this->at - $this->token_starts_at; $this->token_value_starts_at = $value_starts_at; $this->token_value_length = $this->at - $value_starts_at - 1; return true; case "\n": case "\f": case "\r": /* * Newline. * * This is a parse error. Reconsume the current input code point, * create a <bad-string-token>, and return it. * * Unescaped newlines are not allowed in strings. To include a newline, * it must be escaped as \A or the string must end and a new one begin. * * @see https://www.w3.org/TR/css-syntax-3/#consume-string-token */ $this->token_type = self::TOKEN_BAD_STRING; $this->token_length = $this->at - $this->token_starts_at; $this->token_value_starts_at = $value_starts_at; $this->token_value_length = $this->at - $value_starts_at; return true; case '\\': // U+005C REVERSE SOLIDUS (\) // If the next input code point is EOF, do nothing. ++$this->at; if ( $this->at >= $this->length ) { // Backslash-EOF: do nothing, just consume the backslash. continue 2; } // Otherwise, if the next input code point is a newline, consume it. $next = $this->css[ $this->at ]; if ( "\n" === $next || "\f" === $next ) { ++$this->at; continue 2; } elseif ( "\r" === $next ) { ++$this->at; // Handle \r\n as a single newline. if ( $this->at < $this->length && "\n" === $this->css[ $this->at ] ) { ++$this->at; } continue 2; } // Otherwise, (the stream starts with a valid escape) consume an escaped // code point (just to advance position, don't store the result). $this->decode_escape_at( $this->at, $matched_bytes ); $this->at += $matched_bytes; continue 2; default: _doing_it_wrong( __METHOD__, 'Unexpected character in string: ' . $char, '1.0.0' ); break; } } // EOF // This is a parse error. Return the <string-token>. $this->token_type = self::TOKEN_STRING; $this->token_length = $this->at - $this->token_starts_at; $this->token_value_starts_at = $value_starts_at; $this->token_value_length = $this->at - $value_starts_at; return true; } /** * Consumes a numeric token (number, percentage, dimension). * * Numbers can be integers or decimals, with optional sign and exponent. * They can be followed by % (percentage) or an identifier (dimension). * * @TODO: Keep track of the "type" flag ("integer" or "number"). * * @see https://www.w3.org/TR/css-syntax-3/#consume-numeric-token * @see https://www.w3.org/TR/css-syntax-3/#consume-number * * @return bool */ private function consume_numeric(): bool { // Consume a number and let number be the result. // If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), // consume it and append it to repr. if ( '+' === $this->css[ $this->at ] || '-' === $this->css[ $this->at ] ) { ++$this->at; } // While the next input code point is a digit, consume it and append it to repr. $digits = strspn( $this->css, '0123456789', $this->at ); if ( $digits > 0 ) { $this->at += $digits; } // If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then. if ( $this->at + 1 < $this->length && '.' === $this->css[ $this->at ] && $this->css[ $this->at + 1 ] >= '0' && $this->css[ $this->at + 1 ] <= '9' ) { // Consume them. ++$this->at; // While the next input code point is a digit, consume it and append it to repr. $digits = strspn( $this->css, '0123456789', $this->at ); if ( $digits > 0 ) { $this->at += $digits; } } // If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) // or U+0065 LATIN SMALL LETTER E (e), optionally followed by U+002D HYPHEN-MINUS (-) // or U+002B PLUS SIGN (+), followed by a digit, then. if ( $this->at < $this->length ) { $e = $this->css[ $this->at ]; if ( 'e' === $e || 'E' === $e ) { $save_pos = $this->at; ++$this->at; $has_exp = false; if ( $this->at < $this->length ) { $next = $this->css[ $this->at ]; if ( ( '+' === $next || '-' === $next ) && $this->at + 1 < $this->length && $this->css[ $this->at + 1 ] >= '0' && $this->css[ $this->at + 1 ] <= '9' ) { // Consume them. ++$this->at; $has_exp = true; } elseif ( $next >= '0' && $next <= '9' ) { $has_exp = true; } } if ( $has_exp ) { // While the next input code point is a digit, consume it and append it to repr. $digits = strspn( $this->css, '0123456789', $this->at ); if ( $digits > 0 ) { $this->at += $digits; } } else { $this->at = $save_pos; } } } /** * This is the end of spec section 4.3.12. Consume a number. * We still have some work to do as specified in section 4.3.3. Consume a numeric token: * https://www.w3.org/TR/css-syntax-3/#consume-numeric-token */ // If the next 3 input code points would start an ident sequence, then. if ( $this->check_if_3_code_points_start_an_ident_sequence( $this->at ) ) { // Create a <dimension-token> with the same value and type flag as number, // and a unit set initially to the empty string. // Consume an ident sequence. Set the <dimension-token>'s unit to the returned value. $unit_starts_at = $this->at; $this->consume_ident_sequence(); $this->token_unit = $this->decode_string_or_url( $unit_starts_at, $this->at - $unit_starts_at ); $this->token_type = self::TOKEN_DIMENSION; $this->token_length = $this->at - $this->token_starts_at; return true; } // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it. // Create a <percentage-token> with the same value as number, and return it. if ( $this->at < $this->length && '%' === $this->css[ $this->at ] ) { ++$this->at; $this->token_type = self::TOKEN_PERCENTAGE; $this->token_length = $this->at - $this->token_starts_at; return true; } // Otherwise, create a <number-token> with the same value and type flag as number, and return it. $this->token_type = self::TOKEN_NUMBER; $this->token_length = $this->at - $this->token_starts_at; return true; } /** * Consumes an ident-like token (function, url, ident). * * After consuming an identifier, checks if it's followed by '(' to determine * if it's a function or url() token, otherwise it's a plain identifier. * * @see https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token * * @return bool */ private function consume_ident_like(): bool { // Consume an ident sequence, and let string be the result. $ident_start = $this->at; $decoded = $this->consume_ident_sequence(); $string = $decoded ?? $this->decode_string_or_url( $ident_start, $this->at - $ident_start ); // If string's value is an ASCII case-insensitive match for "url", // and the next input code point is U+0028 LEFT PARENTHESIS ((). if ( 0 === strcasecmp( $string, 'url' ) && $this->at < $this->length && '(' === $this->css[ $this->at ] ) { // Consume it. ++$this->at; // While the next two input code points are whitespace, consume the next input code point. $ws_len = strspn( $this->css, "\t\n\f\r ", $this->at ); // If the next one or two input code points are U+0022 QUOTATION MARK ("), // U+0027 APOSTROPHE ('), or whitespace followed by U+0022 QUOTATION MARK (") // or U+0027 APOSTROPHE ('). if ( $this->at + $ws_len < $this->length ) { $next = $this->css[ $this->at + $ws_len ]; if ( '"' === $next || "'" === $next ) { // then create a <function-token> with its value set to string and return it. if ( null !== $decoded ) { $this->token_value = $decoded; } $this->token_type = self::TOKEN_FUNCTION; $this->token_length = $this->at - $this->token_starts_at; return true; } } // Otherwise, consume a url token, and return it. $this->at += $ws_len; return $this->consume_url(); } // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((). if ( $this->at < $this->length && '(' === $this->css[ $this->at ] ) { // Consume it. ++$this->at; // Create a <function-token> with its value set to string and return it. if ( null !== $decoded ) { $this->token_value = $decoded; } $this->token_type = self::TOKEN_FUNCTION; $this->token_length = $this->at - $this->token_starts_at; return true; } // Otherwise, create an <ident-token> with its value set to string and return it. if ( null !== $decoded ) { $this->token_value = $decoded; } $this->token_type = self::TOKEN_IDENT; $this->token_length = $this->at - $this->token_starts_at; return true; } /** * Consumes a url token. * * URL tokens can contain unquoted URLs with escape sequences but not quotes, * parentheses, or certain control characters. Invalid characters create a * bad-url token. * * @see https://www.w3.org/TR/css-syntax-3/#consume-url-token * * @return bool */ private function consume_url(): bool { // Initially create a <url-token> with its value set to the empty string. // Consume as much whitespace as possible. $this->at += strspn( $this->css, "\t\n\f\r ", $this->at ); $value_starts_at = $this->at; // Repeatedly consume the next input code point from the stream. while ( $this->at < $this->length ) { // U+0029 RIGHT PARENTHESIS ()) // Return the <url-token>. if ( ')' === $this->css[ $this->at ] ) { ++$this->at; $this->token_type = self::TOKEN_URL; $this->token_length = $this->at - $this->token_starts_at; $this->token_value_starts_at = $value_starts_at; $this->token_value_length = $this->at - $value_starts_at - 1; return true; } // whitespace // Consume as much whitespace as possible. If the next input code point is // U+0029 RIGHT PARENTHESIS ()) or EOF, consume it and return the <url-token> // (if EOF was encountered, this is a parse error); otherwise, consume the // remnants of a bad url, create a <bad-url-token>, and return it. $ws_len = strspn( $this->css, "\t\n\f\r ", $this->at ); if ( $ws_len > 0 ) { $value_ends_at = $this->at; $this->at += $ws_len; // Accept either ) or EOF after whitespace. if ( $this->at >= $this->length ) { // EOF is a parse error, but we return the <url-token> anyway. $this->token_type = self::TOKEN_URL; $this->token_length = $this->at - $this->token_starts_at; $this->token_value_starts_at = $value_starts_at; $this->token_value_length = $value_ends_at - $value_starts_at; return true; } if ( ')' === $this->css[ $this->at ] ) { // Skip the closing parenthesis and return the <url-token>. ++$this->at; $this->token_type = self::TOKEN_URL; $this->token_length = $this->at - $this->token_starts_at; $this->token_value_starts_at = $value_starts_at; $this->token_value_length = $value_ends_at - $value_starts_at; return true; } return $this->consume_remnants_of_bad_url(); } // These codepoints trigger a parse error. $byte = ord( $this->css[ $this->at ] ); if ( '"' === $this->css[ $this->at ] || "'" === $this->css[ $this->at ] || '(' === $this->css[ $this->at ] || // Non-printable code point. $byte <= 0x08 || // Line Tabulation. 0x0B === $byte || // Control characters. ( $byte >= 0x000E && $byte <= 0x001F ) || // Delete. 0x7F === $byte ) { // Consume the remnants of a bad url, // create a <bad-url-token>, and return it. return $this->consume_remnants_of_bad_url(); } // U+005C REVERSE SOLIDUS (\) // If the stream starts with a valid escape, consume an escaped code point. if ( '\\' === $this->css[ $this->at ] ) { if ( $this->is_valid_escape( $this->at ) ) { ++$this->at; $this->decode_escape_at( $this->at, $matched_bytes ); $this->at += $matched_bytes; continue; } // Otherwise, this is a parse error. Consume the remnants of a bad url, // create a <bad-url-token>, and return it. return $this->consume_remnants_of_bad_url(); } $at = $this->at; $invalid_length = 0; if ( 1 !== _wp_scan_utf8( $this->css, $at, $invalid_length, null, 1 ) ) { /** * Trouble ahead! * Bytes at $at are not a valid UTF-8 sequence. * * We'll move forward by $invalid_length bytes and continue processing. * Later on, during the string decoding, we'll replace the invalid bytes with U+FFFD * via maximal subpart”replacement. */ $this->at += $invalid_length; } else { $this->at = $at; } } // EOF // This is a parse error. Return the <url-token>. $this->token_type = self::TOKEN_URL; $this->token_length = $this->at - $this->token_starts_at; $this->token_value_starts_at = $value_starts_at; $this->token_value_length = $this->at - $value_starts_at; return true; } /** * Finishes a bad url token by consuming remnants. * * When an invalid character is encountered in a URL, we must consume * the remainder of the URL up to the closing ) or EOF. * * @see https://www.w3.org/TR/css-syntax-3/#consume-remnants-of-bad-url * * @return bool */ private function consume_remnants_of_bad_url(): bool { while ( $this->at < $this->length ) { $this->at += strcspn( $this->css, ')\\', $this->at ); if ( $this->at >= $this->length ) { break; } if ( '\\' === $this->css[ $this->at ] ) { ++$this->at; if ( $this->is_valid_escape( $this->at - 1 ) ) { $this->decode_escape_at( $this->at, $matched_bytes ); $this->at += $matched_bytes; continue; } } elseif ( ')' === $this->css[ $this->at ] ) { ++$this->at; break; } } $this->token_type = self::TOKEN_BAD_URL; $this->token_length = $this->at - $this->token_starts_at; return true; } /** * Consumes an identifier sequence. * * Identifiers can contain letters, digits, hyphens, underscores, non-ASCII * characters, and escape sequences. Null bytes are replaced with U+FFFD. * * Returns the decoded identifier string if escapes were encountered, * or null if no decoding was needed (can use raw substring). * * @see https://www.w3.org/TR/css-syntax-3/#consume-name */ private function consume_ident_sequence() { while ( $this->at < $this->length ) { $codepoint_bytes = $this->consume_ident_codepoint( $this->at ); if ( $codepoint_bytes > 0 ) { $this->at += $codepoint_bytes; continue; } if ( $this->is_valid_escape( $this->at ) ) { ++$this->at; $this->decode_escape_at( $this->at, $matched_bytes ); $this->at += $matched_bytes; continue; } break; } } /** * Ident-start code point * A letter, a non-ASCII code point, or U+005F LOW LINE (_). * * Ident code point * An ident-start code point, a digit, or U+002D HYPHEN-MINUS (-). * * @see https://www.w3.org/TR/css-syntax-3/#ident-start-code-point * @return int The number of bytes consumed. */ private function consume_ident_codepoint( $at ): int { // ident code points. if ( ( $this->css[ $at ] >= '0' && $this->css[ $at ] <= '9' ) || '-' === $this->css[ $at ] ) { return 1; } return $this->consume_ident_start_codepoint( $at ); } /** * Ident-start code point * A letter, a non-ASCII code point, or U+005F LOW LINE (_). * * Ident code point * An ident-start code point, a digit, or U+002D HYPHEN-MINUS (-). * * @see https://www.w3.org/TR/css-syntax-3/#ident-start-code-point * @return int The number of bytes consumed. */ private function consume_ident_start_codepoint( $at ): int { if ( $at > $this->length ) { return 0; } // ASCII codepoints. if ( ( $this->css[ $at ] >= 'A' && $this->css[ $at ] <= 'Z' ) || ( $this->css[ $at ] >= 'a' && $this->css[ $at ] <= 'z' ) || '_' === $this->css[ $at ] ) { return 1; } // Special case for null bytes – they are replaced with U+FFFD during preprocessing. if ( "\x00" === $this->css[ $at ] ) { return 1; } $new_at = $at; $invalid_length = 0; if ( 1 !== _wp_scan_utf8( $this->css, $new_at, $invalid_length, null, 1 ) ) { /** * Trouble ahead! * Bytes at $at are not a valid UTF-8 sequence. * * We'll move forward by $invalid_length bytes and continue processing. * Later on, during the string decoding, we'll replace the invalid bytes with U+FFFD * via maximal subpart”replacement. */ return $invalid_length; } $codepoint_byte_length = $new_at - $at; $codepoint = utf8_ord( substr( $this->css, $at, $codepoint_byte_length ) ); if ( null !== $codepoint && $codepoint >= 0x80 ) { return $codepoint_byte_length; } return 0; } /** * Decodes a string or URL value with escape sequences and normalization. * * Fast path: If the slice contains no special characters, returns the raw * substring with almost zero allocations. * * Slow path: Builds the decoded string by optionally processing escapes and * normalizing line endings and null bytes. * * @param int $start Start byte offset. * @param int $length Length of the substring to decode. * @return string Decoded/normalized string. */ private function decode_string_or_url( int $start, int $length ): string { // Fast path: check if any processing is needed. $slice = wp_scrub_utf8( substr( $this->css, $start, $length ) ); $special_chars = "\\\r\f\x00"; if ( false === strpbrk( $slice, $special_chars ) ) { // No special chars - return raw substring (almost zero allocations). return $slice; } // Slow path: build decoded string (one allocation). $decoded = ''; $at = $start; $end = $start + $length; while ( $at < $end ) { // Find next special character. $normal_len = strcspn( $this->css, $special_chars, $at ); if ( $normal_len > 0 ) { // Clamp to not exceed the end boundary. $normal_len = min( $normal_len, $end - $at ); $decoded .= substr( $this->css, $at, $normal_len ); $at += $normal_len; } if ( $at >= $end ) { break; } $char = $this->css[ $at ]; // Handle escapes (if enabled). if ( '\\' === $char ) { if ( $this->is_valid_escape( $at ) ) { ++$at; $decoded .= $this->decode_escape_at( $at, $bytes_consumed ); $at += $bytes_consumed; continue; } // Invalid escape - consume the backslash and keep going. $decoded .= '\\'; ++$at; continue; } // CSS normalization: \r\n, \r, and \f all become \n. if ( "\r" === $char ) { $decoded .= "\n"; ++$at; // Handle \r\n as single newline. if ( $at < $end && "\n" === $this->css[ $at ] ) { ++$at; } continue; } if ( "\f" === $char ) { $decoded .= "\n"; ++$at; continue; } // Null bytes become U+FFFD. if ( "\x00" === $char ) { $decoded .= "\u{FFFD}"; ++$at; continue; } } return $decoded; } /** * Decodes an escape sequence starting at the given offset without * modifying $this->at. * * Escape sequences are backslash followed by 1-6 hex digits (with optional * trailing whitespace) or any other character. Invalid code points are * replaced with U+FFFD. * * @see https://www.w3.org/TR/css-syntax-3/#consume-escaped-code-point * * @param int $offset Byte offset (should point to the character after the backslash). * @param int &$bytes_consumed Output parameter: number of bytes consumed. * @return string The decoded character(s). */ private function decode_escape_at( int $offset, &$bytes_consumed ): string { // This method assumes the U+005C REVERSE SOLIDUS (\) has already been consumed // and the next input code point has already been verified to be part of a valid // escape sequence. $at = $offset; // EOF. if ( $at >= $this->length ) { // This is a parse error. Return U+FFFD REPLACEMENT CHARACTER (�). $bytes_consumed = 0; return "\u{FFFD}"; } // Hex digits. $hex_len = strspn( $this->css, '0123456789ABCDEFabcdef', $at ); if ( $hex_len > 0 ) { // Consume up to 6 hex digits. $hex_len = min( $hex_len, 6 ); $hex = substr( $this->css, $at, $hex_len ); $at += $hex_len; // If the next input code point is whitespace, consume it as well. if ( $at < $this->length ) { $next = $this->css[ $at ]; if ( "\t" === $next || "\n" === $next || "\f" === $next || ' ' === $next ) { ++$at; } elseif ( "\r" === $next ) { ++$at; // Handle \r\n as a single whitespace – the preprocessing phase would replace \r\n with \n. if ( $at < $this->length && "\n" === $this->css[ $at ] ) { ++$at; } } } $bytes_consumed = $at - $offset; // Convert the hex digits to a UTF-8 string. return codepoint_to_utf8_bytes( hexdec( $hex ) ); } // Anything else. // Return the current input code point. // Null bytes are replaced with U+FFFD during preprocessing. if ( "\x00" === $this->css[ $at ] ) { $bytes_consumed = 1; return "\u{FFFD}"; } $new_at = $at; $invalid_length = 0; if ( 1 !== _wp_scan_utf8( $this->css, $new_at, $invalid_length, null, 1 ) ) { /** * Trouble ahead! * Bytes at $at are not a valid UTF-8 sequence. * * We'll move forward by $invalid_length bytes and continue processing. * Later on, during the string decoding, we'll replace the invalid bytes with U+FFFD * via maximal subpart”replacement. */ $matched_bytes = $invalid_length; } else { $matched_bytes = $new_at - $at; } $bytes_consumed = $matched_bytes; return substr( $this->css, $at, $matched_bytes ); } /** * Checks if current position starts a valid escape sequence. * * A valid escape is a backslash not followed by a newline or EOF. * * @see https://www.w3.org/TR/css-syntax-3/#starts-with-a-valid-escape * * @param int $offset Byte offset. * @return bool */ private function is_valid_escape( int $offset ): bool { // If the first code point is not U+005C REVERSE SOLIDUS (\), return false. if ( $offset >= $this->length || '\\' !== $this->css[ $offset ] ) { return false; } // Otherwise, if the second code point is a newline, return false. if ( $offset + 1 >= $this->length ) { // Second code point is EOF - this is a valid escape per spec (weird!) // Are we sure we're interpreting the spec correctly? return true; } // Otherwise, if the second code point is not a newline, return true. return ( "\n" !== $this->css[ $offset + 1 ] && // Form feed is normalized to newline during preprocessing. "\f" !== $this->css[ $offset + 1 ] && // Carriage return is normalized to newline during preprocessing. "\r" !== $this->css[ $offset + 1 ] // We don't need to check for \r\n separately here. The \r check alone covers // that scenario. ); } /** * Checks if the next 3 code points would start a number. * * @see https://www.w3.org/TR/css-syntax-3/#starts-with-a-number * * @return bool */ private function would_next_3_code_points_start_a_number(): bool { if ( $this->at >= $this->length ) { return false; } // Look at the first code point. // U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-). if ( '+' === $this->css[ $this->at ] || '-' === $this->css[ $this->at ] ) { if ( $this->at + 1 >= $this->length ) { return false; } // If the second code point is a digit, return true. if ( $this->css[ $this->at + 1 ] >= '0' && $this->css[ $this->at + 1 ] <= '9' ) { return true; } // Otherwise, the second code point must be a full stop (.) and the third code point must be a digit. if ( '.' === $this->css[ $this->at + 1 ] && $this->at + 2 < $this->length ) { return $this->css[ $this->at + 2 ] >= '0' && $this->css[ $this->at + 2 ] <= '9'; } // Otherwise, return false. return false; } // U+002E FULL STOP (.). if ( '.' === $this->css[ $this->at ] ) { if ( $this->at + 1 >= $this->length ) { return false; } return $this->css[ $this->at + 1 ] >= '0' && $this->css[ $this->at + 1 ] <= '9'; } // Digit. if ( $this->css[ $this->at ] >= '0' && $this->css[ $this->at ] <= '9' ) { return true; } // Anything else – return false. return false; } /** * Checks if three code points would start an identifier sequence. * * This implements the CSS spec's "Check if three code points would start an ident sequence" * algorithm, which checks the code point at $offset and the following two code points. * * NOTE: "Three code points" means three Unicode code points, not three bytes. * Multi-byte UTF-8 sequences count as single code points. * * @see https://www.w3.org/TR/css-syntax-3/#would-start-an-identifier * * @param int $offset Byte offset of the first code point to check. * @return bool */ private function check_if_3_code_points_start_an_ident_sequence( int $offset ): bool { if ( $offset >= $this->length ) { return false; } if ( '-' === $this->css[ $offset ] ) { // If the second code point is a U+002D HYPHEN-MINUS (-), return true. // e.g. --custom-property. if ( $offset + 1 < $this->length && '-' === $this->css[ $offset + 1 ] ) { return true; } // Otherwise, check if the second code point is an ident-START code point or valid escape. // Note: After a hyphen, only ident-START code points are valid, NOT digits or hyphens. ++$offset; } return $this->consume_ident_start_codepoint( $offset ) > 0 || $this->is_valid_escape( $offset ); } } php-toolkit/DataLiberation/vendor-patched/psr/log/README.md 0000777 00000002444 15251156632 0017470 0 ustar 00 # PSR Log This repository holds all interfaces/classes/traits related to [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). Note that this is not a logger of its own. It is merely an interface that describes a logger. See the specification for more details. ## Installation ```bash composer require psr/log ``` ## Usage If you need a logger, you can use the interface like this: ```php <?php use Psr\Log\LoggerInterface; class Foo { private $logger; public function __construct(LoggerInterface $logger = null) { $this->logger = $logger; } public function doSomething() { if ($this->logger) { $this->logger->info('Doing work'); } try { $this->doSomethingElse(); } catch (Exception $exception) { $this->logger->error('Oh no!', array('exception' => $exception)); } // do something useful } } ``` You can then pick one of the implementations of the interface to get a logger. If you want to implement the interface, you can require this package and implement `Psr\Log\LoggerInterface` in your code. Please read the [specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) for details. php-toolkit/DataLiberation/vendor-patched/psr/log/src/LoggerInterface.php 0000777 00000005365 15251156632 0022556 0 ustar 00 <?php namespace Psr\Log; use Stringable; /** * Describes a logger instance. * * The message MUST be a string or object implementing __toString(). * * The message MAY contain placeholders in the form: {foo} where foo * will be replaced by the context data in key "foo". * * The context array can contain arbitrary data. The only assumption that * can be made by implementors is that if an Exception instance is given * to produce a stack trace, it MUST be in a key named "exception". * * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md * for the full interface specification. */ interface LoggerInterface { /** * System is unusable. * * @param mixed[] $context * @param string|Stringable $message */ public function emergency( $message, array $context = [] ): void; /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param mixed[] $context * @param string|Stringable $message */ public function alert( $message, array $context = [] ): void; /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param mixed[] $context * @param string|Stringable $message */ public function critical( $message, array $context = [] ): void; /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param mixed[] $context * @param string|Stringable $message */ public function error( $message, array $context = [] ): void; /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param mixed[] $context * @param string|Stringable $message */ public function warning( $message, array $context = [] ): void; /** * Normal but significant events. * * @param mixed[] $context * @param string|Stringable $message */ public function notice( $message, array $context = [] ): void; /** * Interesting events. * * Example: User logs in, SQL logs. * * @param mixed[] $context * @param string|Stringable $message */ public function info( $message, array $context = [] ): void; /** * Detailed debug information. * * @param mixed[] $context * @param string|Stringable $message */ public function debug( $message, array $context = [] ): void; /** * Logs with an arbitrary level. * * @param mixed $level * @param mixed[] $context * * @param string|Stringable $message * * @throws InvalidArgumentException */ public function log( $level, $message, array $context = [] ): void; } php-toolkit/DataLiberation/vendor-patched/psr/log/src/LoggerAwareInterface.php 0000777 00000000335 15251156632 0023526 0 ustar 00 <?php namespace Psr\Log; /** * Describes a logger-aware instance. */ interface LoggerAwareInterface { /** * Sets a logger instance on the object. */ public function setLogger( LoggerInterface $logger ): void; } php-toolkit/DataLiberation/vendor-patched/psr/log/src/LoggerAwareTrait.php 0000777 00000000476 15251156632 0022717 0 ustar 00 <?php namespace Psr\Log; /** * Basic Implementation of LoggerAwareInterface. */ trait LoggerAwareTrait { /** * The logger instance. * @var LoggerInterface|null */ protected $logger; /** * Sets a logger. */ public function setLogger( LoggerInterface $logger ): void { $this->logger = $logger; } } php-toolkit/DataLiberation/vendor-patched/psr/log/src/AbstractLogger.php 0000777 00000000633 15251156632 0022412 0 ustar 00 <?php namespace Psr\Log; /** * This is a simple Logger implementation that other Loggers can inherit from. * * It simply delegates all log-level-specific methods to the `log` method to * reduce boilerplate code that a simple Logger that does the same thing with * messages regardless of the error level has to implement. */ abstract class AbstractLogger implements LoggerInterface { use LoggerTrait; } php-toolkit/DataLiberation/vendor-patched/psr/log/src/LogLevel.php 0000777 00000000441 15251156632 0021215 0 ustar 00 <?php namespace Psr\Log; /** * Describes log levels. */ class LogLevel { const EMERGENCY = 'emergency'; const ALERT = 'alert'; const CRITICAL = 'critical'; const ERROR = 'error'; const WARNING = 'warning'; const NOTICE = 'notice'; const INFO = 'info'; const DEBUG = 'debug'; } php-toolkit/DataLiberation/vendor-patched/psr/log/src/InvalidArgumentException.php 0000777 00000000140 15251156632 0024450 0 ustar 00 <?php namespace Psr\Log; class InvalidArgumentException extends \InvalidArgumentException { } php-toolkit/DataLiberation/vendor-patched/psr/log/src/LoggerTrait.php 0000777 00000005317 15251156632 0021736 0 ustar 00 <?php namespace Psr\Log; use Stringable; /** * This is a simple Logger trait that classes unable to extend AbstractLogger * (because they extend another class, etc) can include. * * It simply delegates all log-level-specific methods to the `log` method to * reduce boilerplate code that a simple Logger that does the same thing with * messages regardless of the error level has to implement. */ trait LoggerTrait { /** * System is unusable. * * @param string|Stringable $message */ public function emergency( $message, array $context = [] ): void { $this->log( LogLevel::EMERGENCY, $message, $context ); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string|Stringable $message */ public function alert( $message, array $context = [] ): void { $this->log( LogLevel::ALERT, $message, $context ); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string|Stringable $message */ public function critical( $message, array $context = [] ): void { $this->log( LogLevel::CRITICAL, $message, $context ); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string|Stringable $message */ public function error( $message, array $context = [] ): void { $this->log( LogLevel::ERROR, $message, $context ); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string|Stringable $message */ public function warning( $message, array $context = [] ): void { $this->log( LogLevel::WARNING, $message, $context ); } /** * Normal but significant events. * * @param string|Stringable $message */ public function notice( $message, array $context = [] ): void { $this->log( LogLevel::NOTICE, $message, $context ); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string|Stringable $message */ public function info( $message, array $context = [] ): void { $this->log( LogLevel::INFO, $message, $context ); } /** * Detailed debug information. * * @param string|Stringable $message */ public function debug( $message, array $context = [] ): void { $this->log( LogLevel::DEBUG, $message, $context ); } /** * Logs with an arbitrary level. * * @param mixed $level * * @param string|Stringable $message * * @throws InvalidArgumentException */ abstract public function log( $level, $message, array $context = [] ): void; } php-toolkit/DataLiberation/vendor-patched/psr/log/src/NullLogger.php 0000777 00000001203 15251156632 0021553 0 ustar 00 <?php namespace Psr\Log; use Stringable; /** * This Logger can be used to avoid conditional log calls. * * Logging should always be optional, and if no logger is provided to your * library creating a NullLogger instance to have something to throw logs at * is a good way to avoid littering your code with `if ($this->logger) { }` * blocks. */ class NullLogger extends AbstractLogger { /** * Logs with an arbitrary level. * * @param mixed[] $context * * @param string|Stringable $message * * @throws InvalidArgumentException */ public function log( $level, $message, array $context = [] ): void { // noop } } php-toolkit/DataLiberation/vendor-patched/psr/log/LICENSE 0000777 00000002075 15251156632 0017216 0 ustar 00 Copyright (c) 2012 PHP Framework Interoperability Group Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/psr/log/composer.json 0000777 00000000677 15251156632 0020741 0 ustar 00 { "name": "psr/log", "description": "Common interface for logging libraries", "keywords": [ "psr", "psr-3", "log" ], "homepage": "https://github.com/php-fig/log", "license": "MIT", "authors": [ { "name": "PHP-FIG", "homepage": "https://www.php-fig.org/" } ], "require": { "php": ">=8.0.0" }, "autoload": { "psr-4": { "Psr\\Log\\": "src" } }, "extra": { "branch-alias": { "dev-master": "3.x-dev" } } } php-toolkit/DataLiberation/vendor-patched/psr/event-dispatcher/LICENSE 0000777 00000002050 15251156632 0021673 0 ustar 00 MIT License Copyright (c) 2018 PHP-FIG Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/psr/event-dispatcher/README.md 0000777 00000000461 15251156632 0022151 0 ustar 00 # PSR Event Dispatcher This repository holds the interfaces related to [PSR-14](http://www.php-fig.org/psr/psr-14/). Note that this is not an Event Dispatcher implementation of its own. It is merely interfaces that describe the components of an Event Dispatcher. See the specification for more details. php-toolkit/DataLiberation/vendor-patched/psr/event-dispatcher/composer.json 0000777 00000000703 15251156632 0023413 0 ustar 00 { "name": "psr/event-dispatcher", "description": "Standard interfaces for event handling.", "type": "library", "keywords": [ "psr", "psr-14", "events" ], "license": "MIT", "authors": [ { "name": "PHP-FIG", "homepage": "http://www.php-fig.org/" } ], "require": { "php": ">=7.2.0" }, "autoload": { "psr-4": { "Psr\\EventDispatcher\\": "src" } }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } } } php-toolkit/DataLiberation/vendor-patched/psr/event-dispatcher/src/StoppableEventInterface.php 0000777 00000001423 15251156632 0026745 0 ustar 00 <?php declare( strict_types=1 ); namespace Psr\EventDispatcher; /** * An Event whose processing may be interrupted when the event has been handled. * * A Dispatcher implementation MUST check to determine if an Event * is marked as stopped after each listener is called. If it is then it should * return immediately without calling any further Listeners. */ interface StoppableEventInterface { /** * Is propagation stopped? * * This will typically only be used by the Dispatcher to determine if the * previous listener halted propagation. * * @return bool * True if the Event is complete and no further listeners should be called. * False to continue calling listeners. */ public function isPropagationStopped(): bool; } php-toolkit/DataLiberation/vendor-patched/psr/event-dispatcher/src/ListenerProviderInterface.php 0000777 00000001031 15251156632 0027305 0 ustar 00 <?php declare( strict_types=1 ); namespace Psr\EventDispatcher; /** * Mapper from an event to the listeners that are applicable to that event. */ interface ListenerProviderInterface { /** * @param object $event * An event for which to return the relevant listeners. * * @return iterable[callable] * An iterable (array, iterator, or generator) of callables. Each * callable MUST be type-compatible with $event. */ public function getListenersForEvent( object $event ): iterable; } php-toolkit/DataLiberation/vendor-patched/psr/event-dispatcher/src/EventDispatcherInterface.php 0000777 00000000666 15251156632 0027112 0 ustar 00 <?php declare( strict_types=1 ); namespace Psr\EventDispatcher; /** * Defines a dispatcher for events. */ interface EventDispatcherInterface { /** * Provide all relevant listeners with an event to process. * * @param object $event * The object to process. * * @return object * The Event that was passed, now modified by listeners. */ public function dispatch( object $event ); } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/tests/IdnaV2Test.php 0000777 00000014235 15251156632 0022652 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna\Test; use ReflectionClass; use Rowbot\Idna\Idna; use function array_filter; use function array_flip; use function is_array; use function sprintf; use function strncmp; use const ARRAY_FILTER_USE_KEY; class IdnaV2Test extends IdnaV2TestCase { private const DEFAULT_OPTIONS = [ 'CheckHyphens' => true, 'CheckBidi' => true, 'CheckJoiners' => true, 'UseSTD3ASCIIRules' => true, 'Transitional_Processing' => false, 'VerifyDnsLength' => true, ]; /** * @var array<int, string> */ protected static $errorMap; public static function setUpBeforeClass(): void { if ( isset( self::$errorMap ) ) { return; } $reflection = new ReflectionClass( Idna::class ); $errors = array_filter( $reflection->getConstants(), static function ( string $name ): bool { return strncmp( $name, 'ERROR_', 6 ) === 0; }, ARRAY_FILTER_USE_KEY ); self::$errorMap = array_flip( $errors ); } /** * @return array<int, array<int, string>> */ public function getData(): array { return $this->loadTestData( Idna::UNICODE_VERSION ); } /** * @dataProvider getData */ public function testToUnicode( string $source, string $toUnicode, string $toUnicodeStatus, string $toAsciiN, string $toAsciiNStatus, string $toAsciiT, string $toAsciiTStatus ): void { [ $toUnicode, $toUnicodeStatus, $toAsciiN, $toAsciiNStatus, $toAsciiT, $toAsciiTStatus, ] = $this->translate( $source, $toUnicode, $toUnicodeStatus, $toAsciiN, $toAsciiNStatus, $toAsciiT, $toAsciiTStatus ); $result = Idna::toUnicode( $source, self::DEFAULT_OPTIONS ); self::assertSame( $toUnicode, $result->getDomain() ); if ( $toUnicodeStatus === [] ) { self::assertFalse( $result->hasErrors(), sprintf( 'Expected no errors, but found %s.', $this->containedErrors( $result->getErrors() ) ) ); } else { self::assertTrue( $result->hasErrors(), sprintf( 'Expected to find %s, but found %s', $this->expectedErrors( $toUnicodeStatus ), $this->containedErrors( $result->getErrors() ) ) ); } } /** * @dataProvider getData */ public function testToAsciiNonTransitional( string $source, string $toUnicode, string $toUnicodeStatus, string $toAsciiN, string $toAsciiNStatus, string $toAsciiT, string $toAsciiTStatus ): void { [ $toUnicode, $toUnicodeStatus, $toAsciiN, $toAsciiNStatus, $toAsciiT, $toAsciiTStatus, ] = $this->translate( $source, $toUnicode, $toUnicodeStatus, $toAsciiN, $toAsciiNStatus, $toAsciiT, $toAsciiTStatus ); $result = Idna::toAscii( $source, self::DEFAULT_OPTIONS ); if ( $toAsciiNStatus === [] ) { self::assertSame( $toAsciiN, $result->getDomain() ); self::assertFalse( $result->hasErrors(), sprintf( 'Expected no errors, but found %s.', $this->containedErrors( $result->getErrors() ) ) ); } else { self::assertTrue( $result->hasErrors(), sprintf( 'Expected %s, but found no errors.', $this->expectedErrors( $toAsciiTStatus ) ) ); } } /** * @dataProvider getData */ public function testToAsciiTransitional( string $source, string $toUnicode, string $toUnicodeStatus, string $toAsciiN, string $toAsciiNStatus, string $toAsciiT, string $toAsciiTStatus ): void { [ $toUnicode, $toUnicodeStatus, $toAsciiN, $toAsciiNStatus, $toAsciiT, $toAsciiTStatus, ] = $this->translate( $source, $toUnicode, $toUnicodeStatus, $toAsciiN, $toAsciiNStatus, $toAsciiT, $toAsciiTStatus ); $options = self::DEFAULT_OPTIONS; $options['Transitional_Processing'] = true; $result = Idna::toAscii( $source, $options ); // There is currently a bug in the test data, where it is expected that the following 2 // source strings result in an empty string. However, due to the way the test files are setup // it currently isn't possible to represent an empty string as an expected value. So, we // skip these 2 problem tests. I have notified the Unicode Consortium about this and they // have passed the information along to the spec editors. if ( $source === "\u{200C}" || $source === "\u{200D}" ) { $toAsciiT = ''; } if ( $toAsciiTStatus === [] ) { self::assertSame( $toAsciiT, $result->getDomain() ); self::assertFalse( $result->hasErrors(), sprintf( 'Expected no errors, but found %s.', $this->containedErrors( $result->getErrors() ) ) ); } else { self::assertTrue( $result->hasErrors(), sprintf( 'Expected %s, but found no errors.', $this->expectedErrors( $toAsciiTStatus ) ) ); } } public function assertErrors( array $expectedErrors, int $errors ): void { foreach ( $expectedErrors as $errorCode ) { if ( is_array( $errorCode ) ) { self::assertTrue( $this->matchErrors( $errorCode, $errors ) ); continue; } self::assertTrue( ( $errors & $errorCode ) !== 0, sprintf( 'Expected %s (%d), but only found %s.', self::$errorMap[ $errorCode ], $errorCode, $this->containedErrors( $errors ) ) ); } } public function expectedErrors( array $expectedErrors ): string { $message = ''; foreach ( $expectedErrors as $i => $errorCode ) { if ( is_array( $errorCode ) ) { $message .= '('; foreach ( $errorCode as $j => $error ) { if ( $j > 0 ) { $message .= ' OR '; } $message .= sprintf( '%s (%d)', self::$errorMap[ $error ], $error ); } $message .= ')'; } else { if ( $i > 0 ) { $message .= ' | '; } $message .= sprintf( '%s (%d)', self::$errorMap[ $errorCode ], $errorCode ); } } return $message; } public function containedErrors( int $errors ): string { if ( $errors === 0 ) { return 'no errors'; } $out = ''; $count = 0; foreach ( self::$errorMap as $code => $name ) { if ( ( $errors & $code ) !== 0 ) { if ( $count ++ > 0 ) { $out .= ' | '; } $out .= sprintf( '%s (%d)', $name, $code ); } } return $out; } public function matchErrors( array $potentialErrors, int $errors ): bool { foreach ( $potentialErrors as $error ) { if ( ( $errors & $error ) !== 0 ) { return true; } } return false; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/tests/IdnaV2TestCase.php 0000777 00000012433 15251156632 0023444 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna\Test; use GuzzleHttp\Client; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; use Rowbot\Idna\Idna; use RuntimeException; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use function array_map; use function count; use function explode; use function in_array; use function preg_last_error; use function preg_match_all; use function sprintf; use const DIRECTORY_SEPARATOR; use const PREG_NO_ERROR; class IdnaV2TestCase extends TestCase { private const ERROR_CODE_MAP = [ 'P1' => Idna::ERROR_DISALLOWED, 'P4' => [ Idna::ERROR_EMPTY_LABEL, Idna::ERROR_DOMAIN_NAME_TOO_LONG, Idna::ERROR_LABEL_TOO_LONG, Idna::ERROR_PUNYCODE, ], 'V1' => Idna::ERROR_INVALID_ACE_LABEL, 'V2' => Idna::ERROR_HYPHEN_3_4, 'V3' => [ Idna::ERROR_LEADING_HYPHEN, Idna::ERROR_TRAILING_HYPHEN ], 'V4' => Idna::ERROR_LABEL_HAS_DOT, 'V5' => Idna::ERROR_LEADING_COMBINING_MARK, 'V6' => Idna::ERROR_DISALLOWED, // V7 and V8 are handled by C* and B* respectively. 'A3' => Idna::ERROR_PUNYCODE, 'A4_1' => Idna::ERROR_DOMAIN_NAME_TOO_LONG, 'A4_2' => [ Idna::ERROR_EMPTY_LABEL, Idna::ERROR_LABEL_TOO_LONG ], 'B1' => Idna::ERROR_BIDI, 'B2' => Idna::ERROR_BIDI, 'B3' => Idna::ERROR_BIDI, 'B4' => Idna::ERROR_BIDI, 'B5' => Idna::ERROR_BIDI, 'B6' => Idna::ERROR_BIDI, 'C1' => Idna::ERROR_CONTEXTJ, 'C2' => Idna::ERROR_CONTEXTJ, // ContextO isn't tested here. // 'C3' => Idna::ERROR_CONTEXTO_PUNCTUATION, // 'C4' => Idna::ERROR_CONTEXTO_PUNCTUATION, // 'C5' => Idna::ERROR_CONTEXTO_PUNCTUATION, // 'C6' => Idna::ERROR_CONTEXTO_PUNCTUATION, // 'C7' => Idna::ERROR_CONTEXTO_PUNCTUATION, // 'C8' => Idna::ERROR_CONTEXTO_DIGITS, // 'C9' => Idna::ERROR_CONTEXTO_DIGITS, 'X4_2' => Idna::ERROR_EMPTY_LABEL, 'X3' => Idna::ERROR_EMPTY_LABEL, ]; private const BASE_URI = 'https://www.unicode.org/Public/idna/'; private const TEST_FILE = 'IdnaTestV2.txt'; private const CACHE_TTL = 86400 * 7; // 7 DAYS private const TEST_DATA_DIR = __DIR__ . DIRECTORY_SEPARATOR . 'data'; /** * @return array<int, array<int, string>> */ public function loadTestData( string $version ): array { $cache = new FilesystemAdapter( 'unicode-idna-test-data', self::CACHE_TTL, self::TEST_DATA_DIR ); $testData = $cache->getItem( $version ); if ( $testData->isHit() ) { return $testData->get(); } $client = new Client( [ 'base_uri' => self::BASE_URI, ] ); $response = $client->request( 'GET', $version . '/' . self::TEST_FILE ); if ( $response->getStatusCode() >= 400 ) { throw new RuntimeException( sprintf( 'Got status code %d trying to retrieve %s for Unicode version %s.', $response->getStatusCode(), self::TEST_FILE, $version ) ); } $data = $this->processResponse( $response ); $testData->set( $data ); $cache->save( $testData ); return $data; } /** * @return array<int, array<int, string>> */ private function processResponse( ResponseInterface $response ): array { $output = []; foreach ( explode( "\n", (string) $response->getBody() ) as $line ) { // Ignore empty lines and comments. if ( $line === '' || $line[0] === '#' ) { continue; } [ $data ] = explode( '#', $line ); $columns = array_map( '\trim', explode( ';', $data ) ); assert( count( $columns ) === 7 ); $output[] = $columns; } return $output; } /** * @param array<int, int|array<int, int>> $inherit * * @return array<int, int|array<int, int>> */ private function resolveErrorCodes( string $statusCodes, array $inherit, array $ignore ): array { if ( $statusCodes === '' ) { return $inherit; } if ( $statusCodes === '[]' ) { return []; } $matchCount = preg_match_all( '/[PVUABCX][0-9](?:_[0-9])?/', $statusCodes, $matches ); if ( preg_last_error() !== PREG_NO_ERROR ) { throw new RuntimeException(); } if ( $matchCount === 0 ) { throw new RuntimeException(); } $errors = []; foreach ( $matches[0] as $match ) { if ( $match[0] === 'U' || in_array( $match, $ignore, true ) ) { continue; } if ( ! isset( self::ERROR_CODE_MAP[ $match ] ) ) { throw new RuntimeException( sprintf( 'Unhandled error code %s.', $match ) ); } $errors[] = self::ERROR_CODE_MAP[ $match ]; } return $errors; } /** * @return array{ * 0: string, * 1: array<int, int|array<int, int>>, * 2: string, * 3: array<int, int|array<int, int>>, * 4: string, * 5: array<int, int|array<int, int>> * } */ public function translate( string $source, string $toUnicode, string $toUnicodeStatus, string $toAsciiN, string $toAsciiNStatus, string $toAsciiT, string $toAsciiTStatus, array $ignore = [] ): array { if ( $toUnicode === '' ) { $toUnicode = $source; } if ( $toAsciiN === '' ) { $toAsciiN = $toUnicode; } if ( $toAsciiT === '' ) { $toAsciiT = $toAsciiN; } $toUnicodeStatus = $this->resolveErrorCodes( $toUnicodeStatus, [], $ignore ); $toAsciiNStatus = $this->resolveErrorCodes( $toAsciiNStatus, $toUnicodeStatus, $ignore ); $toAsciiTStatus = $this->resolveErrorCodes( $toAsciiTStatus, $toAsciiNStatus, $ignore ); return [ $toUnicode, $toUnicodeStatus, $toAsciiN, $toAsciiNStatus, $toAsciiT, $toAsciiTStatus, ]; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/README.md 0000777 00000033513 15251156632 0020333 0 ustar 00 # IDNA [](https://github.com/TRowbotham/URL-Parser/blob/master/LICENSE) [](https://travis-ci.com/github/TRowbotham/idna) [](https://codecov.io/gh/TRowbotham/idna) [](https://packagist.org/packages/rowbot/idna) [](https://packagist.org/packages/rowbot/idna) A fully compliant implementation of [UTS#46](https://www.unicode.org/reports/tr46/), otherwise known as Unicode IDNA Compatibility Processing. You can read more about the differences between IDNA2003, IDNA2008, and UTS#46 in [Section 7. IDNA Comparison](https://www.unicode.org/reports/tr46/#IDNAComparison) of the specification. This library currently ships with Unicode 14.0.0 support and implements Version 14.0.0, Revision 27 of IDNA Compatibility Processing. It has the ability to use Unicode 11.0.0 to Unicode 14.0.0. While this library likely supports versions of Unicode less than 11.0.0, the format of the Unicode test files were changed beginning in 11.0.0 and as a result, versions of Unicode less than 11.0.0 have not been tested. - [Requirements](#requirements) - [Installation](#installation) - [API](#api) - [Error Codes](#error-codes) - [The WTFs of Unicode Support in PHP](#the-wtfs-of-unicode-support-in-php) - [FAQs](#faqs) - [Internals](#internals) ## Requirements - PHP 7.1+ - `rowbot/punycode` - `symfony/polyfill-intl-normalizer` ## Installation ```bash composer require rowbot/idna ``` ## API ### Idna::UNICODE_VERSION The Unicode version of the data files used, as a string. ### Idna::toAscii(string $domain, array $options = []): IdnaResult Converts a domain name to its ASCII form. Anytime an error is recorded while doing an ASCII transformation, the transformation is considered to have failed and whatever domain name string is returned is considered "garbage". What you do with that result is entirely up to you. #### toAscii Parameters - $domain - A domain name to convert to ASCII. - $options - An array of options for customizing the behavior of the transformation. Possible options include: - `"CheckBidi"` - Checks the domain name string for errors with bi-directional characters. Defaults to true. - `"CheckHyphens"` - Checks the domain name string for the positioning of hypens. Defaults to true. - `"CheckJoiners"` - Checks the domain name string for errors with joining code points. Defaults to true. - `"UseSTD3ASCIIRules"` - Disallows the use of ASCII characters other than `[a-zA-Z0-9-]`. Defaults to true. - `"Transitional_Processing"` - Whether transitional or non-transitional processing is used. When enabled, processing behaves more like IDNA2003 and when disabled behaves like IDNA2008. Defaults to false, which means that non-transitional processing is used by default. - `"VerifyDnsLength"` - Validates the length of the domain name string and it's individual labels. Defaults to true. **Note**: All options are case-sensitive. ```php use Rowbot\Idna\Idna; $result = Idna::toAscii('x-.xn--nxa'); // You must not use an ASCII domain that has errors. if ($result->hasErrors()) { throw new \Exception(); } echo $result->getDomain(); // x-.xn--nxa ``` ### Idna::toUnicode(string $domain, array $options = []): IdnaResult Converts the domain name to its Unicode form. Unlike the toAscii transformation, toUnicode does not have a failure concept. This means that you can always use the returned string. However, deciding what to do with the returned domain name string when an error is recorded is entirely up to you. - $domain - A domain name to convert to UTF-8. - $options - An array of options for customizing the behavior of the transformation. Possible options include: - `"CheckBidi"` - Checks the domain name string for errors with bi-directional characters. Defaults to true. - `"CheckHyphens"` - Checks the domain name string for the positioning of hypens. Defaults to true. - `"CheckJoiners"` - Checks the domain name string for errors with joining code points. Defaults to true. - `"UseSTD3ASCIIRules"` - Disallows the use of ASCII characters other than `[a-zA-Z0-9-]`. Defaults to true. - `"Transitional_Processing"` - Whether transitional or non-transitional processing is used. When enabled, processing behaves more like IDNA2003 and when disabled behaves like IDNA2008. Defaults to false, which means that non-transitional processing is used by default. **Note**: All options are case-sensitive. **Note**: `"VerifyDnsLength"` is not a valid option here. ```php use Rowbot\Idna\Idna; $result = Idna::toUnicode('xn---with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n.com'); echo $result->getDomain(); // 安室奈美恵-with-super-monkeys.com ``` ### IdnaResult object #### Members ##### IdnaResult::getDomain(): string Returns the transformed domain name string. ##### IdnaResult::getErrors(): int Returns a bitmask representing all errors that were recorded while processing the input domain name string. ##### IdnaResult::hasError(int $error): bool Returns whether or not a specific error was recorded. ##### IdnaResult::hasErrors(): bool Returns whether or not an error was recorded while processing the input domain name string. ##### IdnaResult::isTransitionalDifferent(): bool Returns `true` if the input domain name contains a code point that has a status of `"deviation"`. This status indicates that the code points are handled differently in IDNA2003 than they are in IDNA2008. At the time of writing, there are only 4 code points that have this status. They are U+00DF, U+03C2, U+200C, and U+200D. ## Error Codes - `Idna::ERROR_EMPTY_LABEL` The domain name or one of it's labels are an empty string. - `Idna::ERROR_LABEL_TOO_LONG` One of the domain's labels exceeds 63 bytes. - `Idna::ERROR_DOMAIN_NAME_TOO_LONG` The length of the domain name exceeds 253 bytes. - `Idna::ERROR_LEADING_HYPHEN` One of the domain name's labels starts with a hyphen-minus character (-). - `Idna::ERROR_TRAILING_HYPHEN` One of the domain name's labels ends with a hyphen-minus character (-). - `Idna::ERROR_HYPHEN_3_4` One of the domain name's labels contains a hyphen-minus character in the 3rd and 4th position. - `Idna::ERROR_LEADING_COMBINING_MARK` One of the domain name's labels starts with a combining mark. - `Idna::ERROR_DISALLOWED` The domain name contains characters that are disallowed. - `Idna::ERROR_PUNYCODE` One of the domain name's labels starts with "xn--", but is not valid punycode. - `Idna::ERROR_LABEL_HAS_DOT` One of the domain name's labels contains a full stop character (.). - `Idna::ERROR_INVALID_ACE_LABEL` One of the domain name's labels is an invalid ACE label. - `Idna::ERROR_BIDI` The domain name does not meet the BiDi requirements for IDNA. - `Idna::ERROR_CONTEXTJ` One of the domain name's labels does not meet the CONTEXTJ requirements for IDNA. ## The WTFs of Unicode Support in PHP In any given version of PHP, there can be a multitude of different versions of Unicode in use. So... WTF? - What does this mean? This means that if I ask the same question, each of the extensions listed below can give me a different answer. This is compounded by the fact that the versions of Unicode used in the below extensions can also be different given the same version of PHP. For example, the `intl` extension being used by my installation of PHP 7.2 could be using Unicode version 11, but the `intl` extension in your web hosts installation of PHP 7.2 could be using Unicode version 6. - How does this happen? - The `mbstring` extension uses its own version of Unicode. - The `Onigurama` library, which is behind `mbstring`'s regular expression functions, uses its own version of Unicode. - The `PCRE` extension, which is the primary extension for working with regular extensions in PHP, uses its own version of Unicode. - The `intl` extension uses its own version of Unicode. - Any other extensions that add their own versions of Unicode. - Userland libraries use their own version of Unicode (including this library). - This library Being able to use `mbstring` or `intl` extensions would be helpful, but we cannot depend on them being installed or them having a consistent version of Unicode when they are installed. Additionally, extensions like `PCRE` could be compiled without Unicode support entirely, though we do rely on `PCRE`'s `u` modifier. For this reason we have to include our own Unicode data. ## FAQs - **I'm confused! Is this IDNA2003 or IDNA2008?** The answer to this is somewhat convoluted. TL;DR; It is neither. Here is what the spec says: > To satisfy user expectations for mapping, and provide maximal compatibility with IDNA2003, this > document specifies a mapping for use with IDNA2008. In addition, to transition more smoothly to > IDNA2008, this document provides a Unicode algorithm for a standardized processing that allows > conformant implementations to minimize the security and interoperability problems caused by the > differences between IDNA2003 and IDNA2008. This Unicode IDNA Compatibility Processing is > structured according to IDNA2003 principles, but extends those principles to Unicode 5.2 and > later. It also incorporates the repertoire extensions provided by IDNA2008. More information can be found in [Section 2. Unicode IDNA Compatibility Processing](https://www.unicode.org/reports/tr46/#Compatibility_Processing) and [Section 7. IDNA Comparison](https://www.unicode.org/reports/tr46/#IDNAComparison). - **What are the recommended options?** The default options are the recommended options, which are also the strictest. ```php // Default options. [ 'CheckHyphens' => true, 'CheckBidi' => true, 'CheckJoiners' => true, 'UseSTD3ASCIIRules' => true, 'Transitional_Processing' => false, 'VerifyDnsLength' => true, // Only for Idna::toAscii() ]; ``` - **Do I have to provide all the options?** No. You only need to specifiy the options that you wish to change. Any option you specify will overwrite the default options. ```php use Rowbot\Idna\Idna; $result = Idna::toAscii('x-.xn--nxa', ['CheckHyphens' => true]); $result->hasErrors(); // true $result->hasError(Idna::ERROR_TRAILING_HYPHEN); // true $result = Idna::toAscii('x-.xn--nxa', ['CheckHyphens' => false]); $result->hasErrors(); // false $result->hasError(Idna::ERROR_TRAILING_HYPHEN); // false ``` - **What is the difference between `Transitional` and `Non-transitional` processing?** `Transitional` processing is designed to mimic IDNA2003. It is highly recommended to use `Non-transitional` processing, which tries to mimic IDNA2008. You can always check if a domain name would be different between the two processing modes by checking `IdnaResult::isTransitionalDifferent()`. - **Wouldn't it be neat if you also tested against the `idn_to_ascii()` and `idn_to_utf8()` functions from the `intl` extension?** Yes. Yes, it would be neat if we could do an additional check for parity with the ICU implementation, however, for the reasons outlined above in [The WTFs of Unicode Support in PHP](#the-wtfs-of-unicode-support-in-php), testing against these functions would be unreliable at best. - **Why does the `intl` extension show weird characters that look like diamonds with question marks inside in invalid domains, but your implementation doesn't?** ```php $input = '憡?Ⴔ.XN--1UG73GL146A'; idn_to_utf8($input, 0, IDNA_INTL_VARIANT_UTS46, $info); echo $info['result']; // 憡��.xn--1ug73gl146a� echo ($info['errors'] & IDNA_ERROR_DISALLOWED) !== 0; // true $result = \Rowbot\Idna\Idna::toUnicode($input); echo $result->getDomain(); // 憡?Ⴔ.xn--1ug73gl146a echo $result->hasError(\Rowbot\Idna\Idna::ERROR_DISALLOWED); // true ``` From [Section 4. Processing](https://www.unicode.org/reports/tr46/#Processing): > Implementations may make further modifications to the resulting Unicode string when showing it to > the user. For example, it is recommended that disallowed characters be replaced by a U+FFFD to > make them visible to the user. Similarly, labels that fail processing during steps 4 or 5 may be > marked by the insertion of a U+FFFD or other visual device. This implementation currently does not make these recommended modifications. ## Internals ### Building Unicode data files are fetched from https://www.unicode.org/Public. Currently, Unicode version 11.0.0-14.0.0 are supported. To change the version of Unicode that the library is built with, you must first change the value of the `\Rowbot\Idna::UNICODE_VERSION` constant, like so: ```diff class Idna { - public const UNICODE_VERSION = '13.0.0'; + public const UNICODE_VERSION = '14.0.0'; ``` Then to generate the necessary data files, you execute the following command: ```bash php bin/generateDataFiles.php ``` If no assertions or exceptions have occured, then you have successfully changed the Unicode version. You should now execute the tests to make sure everything is good to go. The tests will automatically fetch the version appropriate tests as the test files are not generated by the above command. ```bash vendor/bin/phpunit ``` php-toolkit/DataLiberation/vendor-patched/rowbot/idna/CHANGELOG.md 0000777 00000001301 15251156632 0020653 0 ustar 00 # Changelog ## [Unreleased] ## [0.1.5] - 2022-01-10 ### Added - Add `UNICODE_VERSION` constant to `Idna` class that returns a string with the Unicode version of the data files used ### Changed - Update data files to Unicode 14 - Update PHPStan to 1.0 ## [0.1.4] - 2021-02-11 ### Added - Test on PHP 8 release ### Fixed - Handle the rare possibility that string nomalization can fail ## [0.1.3] - 2020-08-03 ### Fixed - Fix bug with domain names containing a URL delimiter ## [0.1.2] - 2020-07-15 ### Added - Performance improvements ## [0.1.1] - 2020-07-10 ### Added - Performance improvements - Reduced memory footprint ## [0.1.0] - 2020-06-10 - Initial release php-toolkit/DataLiberation/vendor-patched/rowbot/idna/bin/generateDataFiles.php 0000777 00000000726 15251156632 0023704 0 ustar 00 <?php declare( strict_types=1 ); use Rowbot\Idna\Bin\IdnaDataBuilder; use Rowbot\Idna\Bin\RegexBuilder; use Rowbot\Idna\Bin\ViramaDataBuilder; const DS = DIRECTORY_SEPARATOR; const ROOT_DIR = __DIR__ . DS . '..'; const OUTPUT_DIR = ROOT_DIR . DS . 'resources'; require ROOT_DIR . DS . 'vendor' . DS . 'autoload.php'; ViramaDataBuilder::buildHashMap( OUTPUT_DIR ); RegexBuilder::buildRegexClass( OUTPUT_DIR ); IdnaDataBuilder::buildHashMaps( OUTPUT_DIR ); php-toolkit/DataLiberation/vendor-patched/rowbot/idna/bin/IdnaDataBuilder.php 0000777 00000006552 15251156632 0023314 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna\Bin; use Rowbot\Idna\CodePoint; use RuntimeException; use function array_map; use function explode; use function fclose; use function fgets; use function file_put_contents; use function intval; use function preg_match_all; use const DIRECTORY_SEPARATOR as DS; class IdnaDataBuilder extends Builder { public static function buildHashMaps( string $output ): void { $handle = self::getIdnaDataResource( 'IdnaMappingTable.txt' ); $statuses = [ 'mapped' => [], 'ignored' => [], 'deviation' => [], 'disallowed' => [], 'disallowed_STD3_mapped' => [], 'disallowed_STD3_valid' => [], ]; $rangeFallback = ''; while ( ( $line = fgets( $handle ) ) !== false ) { if ( $line === "\n" || $line[0] === '#' ) { continue; } [ $data ] = explode( '#', $line ); $data = array_map( 'trim', explode( ';', $data ) ); [ $codePoints, $status ] = $data; $codePoints = self::parseCodePoints( $codePoints ); $diff = $codePoints[1] - $codePoints[0] + 1; switch ( $status ) { case 'valid': // skip valid. break; case 'mapped': case 'deviation': case 'disallowed_STD3_mapped': if ( preg_match_all( '/[[:xdigit:]]+/', $data[2], $matches ) === false ) { throw new RuntimeException(); } $mapped = ''; foreach ( $matches[0] as $codePoint ) { $mapped .= CodePoint::encode( intval( $codePoint, 16 ) ); } for ( $i = 0; $i < $diff; ++ $i ) { $statuses[ $status ][ $codePoints[0] + $i ] = $mapped; } break; case 'disallowed': if ( $diff > 30 ) { if ( $rangeFallback !== '' ) { $rangeFallback .= "\n\n"; } $rangeFallback .= <<<RANGE_FALLBACK if (\$codePoint >= {$codePoints[0]} && \$codePoint <= {$codePoints[1]}) { return true; } RANGE_FALLBACK; continue 2; } for ( $i = 0; $i < $diff; ++ $i ) { $statuses[ $status ][ $codePoints[0] + $i ] = true; } break; case 'ignored': case 'disallowed_STD3_valid': for ( $i = 0; $i < $diff; ++ $i ) { $statuses[ $status ][ $codePoints[0] + $i ] = true; } break; } } fclose( $handle ); file_put_contents( $output . DS . 'mapped.php', "<?php\n\nreturn " . var_export( $statuses['mapped'], true ) . ";\n" ); file_put_contents( $output . DS . 'ignored.php', "<?php\n\nreturn " . var_export( $statuses['ignored'], true ) . ";\n" ); file_put_contents( $output . DS . 'deviation.php', "<?php\n\nreturn " . var_export( $statuses['deviation'], true ) . ";\n" ); file_put_contents( $output . DS . 'disallowed.php', "<?php\n\nreturn " . var_export( $statuses['disallowed'], true ) . ";\n" ); file_put_contents( $output . DS . 'disallowed_STD3_mapped.php', "<?php\n\nreturn " . var_export( $statuses['disallowed_STD3_mapped'], true ) . ";\n" ); file_put_contents( $output . DS . 'disallowed_STD3_valid.php', "<?php\n\nreturn " . var_export( $statuses['disallowed_STD3_valid'], true ) . ";\n" ); $s = <<<CP_STATUS <?php declare(strict_types=1); namespace Rowbot\Idna\Resource; final class DisallowedRanges { public static function inRange(int \$codePoint): bool { {$rangeFallback} return false; } } CP_STATUS; file_put_contents( $output . DS . 'DisallowedRanges.php', $s ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/bin/Builder.php 0000777 00000003626 15251156632 0021725 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna\Bin; use Rowbot\Idna\Idna; use RuntimeException; use function array_map; use function explode; use function fclose; use function fopen; use function intval; use function sprintf; use function usort; abstract class Builder { protected const BASE_URL = 'https://www.unicode.org/Public'; /** * @return resource */ protected static function getIdnaDataResource( string $file ) { $file = sprintf( '%s/idna/%s/%s', self::BASE_URL, Idna::UNICODE_VERSION, $file ); $handle = fopen( $file, 'r' ); if ( $handle === false ) { throw new RuntimeException( 'Failed to open ' . $file ); } return $handle; } /** * @return resource */ protected static function getUnicodeDataResource( string $file ) { $file = sprintf( '%s/%s/ucd/%s', self::BASE_URL, Idna::UNICODE_VERSION, $file ); $handle = fopen( $file, 'r' ); if ( $handle === false ) { throw new RuntimeException( 'Failed to open ' . $file ); } return $handle; } /** * @return list<int> */ protected static function parseCodePoints( string $codePoints ): array { $range = explode( '..', $codePoints ); $start = intval( $range[0], 16 ); $end = isset( $range[1] ) ? intval( $range[1], 16 ) : $start; return [ $start, $end ]; } /** * @return array<int, array<int, array<int, int>|string>> */ protected static function parseProperties( string $file ): array { $handle = self::getUnicodeDataResource( $file ); $retVal = []; while ( ( $line = fgets( $handle ) ) !== false ) { if ( $line === "\n" || $line[0] === '#' ) { continue; } [ $data ] = explode( '#', $line ); $data = array_map( 'trim', explode( ';', $data ) ); $data[0] = self::parseCodePoints( $data[0] ); $retVal[] = $data; } fclose( $handle ); usort( $retVal, static function ( array $a, array $b ): int { return $a[0][0] <=> $b[0][0]; } ); return $retVal; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/bin/RegexBuilder.php 0000777 00000015765 15251156632 0022727 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna\Bin; use function array_filter; use function assert; use function file_put_contents; use function in_array; use function is_array; use function sprintf; use const DIRECTORY_SEPARATOR as DS; class RegexBuilder extends Builder { public static function buildRegexClass( string $output ): void { $bidiData = self::parseProperties( 'extracted/DerivedBidiClass.txt' ); $rtlLabel = sprintf( '/[%s]/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return in_array( $data[1], [ 'R', 'AL', 'AN' ], true ); } ) ) ); // Step 1. The first character must be a character with Bidi property L, R, or AL. If it has the R // or AL property, it is an RTL label; if it has the L property, it is an LTR label. // // Because any code point not explicitly listed in DerivedBidiClass.txt is considered to have the // 'L' property, we negate a character class matching all code points explicitly listed in // DerivedBidiClass.txt minus the ones explicitly marked as 'L'. $bidiStep1Ltr = sprintf( '/^[^%s]/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return $data[1] !== 'L'; } ) ) ); $bidiStep1Rtl = sprintf( '/^[%s]/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return in_array( $data[1], [ 'R', 'AL' ], true ); } ) ) ); // Step 2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, CS, ET, ON, // BN, or NSM are allowed. $bidiStep2 = sprintf( '/[^%s]/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return in_array( $data[1], [ 'R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM' ], true ); } ) ) ); // Step 3. In an RTL label, the end of the label must be a character with Bidi property R, AL, EN, // or AN, followed by zero or more characters with Bidi property NSM. $bidiStep3 = sprintf( '/[%s][%s]*$/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return in_array( $data[1], [ 'R', 'AL', 'EN', 'AN' ], true ); } ) ), self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return $data[1] === 'NSM'; } ) ) ); // Step 4. In an RTL label, if an EN is present, no AN may be present, and vice versa. $bidiStep4EN = sprintf( '/[%s]/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return $data[1] === 'EN'; } ) ) ); $bidiStep4AN = sprintf( '/[%s]/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return $data[1] === 'AN'; } ) ) ); // Step 5. In an LTR label, only characters with the Bidi properties L, EN, ES, CS, ET, ON, BN, or // NSM are allowed. // // Because any code point not explicitly listed in DerivedBidiClass.txt is considered to have the // 'L' property, we create a character class matching all code points explicitly listed in // DerivedBidiClass.txt minus the ones explicitly marked as 'L', 'EN', 'ES', 'CS', 'ET', 'ON', // 'BN', or 'NSM'. $bidiStep5 = sprintf( '/[%s]/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return ! in_array( $data[1], [ 'L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM' ], true ); } ) ) ); // Step 6. In an LTR label, the end of the label must be a character with Bidi property L or EN, // followed by zero or more characters with Bidi property NSM. // // Again, because any code point not explicitly listed in DerivedBidiClass.txt is considered to // have the 'L' property, we negate a character class matching all code points explicitly listed in // DerivedBidiClass.txt to match characters with the 'L' and 'EN' property. $bidiStep6 = sprintf( '/[^%s][%s]*$/u', self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return ! in_array( $data[1], [ 'L', 'EN' ], true ); } ) ), self::buildCharacterClass( array_filter( $bidiData, static function ( array $data ): bool { return $data[1] === 'NSM'; } ) ) ); $combiningMarks = self::buildCombiningMarksRegex(); $zwnj = self::buildJoiningTypesRegex(); $s = <<<RegexClass <?php // This file was auto generated by running 'php bin/generateDataFiles.php' declare(strict_types=1); namespace Rowbot\Idna\Resource; final class Regex { public const COMBINING_MARK = '{$combiningMarks}'; public const RTL_LABEL = '{$rtlLabel}'; public const BIDI_STEP_1_LTR = '{$bidiStep1Ltr}'; public const BIDI_STEP_1_RTL = '{$bidiStep1Rtl}'; public const BIDI_STEP_2 = '{$bidiStep2}'; public const BIDI_STEP_3 = '{$bidiStep3}'; public const BIDI_STEP_4_AN = '{$bidiStep4AN}'; public const BIDI_STEP_4_EN = '{$bidiStep4EN}'; public const BIDI_STEP_5 = '{$bidiStep5}'; public const BIDI_STEP_6 = '{$bidiStep6}'; public const ZWNJ = '{$zwnj}'; /** * @codeCoverageIgnore */ private function __construct() { } } RegexClass; file_put_contents( $output . DS . 'Regex.php', $s ); } private static function buildJoiningTypesRegex(): string { $joiningTypes = self::parseProperties( 'extracted/DerivedJoiningType.txt' ); // ((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) // We use a capturing group around the first portion of the regex so we can count the byte length // of the match and increment preg_match's offset accordingly. return sprintf( '/([%1$s%2$s][%3$s]*\x{200C}[%3$s]*)[%4$s%2$s]/u', self::buildCharacterClass( array_filter( $joiningTypes, static function ( array $data ): bool { return $data[1] === 'L'; } ) ), self::buildCharacterClass( array_filter( $joiningTypes, static function ( array $data ): bool { return $data[1] === 'D'; } ) ), self::buildCharacterClass( array_filter( $joiningTypes, static function ( array $data ): bool { return $data[1] === 'T'; } ) ), self::buildCharacterClass( array_filter( $joiningTypes, static function ( array $data ): bool { return $data[1] === 'R'; } ) ) ); } private static function buildCombiningMarksRegex(): string { $generalCategories = self::parseProperties( 'extracted/DerivedGeneralCategory.txt' ); return sprintf( '/^[%s]/u', self::buildCharacterClass( array_filter( $generalCategories, static function ( array $data ): bool { return in_array( $data[1], [ 'Mc', 'Me', 'Mn' ], true ); } ) ) ); } /** * @param array<int, array<int, array<int, int>|string>> $data */ private static function buildCharacterClass( array $data ): string { $out = ''; foreach ( $data as $codePoints ) { assert( is_array( $codePoints[0] ) ); if ( $codePoints[0][0] !== $codePoints[0][1] ) { $out .= sprintf( '\x{%04X}-\x{%04X}', ...$codePoints[0] ); continue; } $out .= sprintf( '\x{%04X}', $codePoints[0][0] ); } return $out; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/bin/ViramaDataBuilder.php 0000777 00000002141 15251156632 0023646 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna\Bin; use function array_map; use function explode; use function fclose; use function fgets; use function file_put_contents; use function var_export; use const DIRECTORY_SEPARATOR as DS; class ViramaDataBuilder extends Builder { public static function buildHashMap( string $output ): void { $handle = self::getUnicodeDataResource( 'extracted/DerivedCombiningClass.txt' ); $virama = []; while ( ( $line = fgets( $handle ) ) !== false ) { if ( $line === "\n" || $line[0] === '#' ) { continue; } [ $data ] = explode( '#', $line ); $data = array_map( 'trim', explode( ';', $data ) ); [ $codePoints, $combiningClass ] = $data; if ( $combiningClass !== '9' ) { continue; } $codePoints = self::parseCodePoints( $codePoints ); $diff = $codePoints[1] - $codePoints[0] + 1; for ( $i = 0; $i < $diff; ++ $i ) { $virama[ $codePoints[0] + $i ] = (int) $combiningClass; } } fclose( $handle ); file_put_contents( $output . DS . 'virama.php', "<?php\n\nreturn " . var_export( $virama, true ) . ";\n" ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/src/LabelValidator.php 0000777 00000014742 15251156632 0023244 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna; use Normalizer; use Rowbot\Idna\Resource\Regex; use function preg_match; use function strlen; use function strpos; use const DIRECTORY_SEPARATOR as DS; use const PREG_OFFSET_CAPTURE; class LabelValidator { /** * @var DomainInfo */ protected $info; /** * @var array<int, int> */ protected static $virama = []; public function __construct( DomainInfo $info ) { $this->info = $info; if ( self::$virama === [] ) { self::$virama = require __DIR__ . DS . '..' . DS . 'resources' . DS . 'virama.php'; } } /** * @param list<int> $codePoints */ protected function isValidContextJ( string $label, array $codePoints ): bool { $offset = 0; foreach ( $codePoints as $i => $codePoint ) { if ( $codePoint !== 0x200C && $codePoint !== 0x200D ) { continue; } $prev = $i - 1; if ( ! isset( $codePoints[ $prev ] ) ) { return false; } // If Canonical_Combining_Class(Before(cp)) .eq. Virama Then True; if ( isset( self::$virama[ $codePoints[ $prev ] ] ) ) { continue; } // If RegExpMatch((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) Then // True; // Generated RegExp = ([Joining_Type:{L,D}][Joining_Type:T]*\u200C[Joining_Type:T]*)[Joining_Type:{R,D}] if ( $codePoint === 0x200C && preg_match( Regex::ZWNJ, $label, $matches, PREG_OFFSET_CAPTURE, $offset ) === 1 ) { $offset += strlen( $matches[1][0] ); continue; } return false; } return true; } /** * @see https://www.unicode.org/reports/tr46/#Validity_Criteria * * @param array<string, bool> $options */ public function validate( string $label, array $options, bool $canBeEmpty ): void { if ( $label === '' ) { if ( ! $canBeEmpty && ( ! isset( $options['VerifyDnsLength'] ) || $options['VerifyDnsLength'] ) ) { $this->info->addError( Idna::ERROR_EMPTY_LABEL ); } return; } $codePoints = CodePoint::utf8Decode( $label ); // Step 1. The label must be in Unicode Normalization Form C. if ( ! Normalizer::isNormalized( $label, Normalizer::FORM_C ) ) { $this->info->addError( Idna::ERROR_INVALID_ACE_LABEL ); } if ( $options['CheckHyphens'] ) { // Step 2. If CheckHyphens, the label must not contain a U+002D HYPHEN-MINUS character // in both the thrid and fourth positions. if ( isset( $codePoints[3] ) && $codePoints[2] === 0x2D && $codePoints[3] === 0x2D ) { $this->info->addError( Idna::ERROR_HYPHEN_3_4 ); } // Step 3. If CheckHyphens, the label must neither begin nor end with a U+002D // HYPHEN-MINUS character. if ( strncmp( $label, '-', 1 ) === 0 ) { $this->info->addError( Idna::ERROR_LEADING_HYPHEN ); } if ( substr_compare( $label, '-', - 1 ) === 0 ) { $this->info->addError( Idna::ERROR_TRAILING_HYPHEN ); } } // Step 4. The label must not contain a U+002E (.) FULL STOP. if ( strpos( $label, '.' ) !== false ) { $this->info->addError( Idna::ERROR_LABEL_HAS_DOT ); } // Step 5. The label must not begin with a combining mark, that is: General_Category=Mark. if ( preg_match( Regex::COMBINING_MARK, $label, $matches ) === 1 ) { $this->info->addError( Idna::ERROR_LEADING_COMBINING_MARK ); } // Step 6. Each code point in the label must only have certain status values according to // Section 5, IDNA Mapping Table: $transitional = $options['Transitional_Processing']; $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; foreach ( $codePoints as $codePoint ) { $data = CodePointStatus::lookup( $codePoint, $useSTD3ASCIIRules ); $status = $data['status']; if ( $status === 'valid' || ( ! $transitional && $status === 'deviation' ) ) { continue; } $this->info->addError( Idna::ERROR_DISALLOWED ); break; } // Step 7. If CheckJoiners, the label must satisify the ContextJ rules from Appendix A, in // The Unicode Code Points and Internationalized Domain Names for Applications (IDNA) // [IDNA2008]. if ( $options['CheckJoiners'] && ! $this->isValidContextJ( $label, $codePoints ) ) { $this->info->addError( Idna::ERROR_CONTEXTJ ); } // Step 8. If CheckBidi, and if the domain name is a Bidi domain name, then the label must // satisfy all six of the numbered conditions in [IDNA2008] RFC 5893, Section 2. if ( $options['CheckBidi'] && ( ! $this->info->isBidiDomain() || $this->info->isValidBidiDomain() ) ) { $this->validateBidi( $label ); } } /** * @see https://tools.ietf.org/html/rfc5893#section-2 */ protected function validateBidi( string $label ): void { if ( preg_match( Regex::RTL_LABEL, $label ) === 1 ) { $this->info->setBidiDomain(); // Step 1. The first character must be a character with Bidi property L, R, or AL. // If it has the R or AL property, it is an RTL label if ( preg_match( Regex::BIDI_STEP_1_RTL, $label ) !== 1 ) { $this->info->setInvalidBidiDomain(); return; } // Step 2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, // CS, ET, ON, BN, or NSM are allowed. if ( preg_match( Regex::BIDI_STEP_2, $label ) === 1 ) { $this->info->setInvalidBidiDomain(); return; } // Step 3. In an RTL label, the end of the label must be a character with Bidi property // R, AL, EN, or AN, followed by zero or more characters with Bidi property NSM. if ( preg_match( Regex::BIDI_STEP_3, $label ) !== 1 ) { $this->info->setInvalidBidiDomain(); return; } // Step 4. In an RTL label, if an EN is present, no AN may be present, and vice versa. if ( preg_match( Regex::BIDI_STEP_4_AN, $label ) === 1 && preg_match( Regex::BIDI_STEP_4_EN, $label ) === 1 ) { $this->info->setInvalidBidiDomain(); return; } return; } // We are a LTR label // Step 1. The first character must be a character with Bidi property L, R, or AL. // If it has the L property, it is an LTR label. if ( preg_match( Regex::BIDI_STEP_1_LTR, $label ) !== 1 ) { $this->info->setInvalidBidiDomain(); return; } // Step 5. In an LTR label, only characters with the Bidi properties L, EN, // ES, CS, ET, ON, BN, or NSM are allowed. if ( preg_match( Regex::BIDI_STEP_5, $label ) === 1 ) { $this->info->setInvalidBidiDomain(); return; } // Step 6.In an LTR label, the end of the label must be a character with Bidi property L or // EN, followed by zero or more characters with Bidi property NSM. if ( preg_match( Regex::BIDI_STEP_6, $label ) !== 1 ) { $this->info->setInvalidBidiDomain(); return; } } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/src/IdnaResult.php 0000777 00000001526 15251156632 0022425 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna; class IdnaResult { /** * @var string */ protected $domain; /** * @var int */ protected $errors; /** * @var bool */ protected $transitionalDifferent; public function __construct( string $domain, DomainInfo $info ) { $this->domain = $domain; $this->errors = $info->getErrors(); $this->transitionalDifferent = $info->isTransitionalDifferent(); } public function getDomain(): string { return $this->domain; } public function getErrors(): int { return $this->errors; } public function hasError( int $error ): bool { return ( $this->errors & $error ) !== 0; } public function hasErrors(): bool { return $this->errors !== 0; } public function isTransitionalDifferent(): bool { return $this->transitionalDifferent; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/src/CodePoint.php 0000777 00000005774 15251156632 0022250 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna; use function chr; use function ord; use function strlen; final class CodePoint { /** * Takes a Unicode code point and encodes it. The return behavior is undefined if the given * code point is outside the range 0..10FFFF. * * @see https://encoding.spec.whatwg.org/#utf-8-encoder */ public static function encode( int $codePoint ): string { if ( $codePoint >= 0x00 && $codePoint <= 0x7F ) { return chr( $codePoint ); } $count = 0; $offset = 0; if ( $codePoint >= 0x0080 && $codePoint <= 0x07FF ) { $count = 1; $offset = 0xC0; } elseif ( $codePoint >= 0x0800 && $codePoint <= 0xFFFF ) { $count = 2; $offset = 0xE0; } elseif ( $codePoint >= 0x10000 && $codePoint <= 0x10FFFF ) { $count = 3; $offset = 0xF0; } $bytes = chr( ( $codePoint >> ( 6 * $count ) ) + $offset ); while ( $count > 0 ) { $temp = $codePoint >> ( 6 * ( $count - 1 ) ); $bytes .= chr( 0x80 | ( $temp & 0x3F ) ); -- $count; } return $bytes; } /** * Takes a UTF-8 encoded string and converts it into a series of integer code points. Any * invalid byte sequences will be replaced by a U+FFFD replacement code point. * * @see https://encoding.spec.whatwg.org/#utf-8-decoder * * @return list<int> */ public static function utf8Decode( string $input ): array { $bytesSeen = 0; $bytesNeeded = 0; $lowerBoundary = 0x80; $upperBoundary = 0xBF; $codePoint = 0; $codePoints = []; $length = strlen( $input ); for ( $i = 0; $i < $length; ++ $i ) { $byte = ord( $input[ $i ] ); if ( $bytesNeeded === 0 ) { if ( $byte >= 0x00 && $byte <= 0x7F ) { $codePoints[] = $byte; continue; } if ( $byte >= 0xC2 && $byte <= 0xDF ) { $bytesNeeded = 1; $codePoint = $byte & 0x1F; } elseif ( $byte >= 0xE0 && $byte <= 0xEF ) { if ( $byte === 0xE0 ) { $lowerBoundary = 0xA0; } elseif ( $byte === 0xED ) { $upperBoundary = 0x9F; } $bytesNeeded = 2; $codePoint = $byte & 0xF; } elseif ( $byte >= 0xF0 && $byte <= 0xF4 ) { if ( $byte === 0xF0 ) { $lowerBoundary = 0x90; } elseif ( $byte === 0xF4 ) { $upperBoundary = 0x8F; } $bytesNeeded = 3; $codePoint = $byte & 0x7; } else { $codePoints[] = 0xFFFD; } continue; } if ( $byte < $lowerBoundary || $byte > $upperBoundary ) { $codePoint = 0; $bytesNeeded = 0; $bytesSeen = 0; $lowerBoundary = 0x80; $upperBoundary = 0xBF; -- $i; $codePoints[] = 0xFFFD; continue; } $lowerBoundary = 0x80; $upperBoundary = 0xBF; $codePoint = ( $codePoint << 6 ) | ( $byte & 0x3F ); if ( ++ $bytesSeen !== $bytesNeeded ) { continue; } $codePoints[] = $codePoint; $codePoint = 0; $bytesNeeded = 0; $bytesSeen = 0; } // String unexpectedly ended, so append a U+FFFD code point. if ( $bytesNeeded !== 0 ) { $codePoints[] = 0xFFFD; } return $codePoints; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/src/Idna.php 0000777 00000017044 15251156632 0021230 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna; use Normalizer; use Rowbot\Punycode\Exception\PunycodeException; use Rowbot\Punycode\Punycode; use function array_merge; use function count; use function explode; use function implode; use function preg_match; use function strlen; use function strncmp; use function substr; /** * @see https://www.unicode.org/reports/tr46/ */ final class Idna { public const UNICODE_VERSION = '14.0.0'; private const DEFAULT_UNICODE_OPTIONS = [ 'CheckHyphens' => true, 'CheckBidi' => true, 'CheckJoiners' => true, 'UseSTD3ASCIIRules' => true, 'Transitional_Processing' => false, ]; private const DEFAULT_ASCII_OPTIONS = self::DEFAULT_UNICODE_OPTIONS + [ 'VerifyDnsLength' => true, ]; public const ERROR_EMPTY_LABEL = 1; public const ERROR_LABEL_TOO_LONG = 2; public const ERROR_DOMAIN_NAME_TOO_LONG = 4; public const ERROR_LEADING_HYPHEN = 8; public const ERROR_TRAILING_HYPHEN = 0x10; public const ERROR_HYPHEN_3_4 = 0x20; public const ERROR_LEADING_COMBINING_MARK = 0x40; public const ERROR_DISALLOWED = 0x80; public const ERROR_PUNYCODE = 0x100; public const ERROR_LABEL_HAS_DOT = 0x200; public const ERROR_INVALID_ACE_LABEL = 0x400; public const ERROR_BIDI = 0x800; public const ERROR_CONTEXTJ = 0x1000; public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; public const ERROR_CONTEXTO_DIGITS = 0x4000; private const MAX_DOMAIN_SIZE = 253; private const MAX_LABEL_SIZE = 63; /** * @codeCoverageIgnore */ private function __construct() { } /** * @see https://www.unicode.org/reports/tr46/#ProcessingStepMap * * @param array<string, bool> $options */ private static function mapCodePoints( string $domain, array $options, DomainInfo $info ): string { $str = ''; $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; $transitional = $options['Transitional_Processing']; $errors = 0; $transitionalDifferent = false; foreach ( CodePoint::utf8Decode( $domain ) as $codePoint ) { $data = CodePointStatus::lookup( $codePoint, $useSTD3ASCIIRules ); switch ( $data['status'] ) { case 'disallowed': $errors |= Idna::ERROR_DISALLOWED; // no break. case 'valid': $str .= CodePoint::encode( $codePoint ); break; case 'ignored': // Do nothing. break; case 'mapped': $str .= $data['mapping']; break; case 'deviation': $transitionalDifferent = true; $str .= ( $transitional ? $data['mapping'] : CodePoint::encode( $codePoint ) ); break; } } $info->addError( $errors ); if ( $transitionalDifferent ) { $info->setTransitionalDifferent(); } return $str; } /** * @see https://www.unicode.org/reports/tr46/#Processing * * @param array<string, bool> $options * * @return list<string> */ private static function process( string $domain, array $options, DomainInfo $info ): array { // If VerifyDnsLength is not set, we are doing ToUnicode otherwise we are doing ToASCII and // we need to respect the VerifyDnsLength option. $checkForEmptyLabels = ! isset( $options['VerifyDnsLength'] ) || $options['VerifyDnsLength']; if ( $checkForEmptyLabels && $domain === '' ) { $info->addError( self::ERROR_EMPTY_LABEL ); return [ '' ]; } // Step 1. Map each code point in the domain name string $domain = self::mapCodePoints( $domain, $options, $info ); // Step 2. Normalize the domain name string to Unicode Normalization Form C. if ( ! Normalizer::isNormalized( $domain, Normalizer::FORM_C ) ) { $originalDomain = $domain; $domain = Normalizer::normalize( $domain, Normalizer::FORM_C ); // This shouldn't be possible since the input string is run through the UTF-8 decoder // when mapping the code points above, but lets account for it anyway. if ( $domain === false ) { $info->addError( self::ERROR_INVALID_ACE_LABEL ); $domain = $originalDomain; } unset( $originalDomain ); } // Step 3. Break the string into labels at U+002E (.) FULL STOP. $labels = explode( '.', $domain ); $lastLabelIndex = count( $labels ) - 1; $validator = new LabelValidator( $info ); // Step 4. Convert and validate each label in the domain name string. foreach ( $labels as $i => $label ) { $validationOptions = $options; if ( strncmp( $label, 'xn--', 4 ) === 0 ) { try { $label = Punycode::decode( substr( $label, 4 ) ); } catch ( PunycodeException $e ) { $info->addError( self::ERROR_PUNYCODE ); continue; } $validationOptions['Transitional_Processing'] = false; $labels[ $i ] = $label; } $validator->validate( $label, $validationOptions, $i > 0 && $i === $lastLabelIndex ); } if ( $info->isBidiDomain() && ! $info->isValidBidiDomain() ) { $info->addError( self::ERROR_BIDI ); } // Any input domain name string that does not record an error has been successfully // processed according to this specification. Conversely, if an input domain_name string // causes an error, then the processing of the input domain_name string fails. Determining // what to do with error input is up to the caller, and not in the scope of this document. return $labels; } /** * @see https://www.unicode.org/reports/tr46/#ToASCII * * @param array<string, bool> $options */ public static function toAscii( string $domain, array $options = [] ): IdnaResult { $options = array_merge( self::DEFAULT_ASCII_OPTIONS, $options ); $info = new DomainInfo(); $labels = self::process( $domain, $options, $info ); foreach ( $labels as $i => $label ) { // Only convert labels to punycode that contain non-ASCII code points if ( preg_match( '/[^\x00-\x7F]/', $label ) === 1 ) { try { $label = 'xn--' . Punycode::encode( $label ); } catch ( PunycodeException $e ) { $info->addError( self::ERROR_PUNYCODE ); } $labels[ $i ] = $label; } } if ( $options['VerifyDnsLength'] ) { self::validateDomainAndLabelLength( $labels, $info ); } return new IdnaResult( implode( '.', $labels ), $info ); } /** * @see https://www.unicode.org/reports/tr46/#ToUnicode * * @param array<string, bool> $options */ public static function toUnicode( string $domain, array $options = [] ): IdnaResult { // VerifyDnsLength is not a valid option for toUnicode, so remove it if it exists. unset( $options['VerifyDnsLength'] ); $options = array_merge( self::DEFAULT_UNICODE_OPTIONS, $options ); $info = new DomainInfo(); $labels = self::process( $domain, $options, $info ); return new IdnaResult( implode( '.', $labels ), $info ); } /** * @param list<string> $labels */ private static function validateDomainAndLabelLength( array $labels, DomainInfo $info ): void { $maxDomainSize = self::MAX_DOMAIN_SIZE; $length = count( $labels ); $totalLength = $length - 1; // If the last label is empty and it is not the first label, then it is the root label. // Increase the max size by 1, making it 254, to account for the root label's "." // delimiter. This also means we don't need to check the last label's length for being too // long. if ( $length > 1 && $labels[ $length - 1 ] === '' ) { ++ $maxDomainSize; -- $length; } for ( $i = 0; $i < $length; ++ $i ) { $bytes = strlen( $labels[ $i ] ); $totalLength += $bytes; if ( $bytes > self::MAX_LABEL_SIZE ) { $info->addError( self::ERROR_LABEL_TOO_LONG ); } } if ( $totalLength > $maxDomainSize ) { $info->addError( self::ERROR_DOMAIN_NAME_TOO_LONG ); } } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/src/DomainInfo.php 0000777 00000002537 15251156632 0022401 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna; class DomainInfo { /** * @var bool */ protected $bidiDomain; /** * @var int */ protected $errors; /** * @var bool */ protected $validBidiDomain; /** * @var bool */ protected $transitionalDifferent; public function __construct() { $this->bidiDomain = false; $this->errors = 0; $this->transitionalDifferent = false; $this->validBidiDomain = true; } public function addError( int $errors ): void { $this->errors |= $errors; } public function getErrors(): int { return $this->errors; } /** * A Bidi domain name is a domain name containing at least one character with Bidi_Class R, AL, or AN. * * @see https://www.unicode.org/reports/tr46/#Notation * @see https://www.unicode.org/reports/tr9/#Bidirectional_Character_Types */ public function isBidiDomain(): bool { return $this->bidiDomain; } public function isTransitionalDifferent(): bool { return $this->transitionalDifferent; } public function isValidBidiDomain(): bool { return $this->validBidiDomain; } public function setBidiDomain(): void { $this->bidiDomain = true; } public function setInvalidBidiDomain(): void { $this->validBidiDomain = false; } public function setTransitionalDifferent(): void { $this->transitionalDifferent = true; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/src/CodePointStatus.php 0000777 00000004740 15251156632 0023444 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna; use Rowbot\Idna\Resource\DisallowedRanges; use const DIRECTORY_SEPARATOR as DS; final class CodePointStatus { private const RESOURCE_DIR = __DIR__ . DS . '..' . DS . 'resources' . DS; /** * @var array<int, string> */ private static $mapped; /** * @var array<int, bool> */ private static $ignored; /** * @var array<int, string> */ private static $deviation; /** * @var array<int, bool> */ private static $disallowed; /** * @var array<int, string> */ private static $disallowed_STD3_mapped; /** * @var array<int, bool> */ private static $disallowed_STD3_valid; /** * @var bool */ private static $dataLoaded = false; /** * @codeCoverageIgnore */ private function __construct() { } /** * @return array{status: string, mapping?: string} */ public static function lookup( int $codePoint, bool $useSTD3ASCIIRules ): array { if ( ! self::$dataLoaded ) { self::$dataLoaded = true; self::$mapped = require self::RESOURCE_DIR . 'mapped.php'; self::$ignored = require self::RESOURCE_DIR . 'ignored.php'; self::$deviation = require self::RESOURCE_DIR . 'deviation.php'; self::$disallowed = require self::RESOURCE_DIR . 'disallowed.php'; self::$disallowed_STD3_mapped = require self::RESOURCE_DIR . 'disallowed_STD3_mapped.php'; self::$disallowed_STD3_valid = require self::RESOURCE_DIR . 'disallowed_STD3_valid.php'; } if ( isset( self::$mapped[ $codePoint ] ) ) { return [ 'status' => 'mapped', 'mapping' => self::$mapped[ $codePoint ] ]; } if ( isset( self::$ignored[ $codePoint ] ) ) { return [ 'status' => 'ignored' ]; } if ( isset( self::$deviation[ $codePoint ] ) ) { return [ 'status' => 'deviation', 'mapping' => self::$deviation[ $codePoint ] ]; } if ( isset( self::$disallowed[ $codePoint ] ) || DisallowedRanges::inRange( $codePoint ) ) { return [ 'status' => 'disallowed' ]; } $isDisallowedMapped = isset( self::$disallowed_STD3_mapped[ $codePoint ] ); if ( $isDisallowedMapped || isset( self::$disallowed_STD3_valid[ $codePoint ] ) ) { $status = 'disallowed'; if ( ! $useSTD3ASCIIRules ) { $status = $isDisallowedMapped ? 'mapped' : 'valid'; } if ( $isDisallowedMapped ) { return [ 'status' => $status, 'mapping' => self::$disallowed_STD3_mapped[ $codePoint ] ]; } return [ 'status' => $status ]; } return [ 'status' => 'valid' ]; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/composer.json 0000777 00000001736 15251156632 0021600 0 ustar 00 { "name": "rowbot/idna", "description": "An implementation of UTS#46 Unicode IDNA Compatibility Processing.", "keywords": [ "idna", "unicode", "tr46", "international domain names", "uts46", "iri", "idn" ], "type": "library", "license": "MIT", "authors": [ { "name": "Trevor Rowbotham" } ], "config": { "sort-packages": true }, "autoload": { "psr-4": { "Rowbot\\Idna\\": "src/", "Rowbot\\Idna\\Resource\\": "resources/" } }, "autoload-dev": { "psr-4": { "Rowbot\\Idna\\Bin\\": "bin/", "Rowbot\\Idna\\Test\\": "tests/" } }, "require": { "php": ">=7.1", "rowbot/punycode": "^1.0", "symfony/polyfill-intl-normalizer": "^1.18" }, "require-dev": { "guzzlehttp/guzzle": "^6.5 || ^7.0", "phpstan/phpstan": "^1.2", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", "squizlabs/php_codesniffer": "^3.5.1", "symfony/cache": "^4.3 || ^5.0" } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/phpunit.xml 0000777 00000001527 15251156632 0021265 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/5.7/phpunit.xsd" bootstrap="vendor/autoload.php" backupGlobals="false" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTestsThatDoNotTestAnything="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> <filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> <exclude> <directory>src/Resources</directory> </exclude> </whitelist> </filter> </phpunit> php-toolkit/DataLiberation/vendor-patched/rowbot/idna/LICENSE 0000777 00000002061 15251156632 0020053 0 ustar 00 MIT License Copyright (c) 2020 Trevor Rowbotham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/rowbot/idna/phpstan.neon 0000777 00000000725 15251156632 0021411 0 ustar 00 includes: - vendor/phpstan/phpstan-deprecation-rules/rules.neon - vendor/phpstan/phpstan-strict-rules/rules.neon parameters: level: max paths: - src - bin ignoreErrors: # This error can safely be ignored as we do these checks when building the data files. - message: "#Offset 'mapping' does not exist on array{status: string, mapping\\?: string}\\.#" path: src/Idna.php count: 2 php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/DisallowedRanges.php 0000777 00000015172 15251156632 0025027 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Idna\Resource; final class DisallowedRanges { public static function inRange( int $codePoint ): bool { if ( $codePoint >= 128 && $codePoint <= 159 ) { return true; } if ( $codePoint >= 3676 && $codePoint <= 3712 ) { return true; } if ( $codePoint >= 3808 && $codePoint <= 3839 ) { return true; } if ( $codePoint >= 4059 && $codePoint <= 4095 ) { return true; } if ( $codePoint >= 4256 && $codePoint <= 4293 ) { return true; } if ( $codePoint >= 6863 && $codePoint <= 6911 ) { return true; } if ( $codePoint >= 11870 && $codePoint <= 11903 ) { return true; } if ( $codePoint >= 55296 && $codePoint <= 57343 ) { return true; } if ( $codePoint >= 57344 && $codePoint <= 63743 ) { return true; } if ( $codePoint >= 64218 && $codePoint <= 64255 ) { return true; } if ( $codePoint >= 64976 && $codePoint <= 65007 ) { return true; } if ( $codePoint >= 65630 && $codePoint <= 65663 ) { return true; } if ( $codePoint >= 65953 && $codePoint <= 65999 ) { return true; } if ( $codePoint >= 66046 && $codePoint <= 66175 ) { return true; } if ( $codePoint >= 66518 && $codePoint <= 66559 ) { return true; } if ( $codePoint >= 67005 && $codePoint <= 67071 ) { return true; } if ( $codePoint >= 67515 && $codePoint <= 67583 ) { return true; } if ( $codePoint >= 67760 && $codePoint <= 67807 ) { return true; } if ( $codePoint >= 67904 && $codePoint <= 67967 ) { return true; } if ( $codePoint >= 68256 && $codePoint <= 68287 ) { return true; } if ( $codePoint >= 68528 && $codePoint <= 68607 ) { return true; } if ( $codePoint >= 68681 && $codePoint <= 68735 ) { return true; } if ( $codePoint >= 68922 && $codePoint <= 69215 ) { return true; } if ( $codePoint >= 69298 && $codePoint <= 69375 ) { return true; } if ( $codePoint >= 69514 && $codePoint <= 69551 ) { return true; } if ( $codePoint >= 70207 && $codePoint <= 70271 ) { return true; } if ( $codePoint >= 70517 && $codePoint <= 70655 ) { return true; } if ( $codePoint >= 70874 && $codePoint <= 71039 ) { return true; } if ( $codePoint >= 71134 && $codePoint <= 71167 ) { return true; } if ( $codePoint >= 71370 && $codePoint <= 71423 ) { return true; } if ( $codePoint >= 71495 && $codePoint <= 71679 ) { return true; } if ( $codePoint >= 71740 && $codePoint <= 71839 ) { return true; } if ( $codePoint >= 72026 && $codePoint <= 72095 ) { return true; } if ( $codePoint >= 72441 && $codePoint <= 72703 ) { return true; } if ( $codePoint >= 72887 && $codePoint <= 72959 ) { return true; } if ( $codePoint >= 73130 && $codePoint <= 73439 ) { return true; } if ( $codePoint >= 73465 && $codePoint <= 73647 ) { return true; } if ( $codePoint >= 74650 && $codePoint <= 74751 ) { return true; } if ( $codePoint >= 75076 && $codePoint <= 77711 ) { return true; } if ( $codePoint >= 78905 && $codePoint <= 82943 ) { return true; } if ( $codePoint >= 83527 && $codePoint <= 92159 ) { return true; } if ( $codePoint >= 93072 && $codePoint <= 93759 ) { return true; } if ( $codePoint >= 93851 && $codePoint <= 93951 ) { return true; } if ( $codePoint >= 94112 && $codePoint <= 94175 ) { return true; } if ( $codePoint >= 101590 && $codePoint <= 101631 ) { return true; } if ( $codePoint >= 101641 && $codePoint <= 110575 ) { return true; } if ( $codePoint >= 110883 && $codePoint <= 110927 ) { return true; } if ( $codePoint >= 111356 && $codePoint <= 113663 ) { return true; } if ( $codePoint >= 113828 && $codePoint <= 118527 ) { return true; } if ( $codePoint >= 118724 && $codePoint <= 118783 ) { return true; } if ( $codePoint >= 119366 && $codePoint <= 119519 ) { return true; } if ( $codePoint >= 119673 && $codePoint <= 119807 ) { return true; } if ( $codePoint >= 121520 && $codePoint <= 122623 ) { return true; } if ( $codePoint >= 122655 && $codePoint <= 122879 ) { return true; } if ( $codePoint >= 122923 && $codePoint <= 123135 ) { return true; } if ( $codePoint >= 123216 && $codePoint <= 123535 ) { return true; } if ( $codePoint >= 123648 && $codePoint <= 124895 ) { return true; } if ( $codePoint >= 125143 && $codePoint <= 125183 ) { return true; } if ( $codePoint >= 125280 && $codePoint <= 126064 ) { return true; } if ( $codePoint >= 126133 && $codePoint <= 126208 ) { return true; } if ( $codePoint >= 126270 && $codePoint <= 126463 ) { return true; } if ( $codePoint >= 126652 && $codePoint <= 126703 ) { return true; } if ( $codePoint >= 126706 && $codePoint <= 126975 ) { return true; } if ( $codePoint >= 127406 && $codePoint <= 127461 ) { return true; } if ( $codePoint >= 127590 && $codePoint <= 127743 ) { return true; } if ( $codePoint >= 129202 && $codePoint <= 129279 ) { return true; } if ( $codePoint >= 129995 && $codePoint <= 130031 ) { return true; } if ( $codePoint >= 130042 && $codePoint <= 131069 ) { return true; } if ( $codePoint >= 173792 && $codePoint <= 173823 ) { return true; } if ( $codePoint >= 191457 && $codePoint <= 194559 ) { return true; } if ( $codePoint >= 195102 && $codePoint <= 196605 ) { return true; } if ( $codePoint >= 201547 && $codePoint <= 262141 ) { return true; } if ( $codePoint >= 262144 && $codePoint <= 327677 ) { return true; } if ( $codePoint >= 327680 && $codePoint <= 393213 ) { return true; } if ( $codePoint >= 393216 && $codePoint <= 458749 ) { return true; } if ( $codePoint >= 458752 && $codePoint <= 524285 ) { return true; } if ( $codePoint >= 524288 && $codePoint <= 589821 ) { return true; } if ( $codePoint >= 589824 && $codePoint <= 655357 ) { return true; } if ( $codePoint >= 655360 && $codePoint <= 720893 ) { return true; } if ( $codePoint >= 720896 && $codePoint <= 786429 ) { return true; } if ( $codePoint >= 786432 && $codePoint <= 851965 ) { return true; } if ( $codePoint >= 851968 && $codePoint <= 917501 ) { return true; } if ( $codePoint >= 917536 && $codePoint <= 917631 ) { return true; } if ( $codePoint >= 917632 && $codePoint <= 917759 ) { return true; } if ( $codePoint >= 918000 && $codePoint <= 983037 ) { return true; } if ( $codePoint >= 983040 && $codePoint <= 1048573 ) { return true; } if ( $codePoint >= 1048576 && $codePoint <= 1114109 ) { return true; } return false; } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/disallowed_STD3_valid.php 0000777 00000002005 15251156632 0025672 0 ustar 00 <?php return array( 0 => true, 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 6 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 14 => true, 15 => true, 16 => true, 17 => true, 18 => true, 19 => true, 20 => true, 21 => true, 22 => true, 23 => true, 24 => true, 25 => true, 26 => true, 27 => true, 28 => true, 29 => true, 30 => true, 31 => true, 32 => true, 33 => true, 34 => true, 35 => true, 36 => true, 37 => true, 38 => true, 39 => true, 40 => true, 41 => true, 42 => true, 43 => true, 44 => true, 47 => true, 58 => true, 59 => true, 60 => true, 61 => true, 62 => true, 63 => true, 64 => true, 91 => true, 92 => true, 93 => true, 94 => true, 95 => true, 96 => true, 123 => true, 124 => true, 125 => true, 126 => true, 127 => true, 8800 => true, 8814 => true, 8815 => true, ); php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/Regex.php 0000777 00000307602 15251156632 0022654 0 ustar 00 <?php // This file was auto generated by running 'php bin/generateDataFiles.php' declare( strict_types=1 ); namespace Rowbot\Idna\Resource; final class Regex { public const COMBINING_MARK = '/^[\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{0898}-\x{089F}\x{08CA}-\x{08E1}\x{08E3}-\x{0902}\x{0903}\x{093A}\x{093B}\x{093C}\x{093E}-\x{0940}\x{0941}-\x{0948}\x{0949}-\x{094C}\x{094D}\x{094E}-\x{094F}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{0982}-\x{0983}\x{09BC}\x{09BE}-\x{09C0}\x{09C1}-\x{09C4}\x{09C7}-\x{09C8}\x{09CB}-\x{09CC}\x{09CD}\x{09D7}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A03}\x{0A3C}\x{0A3E}-\x{0A40}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0A83}\x{0ABC}\x{0ABE}-\x{0AC0}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0AC9}\x{0ACB}-\x{0ACC}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B02}-\x{0B03}\x{0B3C}\x{0B3E}\x{0B3F}\x{0B40}\x{0B41}-\x{0B44}\x{0B47}-\x{0B48}\x{0B4B}-\x{0B4C}\x{0B4D}\x{0B55}-\x{0B56}\x{0B57}\x{0B62}-\x{0B63}\x{0B82}\x{0BBE}-\x{0BBF}\x{0BC0}\x{0BC1}-\x{0BC2}\x{0BC6}-\x{0BC8}\x{0BCA}-\x{0BCC}\x{0BCD}\x{0BD7}\x{0C00}\x{0C01}-\x{0C03}\x{0C04}\x{0C3C}\x{0C3E}-\x{0C40}\x{0C41}-\x{0C44}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0C82}-\x{0C83}\x{0CBC}\x{0CBE}\x{0CBF}\x{0CC0}-\x{0CC4}\x{0CC6}\x{0CC7}-\x{0CC8}\x{0CCA}-\x{0CCB}\x{0CCC}-\x{0CCD}\x{0CD5}-\x{0CD6}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D02}-\x{0D03}\x{0D3B}-\x{0D3C}\x{0D3E}-\x{0D40}\x{0D41}-\x{0D44}\x{0D46}-\x{0D48}\x{0D4A}-\x{0D4C}\x{0D4D}\x{0D57}\x{0D62}-\x{0D63}\x{0D81}\x{0D82}-\x{0D83}\x{0DCA}\x{0DCF}-\x{0DD1}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0DD8}-\x{0DDF}\x{0DF2}-\x{0DF3}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3E}-\x{0F3F}\x{0F71}-\x{0F7E}\x{0F7F}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102B}-\x{102C}\x{102D}-\x{1030}\x{1031}\x{1032}-\x{1037}\x{1038}\x{1039}-\x{103A}\x{103B}-\x{103C}\x{103D}-\x{103E}\x{1056}-\x{1057}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106D}\x{1071}-\x{1074}\x{1082}\x{1083}-\x{1084}\x{1085}-\x{1086}\x{1087}-\x{108C}\x{108D}\x{108F}\x{109A}-\x{109C}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1715}\x{1732}-\x{1733}\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B6}\x{17B7}-\x{17BD}\x{17BE}-\x{17C5}\x{17C6}\x{17C7}-\x{17C8}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{180F}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1923}-\x{1926}\x{1927}-\x{1928}\x{1929}-\x{192B}\x{1930}-\x{1931}\x{1932}\x{1933}-\x{1938}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A19}-\x{1A1A}\x{1A1B}\x{1A55}\x{1A56}\x{1A57}\x{1A58}-\x{1A5E}\x{1A60}\x{1A61}\x{1A62}\x{1A63}-\x{1A64}\x{1A65}-\x{1A6C}\x{1A6D}-\x{1A72}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1ACE}\x{1B00}-\x{1B03}\x{1B04}\x{1B34}\x{1B35}\x{1B36}-\x{1B3A}\x{1B3B}\x{1B3C}\x{1B3D}-\x{1B41}\x{1B42}\x{1B43}-\x{1B44}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1B82}\x{1BA1}\x{1BA2}-\x{1BA5}\x{1BA6}-\x{1BA7}\x{1BA8}-\x{1BA9}\x{1BAA}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE7}\x{1BE8}-\x{1BE9}\x{1BEA}-\x{1BEC}\x{1BED}\x{1BEE}\x{1BEF}-\x{1BF1}\x{1BF2}-\x{1BF3}\x{1C24}-\x{1C2B}\x{1C2C}-\x{1C33}\x{1C34}-\x{1C35}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE1}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF7}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{302E}-\x{302F}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A823}-\x{A824}\x{A825}-\x{A826}\x{A827}\x{A82C}\x{A880}-\x{A881}\x{A8B4}-\x{A8C3}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A952}-\x{A953}\x{A980}-\x{A982}\x{A983}\x{A9B3}\x{A9B4}-\x{A9B5}\x{A9B6}-\x{A9B9}\x{A9BA}-\x{A9BB}\x{A9BC}-\x{A9BD}\x{A9BE}-\x{A9C0}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA2F}-\x{AA30}\x{AA31}-\x{AA32}\x{AA33}-\x{AA34}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA4D}\x{AA7B}\x{AA7C}\x{AA7D}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEB}\x{AAEC}-\x{AAED}\x{AAEE}-\x{AAEF}\x{AAF5}\x{AAF6}\x{ABE3}-\x{ABE4}\x{ABE5}\x{ABE6}-\x{ABE7}\x{ABE8}\x{ABE9}-\x{ABEA}\x{ABEC}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{10F82}-\x{10F85}\x{11000}\x{11001}\x{11002}\x{11038}-\x{11046}\x{11070}\x{11073}-\x{11074}\x{1107F}-\x{11081}\x{11082}\x{110B0}-\x{110B2}\x{110B3}-\x{110B6}\x{110B7}-\x{110B8}\x{110B9}-\x{110BA}\x{110C2}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112C}\x{1112D}-\x{11134}\x{11145}-\x{11146}\x{11173}\x{11180}-\x{11181}\x{11182}\x{111B3}-\x{111B5}\x{111B6}-\x{111BE}\x{111BF}-\x{111C0}\x{111C9}-\x{111CC}\x{111CE}\x{111CF}\x{1122C}-\x{1122E}\x{1122F}-\x{11231}\x{11232}-\x{11233}\x{11234}\x{11235}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E0}-\x{112E2}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{11302}-\x{11303}\x{1133B}-\x{1133C}\x{1133E}-\x{1133F}\x{11340}\x{11341}-\x{11344}\x{11347}-\x{11348}\x{1134B}-\x{1134D}\x{11357}\x{11362}-\x{11363}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11435}-\x{11437}\x{11438}-\x{1143F}\x{11440}-\x{11441}\x{11442}-\x{11444}\x{11445}\x{11446}\x{1145E}\x{114B0}-\x{114B2}\x{114B3}-\x{114B8}\x{114B9}\x{114BA}\x{114BB}-\x{114BE}\x{114BF}-\x{114C0}\x{114C1}\x{114C2}-\x{114C3}\x{115AF}-\x{115B1}\x{115B2}-\x{115B5}\x{115B8}-\x{115BB}\x{115BC}-\x{115BD}\x{115BE}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11630}-\x{11632}\x{11633}-\x{1163A}\x{1163B}-\x{1163C}\x{1163D}\x{1163E}\x{1163F}-\x{11640}\x{116AB}\x{116AC}\x{116AD}\x{116AE}-\x{116AF}\x{116B0}-\x{116B5}\x{116B6}\x{116B7}\x{1171D}-\x{1171F}\x{11720}-\x{11721}\x{11722}-\x{11725}\x{11726}\x{11727}-\x{1172B}\x{1182C}-\x{1182E}\x{1182F}-\x{11837}\x{11838}\x{11839}-\x{1183A}\x{11930}-\x{11935}\x{11937}-\x{11938}\x{1193B}-\x{1193C}\x{1193D}\x{1193E}\x{11940}\x{11942}\x{11943}\x{119D1}-\x{119D3}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119DC}-\x{119DF}\x{119E0}\x{119E4}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A39}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A57}-\x{11A58}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A97}\x{11A98}-\x{11A99}\x{11C2F}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3E}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CA9}\x{11CAA}-\x{11CB0}\x{11CB1}\x{11CB2}-\x{11CB3}\x{11CB4}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D8A}-\x{11D8E}\x{11D90}-\x{11D91}\x{11D93}-\x{11D94}\x{11D95}\x{11D96}\x{11D97}\x{11EF3}-\x{11EF4}\x{11EF5}-\x{11EF6}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F51}-\x{16F87}\x{16F8F}-\x{16F92}\x{16FE4}\x{16FF0}-\x{16FF1}\x{1BC9D}-\x{1BC9E}\x{1CF00}-\x{1CF2D}\x{1CF30}-\x{1CF46}\x{1D165}-\x{1D166}\x{1D167}-\x{1D169}\x{1D16D}-\x{1D172}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2AE}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]/u'; public const RTL_LABEL = '/[\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{0887}\x{0888}\x{0889}-\x{088E}\x{088F}\x{0890}-\x{0891}\x{0892}-\x{0897}\x{08A0}-\x{08C8}\x{08C9}\x{08E2}\x{200F}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC2}\x{FBC3}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCE}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10F81}\x{10F86}-\x{10F89}\x{10F8A}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u'; public const BIDI_STEP_1_LTR = '/^[^\x{0000}-\x{0008}\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{000E}-\x{001B}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{0030}-\x{0039}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0085}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B2}-\x{00B3}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00B9}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{0887}\x{0888}\x{0889}-\x{088E}\x{088F}\x{0890}-\x{0891}\x{0892}-\x{0897}\x{0898}-\x{089F}\x{08A0}-\x{08C8}\x{08C9}\x{08CA}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3C}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{1680}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1733}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{180F}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1ACE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{2000}-\x{200A}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{205F}\x{2060}-\x{2064}\x{2065}\x{2066}\x{2067}\x{2068}\x{2069}\x{206A}-\x{206F}\x{2070}\x{2074}-\x{2079}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{2080}-\x{2089}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20C0}\x{20C1}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{2488}-\x{249B}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}-\x{2E54}\x{2E55}\x{2E56}\x{2E57}\x{2E58}\x{2E59}\x{2E5A}\x{2E5B}\x{2E5C}\x{2E5D}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3000}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC2}\x{FBC3}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCE}\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF10}-\x{FF19}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{102E1}-\x{102FB}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10F81}\x{10F82}-\x{10F85}\x{10F86}-\x{10F89}\x{10F8A}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{11070}\x{11073}-\x{11074}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110C2}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1CF00}-\x{1CF2D}\x{1CF30}-\x{1CF46}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D1E9}-\x{1D1EA}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1D7CE}-\x{1D7FF}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2AE}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F100}-\x{1F10A}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6DD}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F7F0}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7C}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAAC}\x{1FAB0}-\x{1FABA}\x{1FAC0}-\x{1FAC5}\x{1FAD0}-\x{1FAD9}\x{1FAE0}-\x{1FAE7}\x{1FAF0}-\x{1FAF6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FBF0}-\x{1FBF9}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}]/u'; public const BIDI_STEP_1_RTL = '/^[\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{0887}\x{0888}\x{0889}-\x{088E}\x{088F}\x{0892}-\x{0897}\x{08A0}-\x{08C8}\x{08C9}\x{200F}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC2}\x{FBC3}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCE}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10F81}\x{10F86}-\x{10F89}\x{10F8A}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u'; public const BIDI_STEP_2 = '/[^\x{0000}-\x{0008}\x{000E}-\x{001B}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{0030}-\x{0039}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B2}-\x{00B3}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00B9}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{0887}\x{0888}\x{0889}-\x{088E}\x{088F}\x{0890}-\x{0891}\x{0892}-\x{0897}\x{0898}-\x{089F}\x{08A0}-\x{08C8}\x{08C9}\x{08CA}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3C}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1733}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{180F}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1ACE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{2060}-\x{2064}\x{2065}\x{206A}-\x{206F}\x{2070}\x{2074}-\x{2079}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{2080}-\x{2089}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20C0}\x{20C1}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{2488}-\x{249B}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}-\x{2E54}\x{2E55}\x{2E56}\x{2E57}\x{2E58}\x{2E59}\x{2E5A}\x{2E5B}\x{2E5C}\x{2E5D}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC2}\x{FBC3}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCE}\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF10}-\x{FF19}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{102E1}-\x{102FB}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10F81}\x{10F82}-\x{10F85}\x{10F86}-\x{10F89}\x{10F8A}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{11070}\x{11073}-\x{11074}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110C2}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1CF00}-\x{1CF2D}\x{1CF30}-\x{1CF46}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D1E9}-\x{1D1EA}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1D7CE}-\x{1D7FF}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2AE}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F100}-\x{1F10A}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6DD}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F7F0}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7C}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAAC}\x{1FAB0}-\x{1FABA}\x{1FAC0}-\x{1FAC5}\x{1FAD0}-\x{1FAD9}\x{1FAE0}-\x{1FAE7}\x{1FAF0}-\x{1FAF6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FBF0}-\x{1FBF9}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}]/u'; public const BIDI_STEP_3 = '/[\x{0030}-\x{0039}\x{00B2}-\x{00B3}\x{00B9}\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{0887}\x{0888}\x{0889}-\x{088E}\x{088F}\x{0890}-\x{0891}\x{0892}-\x{0897}\x{08A0}-\x{08C8}\x{08C9}\x{08E2}\x{200F}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}\x{2488}-\x{249B}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC2}\x{FBC3}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCE}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FF10}-\x{FF19}\x{102E1}-\x{102FB}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10F81}\x{10F86}-\x{10F89}\x{10F8A}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1D7CE}-\x{1D7FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F100}-\x{1F10A}\x{1FBF0}-\x{1FBF9}][\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{0898}-\x{089F}\x{08CA}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3C}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1733}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{180F}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1ACE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{10F82}-\x{10F85}\x{11001}\x{11038}-\x{11046}\x{11070}\x{11073}-\x{11074}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110C2}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1CF00}-\x{1CF2D}\x{1CF30}-\x{1CF46}\x{1D167}-\x{1D169}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2AE}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]*$/u'; public const BIDI_STEP_4_AN = '/[\x{0600}-\x{0605}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{06DD}\x{0890}-\x{0891}\x{08E2}\x{10D30}-\x{10D39}\x{10E60}-\x{10E7E}]/u'; public const BIDI_STEP_4_EN = '/[\x{0030}-\x{0039}\x{00B2}-\x{00B3}\x{00B9}\x{06F0}-\x{06F9}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}\x{2488}-\x{249B}\x{FF10}-\x{FF19}\x{102E1}-\x{102FB}\x{1D7CE}-\x{1D7FF}\x{1F100}-\x{1F10A}\x{1FBF0}-\x{1FBF9}]/u'; public const BIDI_STEP_5 = '/[\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0085}\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{0887}\x{0888}\x{0889}-\x{088E}\x{088F}\x{0890}-\x{0891}\x{0892}-\x{0897}\x{08A0}-\x{08C8}\x{08C9}\x{08E2}\x{1680}\x{2000}-\x{200A}\x{200F}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{205F}\x{2066}\x{2067}\x{2068}\x{2069}\x{3000}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC2}\x{FBC3}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCE}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10F81}\x{10F86}-\x{10F89}\x{10F8A}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u'; public const BIDI_STEP_6 = '/[^\x{0000}-\x{0008}\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{000E}-\x{001B}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0085}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{0887}\x{0888}\x{0889}-\x{088E}\x{088F}\x{0890}-\x{0891}\x{0892}-\x{0897}\x{0898}-\x{089F}\x{08A0}-\x{08C8}\x{08C9}\x{08CA}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3C}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{1680}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1733}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{180F}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1ACE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{2000}-\x{200A}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{205F}\x{2060}-\x{2064}\x{2065}\x{2066}\x{2067}\x{2068}\x{2069}\x{206A}-\x{206F}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20C0}\x{20C1}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}-\x{2E54}\x{2E55}\x{2E56}\x{2E57}\x{2E58}\x{2E59}\x{2E5A}\x{2E5B}\x{2E5C}\x{2E5D}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3000}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC2}\x{FBC3}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCE}\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10F81}\x{10F82}-\x{10F85}\x{10F86}-\x{10F89}\x{10F8A}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{11070}\x{11073}-\x{11074}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110C2}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1CF00}-\x{1CF2D}\x{1CF30}-\x{1CF46}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D1E9}-\x{1D1EA}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2AE}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6DD}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F7F0}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7C}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAAC}\x{1FAB0}-\x{1FABA}\x{1FAC0}-\x{1FAC5}\x{1FAD0}-\x{1FAD9}\x{1FAE0}-\x{1FAE7}\x{1FAF0}-\x{1FAF6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}][\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{0898}-\x{089F}\x{08CA}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3C}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1733}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{180F}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1ACE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{10F82}-\x{10F85}\x{11001}\x{11038}-\x{11046}\x{11070}\x{11073}-\x{11074}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110C2}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1CF00}-\x{1CF2D}\x{1CF30}-\x{1CF46}\x{1D167}-\x{1D169}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2AE}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]*$/u'; public const ZWNJ = '/([\x{A872}\x{10ACD}\x{10AD7}\x{10D00}\x{10FCB}\x{0620}\x{0626}\x{0628}\x{062A}-\x{062E}\x{0633}-\x{063F}\x{0641}-\x{0647}\x{0649}-\x{064A}\x{066E}-\x{066F}\x{0678}-\x{0687}\x{069A}-\x{06BF}\x{06C1}-\x{06C2}\x{06CC}\x{06CE}\x{06D0}-\x{06D1}\x{06FA}-\x{06FC}\x{06FF}\x{0712}-\x{0714}\x{071A}-\x{071D}\x{071F}-\x{0727}\x{0729}\x{072B}\x{072D}-\x{072E}\x{074E}-\x{0758}\x{075C}-\x{076A}\x{076D}-\x{0770}\x{0772}\x{0775}-\x{0777}\x{077A}-\x{077F}\x{07CA}-\x{07EA}\x{0841}-\x{0845}\x{0848}\x{084A}-\x{0853}\x{0855}\x{0860}\x{0862}-\x{0865}\x{0868}\x{0886}\x{0889}-\x{088D}\x{08A0}-\x{08A9}\x{08AF}-\x{08B0}\x{08B3}-\x{08B8}\x{08BA}-\x{08C8}\x{1807}\x{1820}-\x{1842}\x{1843}\x{1844}-\x{1878}\x{1887}-\x{18A8}\x{18AA}\x{A840}-\x{A871}\x{10AC0}-\x{10AC4}\x{10AD3}-\x{10AD6}\x{10AD8}-\x{10ADC}\x{10ADE}-\x{10AE0}\x{10AEB}-\x{10AEE}\x{10B80}\x{10B82}\x{10B86}-\x{10B88}\x{10B8A}-\x{10B8B}\x{10B8D}\x{10B90}\x{10BAD}-\x{10BAE}\x{10D01}-\x{10D21}\x{10D23}\x{10F30}-\x{10F32}\x{10F34}-\x{10F44}\x{10F51}-\x{10F53}\x{10F70}-\x{10F73}\x{10F76}-\x{10F81}\x{10FB0}\x{10FB2}-\x{10FB3}\x{10FB8}\x{10FBB}-\x{10FBC}\x{10FBE}-\x{10FBF}\x{10FC1}\x{10FC4}\x{10FCA}\x{1E900}-\x{1E943}][\x{00AD}\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{061C}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{0898}-\x{089F}\x{08CA}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3C}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1733}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{180F}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1ACE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DFF}\x{200B}\x{200E}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{206A}-\x{206F}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{FEFF}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{10F82}-\x{10F85}\x{11001}\x{11038}-\x{11046}\x{11070}\x{11073}-\x{11074}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110C2}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{13430}-\x{13438}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1CF00}-\x{1CF2D}\x{1CF30}-\x{1CF46}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2AE}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1E94B}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}]*\x{200C}[\x{00AD}\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{061C}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{0898}-\x{089F}\x{08CA}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3C}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1733}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{180F}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1ACE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DFF}\x{200B}\x{200E}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{206A}-\x{206F}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{FEFF}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{10F82}-\x{10F85}\x{11001}\x{11038}-\x{11046}\x{11070}\x{11073}-\x{11074}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110C2}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{13430}-\x{13438}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1CF00}-\x{1CF2D}\x{1CF30}-\x{1CF46}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2AE}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1E94B}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}]*)[\x{0622}-\x{0625}\x{0627}\x{0629}\x{062F}-\x{0632}\x{0648}\x{0671}-\x{0673}\x{0675}-\x{0677}\x{0688}-\x{0699}\x{06C0}\x{06C3}-\x{06CB}\x{06CD}\x{06CF}\x{06D2}-\x{06D3}\x{06D5}\x{06EE}-\x{06EF}\x{0710}\x{0715}-\x{0719}\x{071E}\x{0728}\x{072A}\x{072C}\x{072F}\x{074D}\x{0759}-\x{075B}\x{076B}-\x{076C}\x{0771}\x{0773}-\x{0774}\x{0778}-\x{0779}\x{0840}\x{0846}-\x{0847}\x{0849}\x{0854}\x{0856}-\x{0858}\x{0867}\x{0869}-\x{086A}\x{0870}-\x{0882}\x{088E}\x{08AA}-\x{08AC}\x{08AE}\x{08B1}-\x{08B2}\x{08B9}\x{10AC5}\x{10AC7}\x{10AC9}-\x{10ACA}\x{10ACE}-\x{10AD2}\x{10ADD}\x{10AE1}\x{10AE4}\x{10AEF}\x{10B81}\x{10B83}-\x{10B85}\x{10B89}\x{10B8C}\x{10B8E}-\x{10B8F}\x{10B91}\x{10BA9}-\x{10BAC}\x{10D22}\x{10F33}\x{10F54}\x{10F74}-\x{10F75}\x{10FB4}-\x{10FB6}\x{10FB9}-\x{10FBA}\x{10FBD}\x{10FC2}-\x{10FC3}\x{10FC9}\x{0620}\x{0626}\x{0628}\x{062A}-\x{062E}\x{0633}-\x{063F}\x{0641}-\x{0647}\x{0649}-\x{064A}\x{066E}-\x{066F}\x{0678}-\x{0687}\x{069A}-\x{06BF}\x{06C1}-\x{06C2}\x{06CC}\x{06CE}\x{06D0}-\x{06D1}\x{06FA}-\x{06FC}\x{06FF}\x{0712}-\x{0714}\x{071A}-\x{071D}\x{071F}-\x{0727}\x{0729}\x{072B}\x{072D}-\x{072E}\x{074E}-\x{0758}\x{075C}-\x{076A}\x{076D}-\x{0770}\x{0772}\x{0775}-\x{0777}\x{077A}-\x{077F}\x{07CA}-\x{07EA}\x{0841}-\x{0845}\x{0848}\x{084A}-\x{0853}\x{0855}\x{0860}\x{0862}-\x{0865}\x{0868}\x{0886}\x{0889}-\x{088D}\x{08A0}-\x{08A9}\x{08AF}-\x{08B0}\x{08B3}-\x{08B8}\x{08BA}-\x{08C8}\x{1807}\x{1820}-\x{1842}\x{1843}\x{1844}-\x{1878}\x{1887}-\x{18A8}\x{18AA}\x{A840}-\x{A871}\x{10AC0}-\x{10AC4}\x{10AD3}-\x{10AD6}\x{10AD8}-\x{10ADC}\x{10ADE}-\x{10AE0}\x{10AEB}-\x{10AEE}\x{10B80}\x{10B82}\x{10B86}-\x{10B88}\x{10B8A}-\x{10B8B}\x{10B8D}\x{10B90}\x{10BAD}-\x{10BAE}\x{10D01}-\x{10D21}\x{10D23}\x{10F30}-\x{10F32}\x{10F34}-\x{10F44}\x{10F51}-\x{10F53}\x{10F70}-\x{10F73}\x{10F76}-\x{10F81}\x{10FB0}\x{10FB2}-\x{10FB3}\x{10FB8}\x{10FBB}-\x{10FBC}\x{10FBE}-\x{10FBF}\x{10FC1}\x{10FC4}\x{10FCA}\x{1E900}-\x{1E943}]/u'; /** * @codeCoverageIgnore */ private function __construct() { } } php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/virama.php 0000777 00000001513 15251156632 0023051 0 ustar 00 <?php return array( 2381 => 9, 2509 => 9, 2637 => 9, 2765 => 9, 2893 => 9, 3021 => 9, 3149 => 9, 3277 => 9, 3387 => 9, 3388 => 9, 3405 => 9, 3530 => 9, 3642 => 9, 3770 => 9, 3972 => 9, 4153 => 9, 4154 => 9, 5908 => 9, 5909 => 9, 5940 => 9, 6098 => 9, 6752 => 9, 6980 => 9, 7082 => 9, 7083 => 9, 7154 => 9, 7155 => 9, 11647 => 9, 43014 => 9, 43052 => 9, 43204 => 9, 43347 => 9, 43456 => 9, 43766 => 9, 44013 => 9, 68159 => 9, 69702 => 9, 69744 => 9, 69759 => 9, 69817 => 9, 69939 => 9, 69940 => 9, 70080 => 9, 70197 => 9, 70378 => 9, 70477 => 9, 70722 => 9, 70850 => 9, 71103 => 9, 71231 => 9, 71350 => 9, 71467 => 9, 71737 => 9, 71997 => 9, 71998 => 9, 72160 => 9, 72244 => 9, 72263 => 9, 72345 => 9, 72767 => 9, 73028 => 9, 73029 => 9, 73111 => 9, ); php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/ignored.php 0000777 00000011006 15251156632 0023217 0 ustar 00 <?php return array( 173 => true, 847 => true, 6155 => true, 6156 => true, 6157 => true, 6159 => true, 8203 => true, 8288 => true, 8292 => true, 65024 => true, 65025 => true, 65026 => true, 65027 => true, 65028 => true, 65029 => true, 65030 => true, 65031 => true, 65032 => true, 65033 => true, 65034 => true, 65035 => true, 65036 => true, 65037 => true, 65038 => true, 65039 => true, 65279 => true, 113824 => true, 113825 => true, 113826 => true, 113827 => true, 917760 => true, 917761 => true, 917762 => true, 917763 => true, 917764 => true, 917765 => true, 917766 => true, 917767 => true, 917768 => true, 917769 => true, 917770 => true, 917771 => true, 917772 => true, 917773 => true, 917774 => true, 917775 => true, 917776 => true, 917777 => true, 917778 => true, 917779 => true, 917780 => true, 917781 => true, 917782 => true, 917783 => true, 917784 => true, 917785 => true, 917786 => true, 917787 => true, 917788 => true, 917789 => true, 917790 => true, 917791 => true, 917792 => true, 917793 => true, 917794 => true, 917795 => true, 917796 => true, 917797 => true, 917798 => true, 917799 => true, 917800 => true, 917801 => true, 917802 => true, 917803 => true, 917804 => true, 917805 => true, 917806 => true, 917807 => true, 917808 => true, 917809 => true, 917810 => true, 917811 => true, 917812 => true, 917813 => true, 917814 => true, 917815 => true, 917816 => true, 917817 => true, 917818 => true, 917819 => true, 917820 => true, 917821 => true, 917822 => true, 917823 => true, 917824 => true, 917825 => true, 917826 => true, 917827 => true, 917828 => true, 917829 => true, 917830 => true, 917831 => true, 917832 => true, 917833 => true, 917834 => true, 917835 => true, 917836 => true, 917837 => true, 917838 => true, 917839 => true, 917840 => true, 917841 => true, 917842 => true, 917843 => true, 917844 => true, 917845 => true, 917846 => true, 917847 => true, 917848 => true, 917849 => true, 917850 => true, 917851 => true, 917852 => true, 917853 => true, 917854 => true, 917855 => true, 917856 => true, 917857 => true, 917858 => true, 917859 => true, 917860 => true, 917861 => true, 917862 => true, 917863 => true, 917864 => true, 917865 => true, 917866 => true, 917867 => true, 917868 => true, 917869 => true, 917870 => true, 917871 => true, 917872 => true, 917873 => true, 917874 => true, 917875 => true, 917876 => true, 917877 => true, 917878 => true, 917879 => true, 917880 => true, 917881 => true, 917882 => true, 917883 => true, 917884 => true, 917885 => true, 917886 => true, 917887 => true, 917888 => true, 917889 => true, 917890 => true, 917891 => true, 917892 => true, 917893 => true, 917894 => true, 917895 => true, 917896 => true, 917897 => true, 917898 => true, 917899 => true, 917900 => true, 917901 => true, 917902 => true, 917903 => true, 917904 => true, 917905 => true, 917906 => true, 917907 => true, 917908 => true, 917909 => true, 917910 => true, 917911 => true, 917912 => true, 917913 => true, 917914 => true, 917915 => true, 917916 => true, 917917 => true, 917918 => true, 917919 => true, 917920 => true, 917921 => true, 917922 => true, 917923 => true, 917924 => true, 917925 => true, 917926 => true, 917927 => true, 917928 => true, 917929 => true, 917930 => true, 917931 => true, 917932 => true, 917933 => true, 917934 => true, 917935 => true, 917936 => true, 917937 => true, 917938 => true, 917939 => true, 917940 => true, 917941 => true, 917942 => true, 917943 => true, 917944 => true, 917945 => true, 917946 => true, 917947 => true, 917948 => true, 917949 => true, 917950 => true, 917951 => true, 917952 => true, 917953 => true, 917954 => true, 917955 => true, 917956 => true, 917957 => true, 917958 => true, 917959 => true, 917960 => true, 917961 => true, 917962 => true, 917963 => true, 917964 => true, 917965 => true, 917966 => true, 917967 => true, 917968 => true, 917969 => true, 917970 => true, 917971 => true, 917972 => true, 917973 => true, 917974 => true, 917975 => true, 917976 => true, 917977 => true, 917978 => true, 917979 => true, 917980 => true, 917981 => true, 917982 => true, 917983 => true, 917984 => true, 917985 => true, 917986 => true, 917987 => true, 917988 => true, 917989 => true, 917990 => true, 917991 => true, 917992 => true, 917993 => true, 917994 => true, 917995 => true, 917996 => true, 917997 => true, 917998 => true, 917999 => true, ); php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/deviation.php 0000777 00000000120 15251156632 0023545 0 ustar 00 <?php return array( 223 => 'ss', 962 => 'σ', 8204 => '', 8205 => '', ); php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/disallowed_STD3_mapped.php 0000777 00000012627 15251156632 0026054 0 ustar 00 <?php return array( 160 => ' ', 168 => ' ̈', 175 => ' ̄', 180 => ' ́', 184 => ' ̧', 728 => ' ̆', 729 => ' ̇', 730 => ' ̊', 731 => ' ̨', 732 => ' ̃', 733 => ' ̋', 890 => ' ι', 894 => ';', 900 => ' ́', 901 => ' ̈́', 8125 => ' ̓', 8127 => ' ̓', 8128 => ' ͂', 8129 => ' ̈͂', 8141 => ' ̓̀', 8142 => ' ̓́', 8143 => ' ̓͂', 8157 => ' ̔̀', 8158 => ' ̔́', 8159 => ' ̔͂', 8173 => ' ̈̀', 8174 => ' ̈́', 8175 => '`', 8189 => ' ́', 8190 => ' ̔', 8192 => ' ', 8193 => ' ', 8194 => ' ', 8195 => ' ', 8196 => ' ', 8197 => ' ', 8198 => ' ', 8199 => ' ', 8200 => ' ', 8201 => ' ', 8202 => ' ', 8215 => ' ̳', 8239 => ' ', 8252 => '!!', 8254 => ' ̅', 8263 => '??', 8264 => '?!', 8265 => '!?', 8287 => ' ', 8314 => '+', 8316 => '=', 8317 => '(', 8318 => ')', 8330 => '+', 8332 => '=', 8333 => '(', 8334 => ')', 8448 => 'a/c', 8449 => 'a/s', 8453 => 'c/o', 8454 => 'c/u', 9332 => '(1)', 9333 => '(2)', 9334 => '(3)', 9335 => '(4)', 9336 => '(5)', 9337 => '(6)', 9338 => '(7)', 9339 => '(8)', 9340 => '(9)', 9341 => '(10)', 9342 => '(11)', 9343 => '(12)', 9344 => '(13)', 9345 => '(14)', 9346 => '(15)', 9347 => '(16)', 9348 => '(17)', 9349 => '(18)', 9350 => '(19)', 9351 => '(20)', 9372 => '(a)', 9373 => '(b)', 9374 => '(c)', 9375 => '(d)', 9376 => '(e)', 9377 => '(f)', 9378 => '(g)', 9379 => '(h)', 9380 => '(i)', 9381 => '(j)', 9382 => '(k)', 9383 => '(l)', 9384 => '(m)', 9385 => '(n)', 9386 => '(o)', 9387 => '(p)', 9388 => '(q)', 9389 => '(r)', 9390 => '(s)', 9391 => '(t)', 9392 => '(u)', 9393 => '(v)', 9394 => '(w)', 9395 => '(x)', 9396 => '(y)', 9397 => '(z)', 10868 => '::=', 10869 => '==', 10870 => '===', 12288 => ' ', 12443 => ' ゙', 12444 => ' ゚', 12800 => '(ᄀ)', 12801 => '(ᄂ)', 12802 => '(ᄃ)', 12803 => '(ᄅ)', 12804 => '(ᄆ)', 12805 => '(ᄇ)', 12806 => '(ᄉ)', 12807 => '(ᄋ)', 12808 => '(ᄌ)', 12809 => '(ᄎ)', 12810 => '(ᄏ)', 12811 => '(ᄐ)', 12812 => '(ᄑ)', 12813 => '(ᄒ)', 12814 => '(가)', 12815 => '(나)', 12816 => '(다)', 12817 => '(라)', 12818 => '(마)', 12819 => '(바)', 12820 => '(사)', 12821 => '(아)', 12822 => '(자)', 12823 => '(차)', 12824 => '(카)', 12825 => '(타)', 12826 => '(파)', 12827 => '(하)', 12828 => '(주)', 12829 => '(오전)', 12830 => '(오후)', 12832 => '(一)', 12833 => '(二)', 12834 => '(三)', 12835 => '(四)', 12836 => '(五)', 12837 => '(六)', 12838 => '(七)', 12839 => '(八)', 12840 => '(九)', 12841 => '(十)', 12842 => '(月)', 12843 => '(火)', 12844 => '(水)', 12845 => '(木)', 12846 => '(金)', 12847 => '(土)', 12848 => '(日)', 12849 => '(株)', 12850 => '(有)', 12851 => '(社)', 12852 => '(名)', 12853 => '(特)', 12854 => '(財)', 12855 => '(祝)', 12856 => '(労)', 12857 => '(代)', 12858 => '(呼)', 12859 => '(学)', 12860 => '(監)', 12861 => '(企)', 12862 => '(資)', 12863 => '(協)', 12864 => '(祭)', 12865 => '(休)', 12866 => '(自)', 12867 => '(至)', 64297 => '+', 64606 => ' ٌّ', 64607 => ' ٍّ', 64608 => ' َّ', 64609 => ' ُّ', 64610 => ' ِّ', 64611 => ' ّٰ', 65018 => 'صلى الله عليه وسلم', 65019 => 'جل جلاله', 65040 => ',', 65043 => ':', 65044 => ';', 65045 => '!', 65046 => '?', 65075 => '_', 65076 => '_', 65077 => '(', 65078 => ')', 65079 => '{', 65080 => '}', 65095 => '[', 65096 => ']', 65097 => ' ̅', 65098 => ' ̅', 65099 => ' ̅', 65100 => ' ̅', 65101 => '_', 65102 => '_', 65103 => '_', 65104 => ',', 65108 => ';', 65109 => ':', 65110 => '?', 65111 => '!', 65113 => '(', 65114 => ')', 65115 => '{', 65116 => '}', 65119 => '#', 65120 => '&', 65121 => '*', 65122 => '+', 65124 => '<', 65125 => '>', 65126 => '=', 65128 => '\\', 65129 => '$', 65130 => '%', 65131 => '@', 65136 => ' ً', 65138 => ' ٌ', 65140 => ' ٍ', 65142 => ' َ', 65144 => ' ُ', 65146 => ' ِ', 65148 => ' ّ', 65150 => ' ْ', 65281 => '!', 65282 => '"', 65283 => '#', 65284 => '$', 65285 => '%', 65286 => '&', 65287 => '\'', 65288 => '(', 65289 => ')', 65290 => '*', 65291 => '+', 65292 => ',', 65295 => '/', 65306 => ':', 65307 => ';', 65308 => '<', 65309 => '=', 65310 => '>', 65311 => '?', 65312 => '@', 65339 => '[', 65340 => '\\', 65341 => ']', 65342 => '^', 65343 => '_', 65344 => '`', 65371 => '{', 65372 => '|', 65373 => '}', 65374 => '~', 65507 => ' ̄', 127233 => '0,', 127234 => '1,', 127235 => '2,', 127236 => '3,', 127237 => '4,', 127238 => '5,', 127239 => '6,', 127240 => '7,', 127241 => '8,', 127242 => '9,', 127248 => '(a)', 127249 => '(b)', 127250 => '(c)', 127251 => '(d)', 127252 => '(e)', 127253 => '(f)', 127254 => '(g)', 127255 => '(h)', 127256 => '(i)', 127257 => '(j)', 127258 => '(k)', 127259 => '(l)', 127260 => '(m)', 127261 => '(n)', 127262 => '(o)', 127263 => '(p)', 127264 => '(q)', 127265 => '(r)', 127266 => '(s)', 127267 => '(t)', 127268 => '(u)', 127269 => '(v)', 127270 => '(w)', 127271 => '(x)', 127272 => '(y)', 127273 => '(z)', ); php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/disallowed.php 0000777 00000137266 15251156632 0023740 0 ustar 00 <?php return array( 888 => true, 889 => true, 896 => true, 897 => true, 898 => true, 899 => true, 907 => true, 909 => true, 930 => true, 1216 => true, 1328 => true, 1367 => true, 1368 => true, 1419 => true, 1420 => true, 1424 => true, 1480 => true, 1481 => true, 1482 => true, 1483 => true, 1484 => true, 1485 => true, 1486 => true, 1487 => true, 1515 => true, 1516 => true, 1517 => true, 1518 => true, 1525 => true, 1526 => true, 1527 => true, 1528 => true, 1529 => true, 1530 => true, 1531 => true, 1532 => true, 1533 => true, 1534 => true, 1535 => true, 1536 => true, 1537 => true, 1538 => true, 1539 => true, 1540 => true, 1541 => true, 1564 => true, 1757 => true, 1806 => true, 1807 => true, 1867 => true, 1868 => true, 1970 => true, 1971 => true, 1972 => true, 1973 => true, 1974 => true, 1975 => true, 1976 => true, 1977 => true, 1978 => true, 1979 => true, 1980 => true, 1981 => true, 1982 => true, 1983 => true, 2043 => true, 2044 => true, 2094 => true, 2095 => true, 2111 => true, 2140 => true, 2141 => true, 2143 => true, 2155 => true, 2156 => true, 2157 => true, 2158 => true, 2159 => true, 2191 => true, 2192 => true, 2193 => true, 2194 => true, 2195 => true, 2196 => true, 2197 => true, 2198 => true, 2199 => true, 2274 => true, 2436 => true, 2445 => true, 2446 => true, 2449 => true, 2450 => true, 2473 => true, 2481 => true, 2483 => true, 2484 => true, 2485 => true, 2490 => true, 2491 => true, 2501 => true, 2502 => true, 2505 => true, 2506 => true, 2511 => true, 2512 => true, 2513 => true, 2514 => true, 2515 => true, 2516 => true, 2517 => true, 2518 => true, 2520 => true, 2521 => true, 2522 => true, 2523 => true, 2526 => true, 2532 => true, 2533 => true, 2559 => true, 2560 => true, 2564 => true, 2571 => true, 2572 => true, 2573 => true, 2574 => true, 2577 => true, 2578 => true, 2601 => true, 2609 => true, 2612 => true, 2615 => true, 2618 => true, 2619 => true, 2621 => true, 2627 => true, 2628 => true, 2629 => true, 2630 => true, 2633 => true, 2634 => true, 2638 => true, 2639 => true, 2640 => true, 2642 => true, 2643 => true, 2644 => true, 2645 => true, 2646 => true, 2647 => true, 2648 => true, 2653 => true, 2655 => true, 2656 => true, 2657 => true, 2658 => true, 2659 => true, 2660 => true, 2661 => true, 2679 => true, 2680 => true, 2681 => true, 2682 => true, 2683 => true, 2684 => true, 2685 => true, 2686 => true, 2687 => true, 2688 => true, 2692 => true, 2702 => true, 2706 => true, 2729 => true, 2737 => true, 2740 => true, 2746 => true, 2747 => true, 2758 => true, 2762 => true, 2766 => true, 2767 => true, 2769 => true, 2770 => true, 2771 => true, 2772 => true, 2773 => true, 2774 => true, 2775 => true, 2776 => true, 2777 => true, 2778 => true, 2779 => true, 2780 => true, 2781 => true, 2782 => true, 2783 => true, 2788 => true, 2789 => true, 2802 => true, 2803 => true, 2804 => true, 2805 => true, 2806 => true, 2807 => true, 2808 => true, 2816 => true, 2820 => true, 2829 => true, 2830 => true, 2833 => true, 2834 => true, 2857 => true, 2865 => true, 2868 => true, 2874 => true, 2875 => true, 2885 => true, 2886 => true, 2889 => true, 2890 => true, 2894 => true, 2895 => true, 2896 => true, 2897 => true, 2898 => true, 2899 => true, 2900 => true, 2904 => true, 2905 => true, 2906 => true, 2907 => true, 2910 => true, 2916 => true, 2917 => true, 2936 => true, 2937 => true, 2938 => true, 2939 => true, 2940 => true, 2941 => true, 2942 => true, 2943 => true, 2944 => true, 2945 => true, 2948 => true, 2955 => true, 2956 => true, 2957 => true, 2961 => true, 2966 => true, 2967 => true, 2968 => true, 2971 => true, 2973 => true, 2976 => true, 2977 => true, 2978 => true, 2981 => true, 2982 => true, 2983 => true, 2987 => true, 2988 => true, 2989 => true, 3002 => true, 3003 => true, 3004 => true, 3005 => true, 3011 => true, 3012 => true, 3013 => true, 3017 => true, 3022 => true, 3023 => true, 3025 => true, 3026 => true, 3027 => true, 3028 => true, 3029 => true, 3030 => true, 3032 => true, 3033 => true, 3034 => true, 3035 => true, 3036 => true, 3037 => true, 3038 => true, 3039 => true, 3040 => true, 3041 => true, 3042 => true, 3043 => true, 3044 => true, 3045 => true, 3067 => true, 3068 => true, 3069 => true, 3070 => true, 3071 => true, 3085 => true, 3089 => true, 3113 => true, 3130 => true, 3131 => true, 3141 => true, 3145 => true, 3150 => true, 3151 => true, 3152 => true, 3153 => true, 3154 => true, 3155 => true, 3156 => true, 3159 => true, 3163 => true, 3164 => true, 3166 => true, 3167 => true, 3172 => true, 3173 => true, 3184 => true, 3185 => true, 3186 => true, 3187 => true, 3188 => true, 3189 => true, 3190 => true, 3213 => true, 3217 => true, 3241 => true, 3252 => true, 3258 => true, 3259 => true, 3269 => true, 3273 => true, 3278 => true, 3279 => true, 3280 => true, 3281 => true, 3282 => true, 3283 => true, 3284 => true, 3287 => true, 3288 => true, 3289 => true, 3290 => true, 3291 => true, 3292 => true, 3295 => true, 3300 => true, 3301 => true, 3312 => true, 3315 => true, 3316 => true, 3317 => true, 3318 => true, 3319 => true, 3320 => true, 3321 => true, 3322 => true, 3323 => true, 3324 => true, 3325 => true, 3326 => true, 3327 => true, 3341 => true, 3345 => true, 3397 => true, 3401 => true, 3408 => true, 3409 => true, 3410 => true, 3411 => true, 3428 => true, 3429 => true, 3456 => true, 3460 => true, 3479 => true, 3480 => true, 3481 => true, 3506 => true, 3516 => true, 3518 => true, 3519 => true, 3527 => true, 3528 => true, 3529 => true, 3531 => true, 3532 => true, 3533 => true, 3534 => true, 3541 => true, 3543 => true, 3552 => true, 3553 => true, 3554 => true, 3555 => true, 3556 => true, 3557 => true, 3568 => true, 3569 => true, 3573 => true, 3574 => true, 3575 => true, 3576 => true, 3577 => true, 3578 => true, 3579 => true, 3580 => true, 3581 => true, 3582 => true, 3583 => true, 3584 => true, 3643 => true, 3644 => true, 3645 => true, 3646 => true, 3715 => true, 3717 => true, 3723 => true, 3748 => true, 3750 => true, 3774 => true, 3775 => true, 3781 => true, 3783 => true, 3790 => true, 3791 => true, 3802 => true, 3803 => true, 3912 => true, 3949 => true, 3950 => true, 3951 => true, 3952 => true, 3992 => true, 4029 => true, 4045 => true, 4294 => true, 4296 => true, 4297 => true, 4298 => true, 4299 => true, 4300 => true, 4302 => true, 4303 => true, 4447 => true, 4448 => true, 4681 => true, 4686 => true, 4687 => true, 4695 => true, 4697 => true, 4702 => true, 4703 => true, 4745 => true, 4750 => true, 4751 => true, 4785 => true, 4790 => true, 4791 => true, 4799 => true, 4801 => true, 4806 => true, 4807 => true, 4823 => true, 4881 => true, 4886 => true, 4887 => true, 4955 => true, 4956 => true, 4989 => true, 4990 => true, 4991 => true, 5018 => true, 5019 => true, 5020 => true, 5021 => true, 5022 => true, 5023 => true, 5110 => true, 5111 => true, 5118 => true, 5119 => true, 5760 => true, 5789 => true, 5790 => true, 5791 => true, 5881 => true, 5882 => true, 5883 => true, 5884 => true, 5885 => true, 5886 => true, 5887 => true, 5910 => true, 5911 => true, 5912 => true, 5913 => true, 5914 => true, 5915 => true, 5916 => true, 5917 => true, 5918 => true, 5943 => true, 5944 => true, 5945 => true, 5946 => true, 5947 => true, 5948 => true, 5949 => true, 5950 => true, 5951 => true, 5972 => true, 5973 => true, 5974 => true, 5975 => true, 5976 => true, 5977 => true, 5978 => true, 5979 => true, 5980 => true, 5981 => true, 5982 => true, 5983 => true, 5997 => true, 6001 => true, 6004 => true, 6005 => true, 6006 => true, 6007 => true, 6008 => true, 6009 => true, 6010 => true, 6011 => true, 6012 => true, 6013 => true, 6014 => true, 6015 => true, 6068 => true, 6069 => true, 6110 => true, 6111 => true, 6122 => true, 6123 => true, 6124 => true, 6125 => true, 6126 => true, 6127 => true, 6138 => true, 6139 => true, 6140 => true, 6141 => true, 6142 => true, 6143 => true, 6150 => true, 6158 => true, 6170 => true, 6171 => true, 6172 => true, 6173 => true, 6174 => true, 6175 => true, 6265 => true, 6266 => true, 6267 => true, 6268 => true, 6269 => true, 6270 => true, 6271 => true, 6315 => true, 6316 => true, 6317 => true, 6318 => true, 6319 => true, 6390 => true, 6391 => true, 6392 => true, 6393 => true, 6394 => true, 6395 => true, 6396 => true, 6397 => true, 6398 => true, 6399 => true, 6431 => true, 6444 => true, 6445 => true, 6446 => true, 6447 => true, 6460 => true, 6461 => true, 6462 => true, 6463 => true, 6465 => true, 6466 => true, 6467 => true, 6510 => true, 6511 => true, 6517 => true, 6518 => true, 6519 => true, 6520 => true, 6521 => true, 6522 => true, 6523 => true, 6524 => true, 6525 => true, 6526 => true, 6527 => true, 6572 => true, 6573 => true, 6574 => true, 6575 => true, 6602 => true, 6603 => true, 6604 => true, 6605 => true, 6606 => true, 6607 => true, 6619 => true, 6620 => true, 6621 => true, 6684 => true, 6685 => true, 6751 => true, 6781 => true, 6782 => true, 6794 => true, 6795 => true, 6796 => true, 6797 => true, 6798 => true, 6799 => true, 6810 => true, 6811 => true, 6812 => true, 6813 => true, 6814 => true, 6815 => true, 6830 => true, 6831 => true, 6989 => true, 6990 => true, 6991 => true, 7039 => true, 7156 => true, 7157 => true, 7158 => true, 7159 => true, 7160 => true, 7161 => true, 7162 => true, 7163 => true, 7224 => true, 7225 => true, 7226 => true, 7242 => true, 7243 => true, 7244 => true, 7305 => true, 7306 => true, 7307 => true, 7308 => true, 7309 => true, 7310 => true, 7311 => true, 7355 => true, 7356 => true, 7368 => true, 7369 => true, 7370 => true, 7371 => true, 7372 => true, 7373 => true, 7374 => true, 7375 => true, 7419 => true, 7420 => true, 7421 => true, 7422 => true, 7423 => true, 7958 => true, 7959 => true, 7966 => true, 7967 => true, 8006 => true, 8007 => true, 8014 => true, 8015 => true, 8024 => true, 8026 => true, 8028 => true, 8030 => true, 8062 => true, 8063 => true, 8117 => true, 8133 => true, 8148 => true, 8149 => true, 8156 => true, 8176 => true, 8177 => true, 8181 => true, 8191 => true, 8206 => true, 8207 => true, 8228 => true, 8229 => true, 8230 => true, 8232 => true, 8233 => true, 8234 => true, 8235 => true, 8236 => true, 8237 => true, 8238 => true, 8289 => true, 8290 => true, 8291 => true, 8293 => true, 8294 => true, 8295 => true, 8296 => true, 8297 => true, 8298 => true, 8299 => true, 8300 => true, 8301 => true, 8302 => true, 8303 => true, 8306 => true, 8307 => true, 8335 => true, 8349 => true, 8350 => true, 8351 => true, 8385 => true, 8386 => true, 8387 => true, 8388 => true, 8389 => true, 8390 => true, 8391 => true, 8392 => true, 8393 => true, 8394 => true, 8395 => true, 8396 => true, 8397 => true, 8398 => true, 8399 => true, 8433 => true, 8434 => true, 8435 => true, 8436 => true, 8437 => true, 8438 => true, 8439 => true, 8440 => true, 8441 => true, 8442 => true, 8443 => true, 8444 => true, 8445 => true, 8446 => true, 8447 => true, 8498 => true, 8579 => true, 8588 => true, 8589 => true, 8590 => true, 8591 => true, 9255 => true, 9256 => true, 9257 => true, 9258 => true, 9259 => true, 9260 => true, 9261 => true, 9262 => true, 9263 => true, 9264 => true, 9265 => true, 9266 => true, 9267 => true, 9268 => true, 9269 => true, 9270 => true, 9271 => true, 9272 => true, 9273 => true, 9274 => true, 9275 => true, 9276 => true, 9277 => true, 9278 => true, 9279 => true, 9291 => true, 9292 => true, 9293 => true, 9294 => true, 9295 => true, 9296 => true, 9297 => true, 9298 => true, 9299 => true, 9300 => true, 9301 => true, 9302 => true, 9303 => true, 9304 => true, 9305 => true, 9306 => true, 9307 => true, 9308 => true, 9309 => true, 9310 => true, 9311 => true, 9352 => true, 9353 => true, 9354 => true, 9355 => true, 9356 => true, 9357 => true, 9358 => true, 9359 => true, 9360 => true, 9361 => true, 9362 => true, 9363 => true, 9364 => true, 9365 => true, 9366 => true, 9367 => true, 9368 => true, 9369 => true, 9370 => true, 9371 => true, 11124 => true, 11125 => true, 11158 => true, 11508 => true, 11509 => true, 11510 => true, 11511 => true, 11512 => true, 11558 => true, 11560 => true, 11561 => true, 11562 => true, 11563 => true, 11564 => true, 11566 => true, 11567 => true, 11624 => true, 11625 => true, 11626 => true, 11627 => true, 11628 => true, 11629 => true, 11630 => true, 11633 => true, 11634 => true, 11635 => true, 11636 => true, 11637 => true, 11638 => true, 11639 => true, 11640 => true, 11641 => true, 11642 => true, 11643 => true, 11644 => true, 11645 => true, 11646 => true, 11671 => true, 11672 => true, 11673 => true, 11674 => true, 11675 => true, 11676 => true, 11677 => true, 11678 => true, 11679 => true, 11687 => true, 11695 => true, 11703 => true, 11711 => true, 11719 => true, 11727 => true, 11735 => true, 11743 => true, 11930 => true, 12020 => true, 12021 => true, 12022 => true, 12023 => true, 12024 => true, 12025 => true, 12026 => true, 12027 => true, 12028 => true, 12029 => true, 12030 => true, 12031 => true, 12246 => true, 12247 => true, 12248 => true, 12249 => true, 12250 => true, 12251 => true, 12252 => true, 12253 => true, 12254 => true, 12255 => true, 12256 => true, 12257 => true, 12258 => true, 12259 => true, 12260 => true, 12261 => true, 12262 => true, 12263 => true, 12264 => true, 12265 => true, 12266 => true, 12267 => true, 12268 => true, 12269 => true, 12270 => true, 12271 => true, 12272 => true, 12273 => true, 12274 => true, 12275 => true, 12276 => true, 12277 => true, 12278 => true, 12279 => true, 12280 => true, 12281 => true, 12282 => true, 12283 => true, 12284 => true, 12285 => true, 12286 => true, 12287 => true, 12352 => true, 12439 => true, 12440 => true, 12544 => true, 12545 => true, 12546 => true, 12547 => true, 12548 => true, 12592 => true, 12644 => true, 12687 => true, 12772 => true, 12773 => true, 12774 => true, 12775 => true, 12776 => true, 12777 => true, 12778 => true, 12779 => true, 12780 => true, 12781 => true, 12782 => true, 12783 => true, 12831 => true, 13250 => true, 13255 => true, 13272 => true, 42125 => true, 42126 => true, 42127 => true, 42183 => true, 42184 => true, 42185 => true, 42186 => true, 42187 => true, 42188 => true, 42189 => true, 42190 => true, 42191 => true, 42540 => true, 42541 => true, 42542 => true, 42543 => true, 42544 => true, 42545 => true, 42546 => true, 42547 => true, 42548 => true, 42549 => true, 42550 => true, 42551 => true, 42552 => true, 42553 => true, 42554 => true, 42555 => true, 42556 => true, 42557 => true, 42558 => true, 42559 => true, 42744 => true, 42745 => true, 42746 => true, 42747 => true, 42748 => true, 42749 => true, 42750 => true, 42751 => true, 42955 => true, 42956 => true, 42957 => true, 42958 => true, 42959 => true, 42962 => true, 42964 => true, 42970 => true, 42971 => true, 42972 => true, 42973 => true, 42974 => true, 42975 => true, 42976 => true, 42977 => true, 42978 => true, 42979 => true, 42980 => true, 42981 => true, 42982 => true, 42983 => true, 42984 => true, 42985 => true, 42986 => true, 42987 => true, 42988 => true, 42989 => true, 42990 => true, 42991 => true, 42992 => true, 42993 => true, 43053 => true, 43054 => true, 43055 => true, 43066 => true, 43067 => true, 43068 => true, 43069 => true, 43070 => true, 43071 => true, 43128 => true, 43129 => true, 43130 => true, 43131 => true, 43132 => true, 43133 => true, 43134 => true, 43135 => true, 43206 => true, 43207 => true, 43208 => true, 43209 => true, 43210 => true, 43211 => true, 43212 => true, 43213 => true, 43226 => true, 43227 => true, 43228 => true, 43229 => true, 43230 => true, 43231 => true, 43348 => true, 43349 => true, 43350 => true, 43351 => true, 43352 => true, 43353 => true, 43354 => true, 43355 => true, 43356 => true, 43357 => true, 43358 => true, 43389 => true, 43390 => true, 43391 => true, 43470 => true, 43482 => true, 43483 => true, 43484 => true, 43485 => true, 43519 => true, 43575 => true, 43576 => true, 43577 => true, 43578 => true, 43579 => true, 43580 => true, 43581 => true, 43582 => true, 43583 => true, 43598 => true, 43599 => true, 43610 => true, 43611 => true, 43715 => true, 43716 => true, 43717 => true, 43718 => true, 43719 => true, 43720 => true, 43721 => true, 43722 => true, 43723 => true, 43724 => true, 43725 => true, 43726 => true, 43727 => true, 43728 => true, 43729 => true, 43730 => true, 43731 => true, 43732 => true, 43733 => true, 43734 => true, 43735 => true, 43736 => true, 43737 => true, 43738 => true, 43767 => true, 43768 => true, 43769 => true, 43770 => true, 43771 => true, 43772 => true, 43773 => true, 43774 => true, 43775 => true, 43776 => true, 43783 => true, 43784 => true, 43791 => true, 43792 => true, 43799 => true, 43800 => true, 43801 => true, 43802 => true, 43803 => true, 43804 => true, 43805 => true, 43806 => true, 43807 => true, 43815 => true, 43823 => true, 43884 => true, 43885 => true, 43886 => true, 43887 => true, 44014 => true, 44015 => true, 44026 => true, 44027 => true, 44028 => true, 44029 => true, 44030 => true, 44031 => true, 55204 => true, 55205 => true, 55206 => true, 55207 => true, 55208 => true, 55209 => true, 55210 => true, 55211 => true, 55212 => true, 55213 => true, 55214 => true, 55215 => true, 55239 => true, 55240 => true, 55241 => true, 55242 => true, 55292 => true, 55293 => true, 55294 => true, 55295 => true, 64110 => true, 64111 => true, 64263 => true, 64264 => true, 64265 => true, 64266 => true, 64267 => true, 64268 => true, 64269 => true, 64270 => true, 64271 => true, 64272 => true, 64273 => true, 64274 => true, 64280 => true, 64281 => true, 64282 => true, 64283 => true, 64284 => true, 64311 => true, 64317 => true, 64319 => true, 64322 => true, 64325 => true, 64451 => true, 64452 => true, 64453 => true, 64454 => true, 64455 => true, 64456 => true, 64457 => true, 64458 => true, 64459 => true, 64460 => true, 64461 => true, 64462 => true, 64463 => true, 64464 => true, 64465 => true, 64466 => true, 64912 => true, 64913 => true, 64968 => true, 64969 => true, 64970 => true, 64971 => true, 64972 => true, 64973 => true, 64974 => true, 65042 => true, 65049 => true, 65050 => true, 65051 => true, 65052 => true, 65053 => true, 65054 => true, 65055 => true, 65072 => true, 65106 => true, 65107 => true, 65127 => true, 65132 => true, 65133 => true, 65134 => true, 65135 => true, 65141 => true, 65277 => true, 65278 => true, 65280 => true, 65440 => true, 65471 => true, 65472 => true, 65473 => true, 65480 => true, 65481 => true, 65488 => true, 65489 => true, 65496 => true, 65497 => true, 65501 => true, 65502 => true, 65503 => true, 65511 => true, 65519 => true, 65520 => true, 65521 => true, 65522 => true, 65523 => true, 65524 => true, 65525 => true, 65526 => true, 65527 => true, 65528 => true, 65529 => true, 65530 => true, 65531 => true, 65532 => true, 65533 => true, 65534 => true, 65535 => true, 65548 => true, 65575 => true, 65595 => true, 65598 => true, 65614 => true, 65615 => true, 65787 => true, 65788 => true, 65789 => true, 65790 => true, 65791 => true, 65795 => true, 65796 => true, 65797 => true, 65798 => true, 65844 => true, 65845 => true, 65846 => true, 65935 => true, 65949 => true, 65950 => true, 65951 => true, 66205 => true, 66206 => true, 66207 => true, 66257 => true, 66258 => true, 66259 => true, 66260 => true, 66261 => true, 66262 => true, 66263 => true, 66264 => true, 66265 => true, 66266 => true, 66267 => true, 66268 => true, 66269 => true, 66270 => true, 66271 => true, 66300 => true, 66301 => true, 66302 => true, 66303 => true, 66340 => true, 66341 => true, 66342 => true, 66343 => true, 66344 => true, 66345 => true, 66346 => true, 66347 => true, 66348 => true, 66379 => true, 66380 => true, 66381 => true, 66382 => true, 66383 => true, 66427 => true, 66428 => true, 66429 => true, 66430 => true, 66431 => true, 66462 => true, 66500 => true, 66501 => true, 66502 => true, 66503 => true, 66718 => true, 66719 => true, 66730 => true, 66731 => true, 66732 => true, 66733 => true, 66734 => true, 66735 => true, 66772 => true, 66773 => true, 66774 => true, 66775 => true, 66812 => true, 66813 => true, 66814 => true, 66815 => true, 66856 => true, 66857 => true, 66858 => true, 66859 => true, 66860 => true, 66861 => true, 66862 => true, 66863 => true, 66916 => true, 66917 => true, 66918 => true, 66919 => true, 66920 => true, 66921 => true, 66922 => true, 66923 => true, 66924 => true, 66925 => true, 66926 => true, 66939 => true, 66955 => true, 66963 => true, 66966 => true, 66978 => true, 66994 => true, 67002 => true, 67383 => true, 67384 => true, 67385 => true, 67386 => true, 67387 => true, 67388 => true, 67389 => true, 67390 => true, 67391 => true, 67414 => true, 67415 => true, 67416 => true, 67417 => true, 67418 => true, 67419 => true, 67420 => true, 67421 => true, 67422 => true, 67423 => true, 67432 => true, 67433 => true, 67434 => true, 67435 => true, 67436 => true, 67437 => true, 67438 => true, 67439 => true, 67440 => true, 67441 => true, 67442 => true, 67443 => true, 67444 => true, 67445 => true, 67446 => true, 67447 => true, 67448 => true, 67449 => true, 67450 => true, 67451 => true, 67452 => true, 67453 => true, 67454 => true, 67455 => true, 67462 => true, 67505 => true, 67590 => true, 67591 => true, 67593 => true, 67638 => true, 67641 => true, 67642 => true, 67643 => true, 67645 => true, 67646 => true, 67670 => true, 67743 => true, 67744 => true, 67745 => true, 67746 => true, 67747 => true, 67748 => true, 67749 => true, 67750 => true, 67827 => true, 67830 => true, 67831 => true, 67832 => true, 67833 => true, 67834 => true, 67868 => true, 67869 => true, 67870 => true, 67898 => true, 67899 => true, 67900 => true, 67901 => true, 67902 => true, 68024 => true, 68025 => true, 68026 => true, 68027 => true, 68048 => true, 68049 => true, 68100 => true, 68103 => true, 68104 => true, 68105 => true, 68106 => true, 68107 => true, 68116 => true, 68120 => true, 68150 => true, 68151 => true, 68155 => true, 68156 => true, 68157 => true, 68158 => true, 68169 => true, 68170 => true, 68171 => true, 68172 => true, 68173 => true, 68174 => true, 68175 => true, 68185 => true, 68186 => true, 68187 => true, 68188 => true, 68189 => true, 68190 => true, 68191 => true, 68327 => true, 68328 => true, 68329 => true, 68330 => true, 68343 => true, 68344 => true, 68345 => true, 68346 => true, 68347 => true, 68348 => true, 68349 => true, 68350 => true, 68351 => true, 68406 => true, 68407 => true, 68408 => true, 68438 => true, 68439 => true, 68467 => true, 68468 => true, 68469 => true, 68470 => true, 68471 => true, 68498 => true, 68499 => true, 68500 => true, 68501 => true, 68502 => true, 68503 => true, 68504 => true, 68509 => true, 68510 => true, 68511 => true, 68512 => true, 68513 => true, 68514 => true, 68515 => true, 68516 => true, 68517 => true, 68518 => true, 68519 => true, 68520 => true, 68787 => true, 68788 => true, 68789 => true, 68790 => true, 68791 => true, 68792 => true, 68793 => true, 68794 => true, 68795 => true, 68796 => true, 68797 => true, 68798 => true, 68799 => true, 68851 => true, 68852 => true, 68853 => true, 68854 => true, 68855 => true, 68856 => true, 68857 => true, 68904 => true, 68905 => true, 68906 => true, 68907 => true, 68908 => true, 68909 => true, 68910 => true, 68911 => true, 69247 => true, 69290 => true, 69294 => true, 69295 => true, 69416 => true, 69417 => true, 69418 => true, 69419 => true, 69420 => true, 69421 => true, 69422 => true, 69423 => true, 69466 => true, 69467 => true, 69468 => true, 69469 => true, 69470 => true, 69471 => true, 69472 => true, 69473 => true, 69474 => true, 69475 => true, 69476 => true, 69477 => true, 69478 => true, 69479 => true, 69480 => true, 69481 => true, 69482 => true, 69483 => true, 69484 => true, 69485 => true, 69486 => true, 69487 => true, 69580 => true, 69581 => true, 69582 => true, 69583 => true, 69584 => true, 69585 => true, 69586 => true, 69587 => true, 69588 => true, 69589 => true, 69590 => true, 69591 => true, 69592 => true, 69593 => true, 69594 => true, 69595 => true, 69596 => true, 69597 => true, 69598 => true, 69599 => true, 69623 => true, 69624 => true, 69625 => true, 69626 => true, 69627 => true, 69628 => true, 69629 => true, 69630 => true, 69631 => true, 69710 => true, 69711 => true, 69712 => true, 69713 => true, 69750 => true, 69751 => true, 69752 => true, 69753 => true, 69754 => true, 69755 => true, 69756 => true, 69757 => true, 69758 => true, 69821 => true, 69827 => true, 69828 => true, 69829 => true, 69830 => true, 69831 => true, 69832 => true, 69833 => true, 69834 => true, 69835 => true, 69836 => true, 69837 => true, 69838 => true, 69839 => true, 69865 => true, 69866 => true, 69867 => true, 69868 => true, 69869 => true, 69870 => true, 69871 => true, 69882 => true, 69883 => true, 69884 => true, 69885 => true, 69886 => true, 69887 => true, 69941 => true, 69960 => true, 69961 => true, 69962 => true, 69963 => true, 69964 => true, 69965 => true, 69966 => true, 69967 => true, 70007 => true, 70008 => true, 70009 => true, 70010 => true, 70011 => true, 70012 => true, 70013 => true, 70014 => true, 70015 => true, 70112 => true, 70133 => true, 70134 => true, 70135 => true, 70136 => true, 70137 => true, 70138 => true, 70139 => true, 70140 => true, 70141 => true, 70142 => true, 70143 => true, 70162 => true, 70279 => true, 70281 => true, 70286 => true, 70302 => true, 70314 => true, 70315 => true, 70316 => true, 70317 => true, 70318 => true, 70319 => true, 70379 => true, 70380 => true, 70381 => true, 70382 => true, 70383 => true, 70394 => true, 70395 => true, 70396 => true, 70397 => true, 70398 => true, 70399 => true, 70404 => true, 70413 => true, 70414 => true, 70417 => true, 70418 => true, 70441 => true, 70449 => true, 70452 => true, 70458 => true, 70469 => true, 70470 => true, 70473 => true, 70474 => true, 70478 => true, 70479 => true, 70481 => true, 70482 => true, 70483 => true, 70484 => true, 70485 => true, 70486 => true, 70488 => true, 70489 => true, 70490 => true, 70491 => true, 70492 => true, 70500 => true, 70501 => true, 70509 => true, 70510 => true, 70511 => true, 70748 => true, 70754 => true, 70755 => true, 70756 => true, 70757 => true, 70758 => true, 70759 => true, 70760 => true, 70761 => true, 70762 => true, 70763 => true, 70764 => true, 70765 => true, 70766 => true, 70767 => true, 70768 => true, 70769 => true, 70770 => true, 70771 => true, 70772 => true, 70773 => true, 70774 => true, 70775 => true, 70776 => true, 70777 => true, 70778 => true, 70779 => true, 70780 => true, 70781 => true, 70782 => true, 70783 => true, 70856 => true, 70857 => true, 70858 => true, 70859 => true, 70860 => true, 70861 => true, 70862 => true, 70863 => true, 71094 => true, 71095 => true, 71237 => true, 71238 => true, 71239 => true, 71240 => true, 71241 => true, 71242 => true, 71243 => true, 71244 => true, 71245 => true, 71246 => true, 71247 => true, 71258 => true, 71259 => true, 71260 => true, 71261 => true, 71262 => true, 71263 => true, 71277 => true, 71278 => true, 71279 => true, 71280 => true, 71281 => true, 71282 => true, 71283 => true, 71284 => true, 71285 => true, 71286 => true, 71287 => true, 71288 => true, 71289 => true, 71290 => true, 71291 => true, 71292 => true, 71293 => true, 71294 => true, 71295 => true, 71354 => true, 71355 => true, 71356 => true, 71357 => true, 71358 => true, 71359 => true, 71451 => true, 71452 => true, 71468 => true, 71469 => true, 71470 => true, 71471 => true, 71923 => true, 71924 => true, 71925 => true, 71926 => true, 71927 => true, 71928 => true, 71929 => true, 71930 => true, 71931 => true, 71932 => true, 71933 => true, 71934 => true, 71943 => true, 71944 => true, 71946 => true, 71947 => true, 71956 => true, 71959 => true, 71990 => true, 71993 => true, 71994 => true, 72007 => true, 72008 => true, 72009 => true, 72010 => true, 72011 => true, 72012 => true, 72013 => true, 72014 => true, 72015 => true, 72104 => true, 72105 => true, 72152 => true, 72153 => true, 72165 => true, 72166 => true, 72167 => true, 72168 => true, 72169 => true, 72170 => true, 72171 => true, 72172 => true, 72173 => true, 72174 => true, 72175 => true, 72176 => true, 72177 => true, 72178 => true, 72179 => true, 72180 => true, 72181 => true, 72182 => true, 72183 => true, 72184 => true, 72185 => true, 72186 => true, 72187 => true, 72188 => true, 72189 => true, 72190 => true, 72191 => true, 72264 => true, 72265 => true, 72266 => true, 72267 => true, 72268 => true, 72269 => true, 72270 => true, 72271 => true, 72355 => true, 72356 => true, 72357 => true, 72358 => true, 72359 => true, 72360 => true, 72361 => true, 72362 => true, 72363 => true, 72364 => true, 72365 => true, 72366 => true, 72367 => true, 72713 => true, 72759 => true, 72774 => true, 72775 => true, 72776 => true, 72777 => true, 72778 => true, 72779 => true, 72780 => true, 72781 => true, 72782 => true, 72783 => true, 72813 => true, 72814 => true, 72815 => true, 72848 => true, 72849 => true, 72872 => true, 72967 => true, 72970 => true, 73015 => true, 73016 => true, 73017 => true, 73019 => true, 73022 => true, 73032 => true, 73033 => true, 73034 => true, 73035 => true, 73036 => true, 73037 => true, 73038 => true, 73039 => true, 73050 => true, 73051 => true, 73052 => true, 73053 => true, 73054 => true, 73055 => true, 73062 => true, 73065 => true, 73103 => true, 73106 => true, 73113 => true, 73114 => true, 73115 => true, 73116 => true, 73117 => true, 73118 => true, 73119 => true, 73649 => true, 73650 => true, 73651 => true, 73652 => true, 73653 => true, 73654 => true, 73655 => true, 73656 => true, 73657 => true, 73658 => true, 73659 => true, 73660 => true, 73661 => true, 73662 => true, 73663 => true, 73714 => true, 73715 => true, 73716 => true, 73717 => true, 73718 => true, 73719 => true, 73720 => true, 73721 => true, 73722 => true, 73723 => true, 73724 => true, 73725 => true, 73726 => true, 74863 => true, 74869 => true, 74870 => true, 74871 => true, 74872 => true, 74873 => true, 74874 => true, 74875 => true, 74876 => true, 74877 => true, 74878 => true, 74879 => true, 77811 => true, 77812 => true, 77813 => true, 77814 => true, 77815 => true, 77816 => true, 77817 => true, 77818 => true, 77819 => true, 77820 => true, 77821 => true, 77822 => true, 77823 => true, 78895 => true, 78896 => true, 78897 => true, 78898 => true, 78899 => true, 78900 => true, 78901 => true, 78902 => true, 78903 => true, 78904 => true, 92729 => true, 92730 => true, 92731 => true, 92732 => true, 92733 => true, 92734 => true, 92735 => true, 92767 => true, 92778 => true, 92779 => true, 92780 => true, 92781 => true, 92863 => true, 92874 => true, 92875 => true, 92876 => true, 92877 => true, 92878 => true, 92879 => true, 92910 => true, 92911 => true, 92918 => true, 92919 => true, 92920 => true, 92921 => true, 92922 => true, 92923 => true, 92924 => true, 92925 => true, 92926 => true, 92927 => true, 92998 => true, 92999 => true, 93000 => true, 93001 => true, 93002 => true, 93003 => true, 93004 => true, 93005 => true, 93006 => true, 93007 => true, 93018 => true, 93026 => true, 93048 => true, 93049 => true, 93050 => true, 93051 => true, 93052 => true, 94027 => true, 94028 => true, 94029 => true, 94030 => true, 94088 => true, 94089 => true, 94090 => true, 94091 => true, 94092 => true, 94093 => true, 94094 => true, 94181 => true, 94182 => true, 94183 => true, 94184 => true, 94185 => true, 94186 => true, 94187 => true, 94188 => true, 94189 => true, 94190 => true, 94191 => true, 94194 => true, 94195 => true, 94196 => true, 94197 => true, 94198 => true, 94199 => true, 94200 => true, 94201 => true, 94202 => true, 94203 => true, 94204 => true, 94205 => true, 94206 => true, 94207 => true, 100344 => true, 100345 => true, 100346 => true, 100347 => true, 100348 => true, 100349 => true, 100350 => true, 100351 => true, 110580 => true, 110588 => true, 110591 => true, 110931 => true, 110932 => true, 110933 => true, 110934 => true, 110935 => true, 110936 => true, 110937 => true, 110938 => true, 110939 => true, 110940 => true, 110941 => true, 110942 => true, 110943 => true, 110944 => true, 110945 => true, 110946 => true, 110947 => true, 110952 => true, 110953 => true, 110954 => true, 110955 => true, 110956 => true, 110957 => true, 110958 => true, 110959 => true, 113771 => true, 113772 => true, 113773 => true, 113774 => true, 113775 => true, 113789 => true, 113790 => true, 113791 => true, 113801 => true, 113802 => true, 113803 => true, 113804 => true, 113805 => true, 113806 => true, 113807 => true, 113818 => true, 113819 => true, 118574 => true, 118575 => true, 118599 => true, 118600 => true, 118601 => true, 118602 => true, 118603 => true, 118604 => true, 118605 => true, 118606 => true, 118607 => true, 119030 => true, 119031 => true, 119032 => true, 119033 => true, 119034 => true, 119035 => true, 119036 => true, 119037 => true, 119038 => true, 119039 => true, 119079 => true, 119080 => true, 119155 => true, 119156 => true, 119157 => true, 119158 => true, 119159 => true, 119160 => true, 119161 => true, 119162 => true, 119275 => true, 119276 => true, 119277 => true, 119278 => true, 119279 => true, 119280 => true, 119281 => true, 119282 => true, 119283 => true, 119284 => true, 119285 => true, 119286 => true, 119287 => true, 119288 => true, 119289 => true, 119290 => true, 119291 => true, 119292 => true, 119293 => true, 119294 => true, 119295 => true, 119540 => true, 119541 => true, 119542 => true, 119543 => true, 119544 => true, 119545 => true, 119546 => true, 119547 => true, 119548 => true, 119549 => true, 119550 => true, 119551 => true, 119639 => true, 119640 => true, 119641 => true, 119642 => true, 119643 => true, 119644 => true, 119645 => true, 119646 => true, 119647 => true, 119893 => true, 119965 => true, 119968 => true, 119969 => true, 119971 => true, 119972 => true, 119975 => true, 119976 => true, 119981 => true, 119994 => true, 119996 => true, 120004 => true, 120070 => true, 120075 => true, 120076 => true, 120085 => true, 120093 => true, 120122 => true, 120127 => true, 120133 => true, 120135 => true, 120136 => true, 120137 => true, 120145 => true, 120486 => true, 120487 => true, 120780 => true, 120781 => true, 121484 => true, 121485 => true, 121486 => true, 121487 => true, 121488 => true, 121489 => true, 121490 => true, 121491 => true, 121492 => true, 121493 => true, 121494 => true, 121495 => true, 121496 => true, 121497 => true, 121498 => true, 121504 => true, 122887 => true, 122905 => true, 122906 => true, 122914 => true, 122917 => true, 123181 => true, 123182 => true, 123183 => true, 123198 => true, 123199 => true, 123210 => true, 123211 => true, 123212 => true, 123213 => true, 123567 => true, 123568 => true, 123569 => true, 123570 => true, 123571 => true, 123572 => true, 123573 => true, 123574 => true, 123575 => true, 123576 => true, 123577 => true, 123578 => true, 123579 => true, 123580 => true, 123581 => true, 123582 => true, 123583 => true, 123642 => true, 123643 => true, 123644 => true, 123645 => true, 123646 => true, 124903 => true, 124908 => true, 124911 => true, 124927 => true, 125125 => true, 125126 => true, 125260 => true, 125261 => true, 125262 => true, 125263 => true, 125274 => true, 125275 => true, 125276 => true, 125277 => true, 126468 => true, 126496 => true, 126499 => true, 126501 => true, 126502 => true, 126504 => true, 126515 => true, 126520 => true, 126522 => true, 126524 => true, 126525 => true, 126526 => true, 126527 => true, 126528 => true, 126529 => true, 126531 => true, 126532 => true, 126533 => true, 126534 => true, 126536 => true, 126538 => true, 126540 => true, 126544 => true, 126547 => true, 126549 => true, 126550 => true, 126552 => true, 126554 => true, 126556 => true, 126558 => true, 126560 => true, 126563 => true, 126565 => true, 126566 => true, 126571 => true, 126579 => true, 126584 => true, 126589 => true, 126591 => true, 126602 => true, 126620 => true, 126621 => true, 126622 => true, 126623 => true, 126624 => true, 126628 => true, 126634 => true, 127020 => true, 127021 => true, 127022 => true, 127023 => true, 127124 => true, 127125 => true, 127126 => true, 127127 => true, 127128 => true, 127129 => true, 127130 => true, 127131 => true, 127132 => true, 127133 => true, 127134 => true, 127135 => true, 127151 => true, 127152 => true, 127168 => true, 127184 => true, 127222 => true, 127223 => true, 127224 => true, 127225 => true, 127226 => true, 127227 => true, 127228 => true, 127229 => true, 127230 => true, 127231 => true, 127232 => true, 127491 => true, 127492 => true, 127493 => true, 127494 => true, 127495 => true, 127496 => true, 127497 => true, 127498 => true, 127499 => true, 127500 => true, 127501 => true, 127502 => true, 127503 => true, 127548 => true, 127549 => true, 127550 => true, 127551 => true, 127561 => true, 127562 => true, 127563 => true, 127564 => true, 127565 => true, 127566 => true, 127567 => true, 127570 => true, 127571 => true, 127572 => true, 127573 => true, 127574 => true, 127575 => true, 127576 => true, 127577 => true, 127578 => true, 127579 => true, 127580 => true, 127581 => true, 127582 => true, 127583 => true, 128728 => true, 128729 => true, 128730 => true, 128731 => true, 128732 => true, 128749 => true, 128750 => true, 128751 => true, 128765 => true, 128766 => true, 128767 => true, 128884 => true, 128885 => true, 128886 => true, 128887 => true, 128888 => true, 128889 => true, 128890 => true, 128891 => true, 128892 => true, 128893 => true, 128894 => true, 128895 => true, 128985 => true, 128986 => true, 128987 => true, 128988 => true, 128989 => true, 128990 => true, 128991 => true, 129004 => true, 129005 => true, 129006 => true, 129007 => true, 129009 => true, 129010 => true, 129011 => true, 129012 => true, 129013 => true, 129014 => true, 129015 => true, 129016 => true, 129017 => true, 129018 => true, 129019 => true, 129020 => true, 129021 => true, 129022 => true, 129023 => true, 129036 => true, 129037 => true, 129038 => true, 129039 => true, 129096 => true, 129097 => true, 129098 => true, 129099 => true, 129100 => true, 129101 => true, 129102 => true, 129103 => true, 129114 => true, 129115 => true, 129116 => true, 129117 => true, 129118 => true, 129119 => true, 129160 => true, 129161 => true, 129162 => true, 129163 => true, 129164 => true, 129165 => true, 129166 => true, 129167 => true, 129198 => true, 129199 => true, 129620 => true, 129621 => true, 129622 => true, 129623 => true, 129624 => true, 129625 => true, 129626 => true, 129627 => true, 129628 => true, 129629 => true, 129630 => true, 129631 => true, 129646 => true, 129647 => true, 129653 => true, 129654 => true, 129655 => true, 129661 => true, 129662 => true, 129663 => true, 129671 => true, 129672 => true, 129673 => true, 129674 => true, 129675 => true, 129676 => true, 129677 => true, 129678 => true, 129679 => true, 129709 => true, 129710 => true, 129711 => true, 129723 => true, 129724 => true, 129725 => true, 129726 => true, 129727 => true, 129734 => true, 129735 => true, 129736 => true, 129737 => true, 129738 => true, 129739 => true, 129740 => true, 129741 => true, 129742 => true, 129743 => true, 129754 => true, 129755 => true, 129756 => true, 129757 => true, 129758 => true, 129759 => true, 129768 => true, 129769 => true, 129770 => true, 129771 => true, 129772 => true, 129773 => true, 129774 => true, 129775 => true, 129783 => true, 129784 => true, 129785 => true, 129786 => true, 129787 => true, 129788 => true, 129789 => true, 129790 => true, 129791 => true, 129939 => true, 131070 => true, 131071 => true, 177977 => true, 177978 => true, 177979 => true, 177980 => true, 177981 => true, 177982 => true, 177983 => true, 178206 => true, 178207 => true, 183970 => true, 183971 => true, 183972 => true, 183973 => true, 183974 => true, 183975 => true, 183976 => true, 183977 => true, 183978 => true, 183979 => true, 183980 => true, 183981 => true, 183982 => true, 183983 => true, 194664 => true, 194676 => true, 194847 => true, 194911 => true, 195007 => true, 196606 => true, 196607 => true, 262142 => true, 262143 => true, 327678 => true, 327679 => true, 393214 => true, 393215 => true, 458750 => true, 458751 => true, 524286 => true, 524287 => true, 589822 => true, 589823 => true, 655358 => true, 655359 => true, 720894 => true, 720895 => true, 786430 => true, 786431 => true, 851966 => true, 851967 => true, 917502 => true, 917503 => true, 917504 => true, 917505 => true, 917506 => true, 917507 => true, 917508 => true, 917509 => true, 917510 => true, 917511 => true, 917512 => true, 917513 => true, 917514 => true, 917515 => true, 917516 => true, 917517 => true, 917518 => true, 917519 => true, 917520 => true, 917521 => true, 917522 => true, 917523 => true, 917524 => true, 917525 => true, 917526 => true, 917527 => true, 917528 => true, 917529 => true, 917530 => true, 917531 => true, 917532 => true, 917533 => true, 917534 => true, 917535 => true, 983038 => true, 983039 => true, 1048574 => true, 1048575 => true, 1114110 => true, 1114111 => true, ); php-toolkit/DataLiberation/vendor-patched/rowbot/idna/resources/mapped.php 0000777 00000314446 15251156632 0023054 0 ustar 00 <?php return array( 65 => 'a', 66 => 'b', 67 => 'c', 68 => 'd', 69 => 'e', 70 => 'f', 71 => 'g', 72 => 'h', 73 => 'i', 74 => 'j', 75 => 'k', 76 => 'l', 77 => 'm', 78 => 'n', 79 => 'o', 80 => 'p', 81 => 'q', 82 => 'r', 83 => 's', 84 => 't', 85 => 'u', 86 => 'v', 87 => 'w', 88 => 'x', 89 => 'y', 90 => 'z', 170 => 'a', 178 => '2', 179 => '3', 181 => 'μ', 185 => '1', 186 => 'o', 188 => '1⁄4', 189 => '1⁄2', 190 => '3⁄4', 192 => 'à', 193 => 'á', 194 => 'â', 195 => 'ã', 196 => 'ä', 197 => 'å', 198 => 'æ', 199 => 'ç', 200 => 'è', 201 => 'é', 202 => 'ê', 203 => 'ë', 204 => 'ì', 205 => 'í', 206 => 'î', 207 => 'ï', 208 => 'ð', 209 => 'ñ', 210 => 'ò', 211 => 'ó', 212 => 'ô', 213 => 'õ', 214 => 'ö', 216 => 'ø', 217 => 'ù', 218 => 'ú', 219 => 'û', 220 => 'ü', 221 => 'ý', 222 => 'þ', 256 => 'ā', 258 => 'ă', 260 => 'ą', 262 => 'ć', 264 => 'ĉ', 266 => 'ċ', 268 => 'č', 270 => 'ď', 272 => 'đ', 274 => 'ē', 276 => 'ĕ', 278 => 'ė', 280 => 'ę', 282 => 'ě', 284 => 'ĝ', 286 => 'ğ', 288 => 'ġ', 290 => 'ģ', 292 => 'ĥ', 294 => 'ħ', 296 => 'ĩ', 298 => 'ī', 300 => 'ĭ', 302 => 'į', 304 => 'i̇', 306 => 'ij', 307 => 'ij', 308 => 'ĵ', 310 => 'ķ', 313 => 'ĺ', 315 => 'ļ', 317 => 'ľ', 319 => 'l·', 320 => 'l·', 321 => 'ł', 323 => 'ń', 325 => 'ņ', 327 => 'ň', 329 => 'ʼn', 330 => 'ŋ', 332 => 'ō', 334 => 'ŏ', 336 => 'ő', 338 => 'œ', 340 => 'ŕ', 342 => 'ŗ', 344 => 'ř', 346 => 'ś', 348 => 'ŝ', 350 => 'ş', 352 => 'š', 354 => 'ţ', 356 => 'ť', 358 => 'ŧ', 360 => 'ũ', 362 => 'ū', 364 => 'ŭ', 366 => 'ů', 368 => 'ű', 370 => 'ų', 372 => 'ŵ', 374 => 'ŷ', 376 => 'ÿ', 377 => 'ź', 379 => 'ż', 381 => 'ž', 383 => 's', 385 => 'ɓ', 386 => 'ƃ', 388 => 'ƅ', 390 => 'ɔ', 391 => 'ƈ', 393 => 'ɖ', 394 => 'ɗ', 395 => 'ƌ', 398 => 'ǝ', 399 => 'ə', 400 => 'ɛ', 401 => 'ƒ', 403 => 'ɠ', 404 => 'ɣ', 406 => 'ɩ', 407 => 'ɨ', 408 => 'ƙ', 412 => 'ɯ', 413 => 'ɲ', 415 => 'ɵ', 416 => 'ơ', 418 => 'ƣ', 420 => 'ƥ', 422 => 'ʀ', 423 => 'ƨ', 425 => 'ʃ', 428 => 'ƭ', 430 => 'ʈ', 431 => 'ư', 433 => 'ʊ', 434 => 'ʋ', 435 => 'ƴ', 437 => 'ƶ', 439 => 'ʒ', 440 => 'ƹ', 444 => 'ƽ', 452 => 'dž', 453 => 'dž', 454 => 'dž', 455 => 'lj', 456 => 'lj', 457 => 'lj', 458 => 'nj', 459 => 'nj', 460 => 'nj', 461 => 'ǎ', 463 => 'ǐ', 465 => 'ǒ', 467 => 'ǔ', 469 => 'ǖ', 471 => 'ǘ', 473 => 'ǚ', 475 => 'ǜ', 478 => 'ǟ', 480 => 'ǡ', 482 => 'ǣ', 484 => 'ǥ', 486 => 'ǧ', 488 => 'ǩ', 490 => 'ǫ', 492 => 'ǭ', 494 => 'ǯ', 497 => 'dz', 498 => 'dz', 499 => 'dz', 500 => 'ǵ', 502 => 'ƕ', 503 => 'ƿ', 504 => 'ǹ', 506 => 'ǻ', 508 => 'ǽ', 510 => 'ǿ', 512 => 'ȁ', 514 => 'ȃ', 516 => 'ȅ', 518 => 'ȇ', 520 => 'ȉ', 522 => 'ȋ', 524 => 'ȍ', 526 => 'ȏ', 528 => 'ȑ', 530 => 'ȓ', 532 => 'ȕ', 534 => 'ȗ', 536 => 'ș', 538 => 'ț', 540 => 'ȝ', 542 => 'ȟ', 544 => 'ƞ', 546 => 'ȣ', 548 => 'ȥ', 550 => 'ȧ', 552 => 'ȩ', 554 => 'ȫ', 556 => 'ȭ', 558 => 'ȯ', 560 => 'ȱ', 562 => 'ȳ', 570 => 'ⱥ', 571 => 'ȼ', 573 => 'ƚ', 574 => 'ⱦ', 577 => 'ɂ', 579 => 'ƀ', 580 => 'ʉ', 581 => 'ʌ', 582 => 'ɇ', 584 => 'ɉ', 586 => 'ɋ', 588 => 'ɍ', 590 => 'ɏ', 688 => 'h', 689 => 'ɦ', 690 => 'j', 691 => 'r', 692 => 'ɹ', 693 => 'ɻ', 694 => 'ʁ', 695 => 'w', 696 => 'y', 736 => 'ɣ', 737 => 'l', 738 => 's', 739 => 'x', 740 => 'ʕ', 832 => '̀', 833 => '́', 835 => '̓', 836 => '̈́', 837 => 'ι', 880 => 'ͱ', 882 => 'ͳ', 884 => 'ʹ', 886 => 'ͷ', 895 => 'ϳ', 902 => 'ά', 903 => '·', 904 => 'έ', 905 => 'ή', 906 => 'ί', 908 => 'ό', 910 => 'ύ', 911 => 'ώ', 913 => 'α', 914 => 'β', 915 => 'γ', 916 => 'δ', 917 => 'ε', 918 => 'ζ', 919 => 'η', 920 => 'θ', 921 => 'ι', 922 => 'κ', 923 => 'λ', 924 => 'μ', 925 => 'ν', 926 => 'ξ', 927 => 'ο', 928 => 'π', 929 => 'ρ', 931 => 'σ', 932 => 'τ', 933 => 'υ', 934 => 'φ', 935 => 'χ', 936 => 'ψ', 937 => 'ω', 938 => 'ϊ', 939 => 'ϋ', 975 => 'ϗ', 976 => 'β', 977 => 'θ', 978 => 'υ', 979 => 'ύ', 980 => 'ϋ', 981 => 'φ', 982 => 'π', 984 => 'ϙ', 986 => 'ϛ', 988 => 'ϝ', 990 => 'ϟ', 992 => 'ϡ', 994 => 'ϣ', 996 => 'ϥ', 998 => 'ϧ', 1000 => 'ϩ', 1002 => 'ϫ', 1004 => 'ϭ', 1006 => 'ϯ', 1008 => 'κ', 1009 => 'ρ', 1010 => 'σ', 1012 => 'θ', 1013 => 'ε', 1015 => 'ϸ', 1017 => 'σ', 1018 => 'ϻ', 1021 => 'ͻ', 1022 => 'ͼ', 1023 => 'ͽ', 1024 => 'ѐ', 1025 => 'ё', 1026 => 'ђ', 1027 => 'ѓ', 1028 => 'є', 1029 => 'ѕ', 1030 => 'і', 1031 => 'ї', 1032 => 'ј', 1033 => 'љ', 1034 => 'њ', 1035 => 'ћ', 1036 => 'ќ', 1037 => 'ѝ', 1038 => 'ў', 1039 => 'џ', 1040 => 'а', 1041 => 'б', 1042 => 'в', 1043 => 'г', 1044 => 'д', 1045 => 'е', 1046 => 'ж', 1047 => 'з', 1048 => 'и', 1049 => 'й', 1050 => 'к', 1051 => 'л', 1052 => 'м', 1053 => 'н', 1054 => 'о', 1055 => 'п', 1056 => 'р', 1057 => 'с', 1058 => 'т', 1059 => 'у', 1060 => 'ф', 1061 => 'х', 1062 => 'ц', 1063 => 'ч', 1064 => 'ш', 1065 => 'щ', 1066 => 'ъ', 1067 => 'ы', 1068 => 'ь', 1069 => 'э', 1070 => 'ю', 1071 => 'я', 1120 => 'ѡ', 1122 => 'ѣ', 1124 => 'ѥ', 1126 => 'ѧ', 1128 => 'ѩ', 1130 => 'ѫ', 1132 => 'ѭ', 1134 => 'ѯ', 1136 => 'ѱ', 1138 => 'ѳ', 1140 => 'ѵ', 1142 => 'ѷ', 1144 => 'ѹ', 1146 => 'ѻ', 1148 => 'ѽ', 1150 => 'ѿ', 1152 => 'ҁ', 1162 => 'ҋ', 1164 => 'ҍ', 1166 => 'ҏ', 1168 => 'ґ', 1170 => 'ғ', 1172 => 'ҕ', 1174 => 'җ', 1176 => 'ҙ', 1178 => 'қ', 1180 => 'ҝ', 1182 => 'ҟ', 1184 => 'ҡ', 1186 => 'ң', 1188 => 'ҥ', 1190 => 'ҧ', 1192 => 'ҩ', 1194 => 'ҫ', 1196 => 'ҭ', 1198 => 'ү', 1200 => 'ұ', 1202 => 'ҳ', 1204 => 'ҵ', 1206 => 'ҷ', 1208 => 'ҹ', 1210 => 'һ', 1212 => 'ҽ', 1214 => 'ҿ', 1217 => 'ӂ', 1219 => 'ӄ', 1221 => 'ӆ', 1223 => 'ӈ', 1225 => 'ӊ', 1227 => 'ӌ', 1229 => 'ӎ', 1232 => 'ӑ', 1234 => 'ӓ', 1236 => 'ӕ', 1238 => 'ӗ', 1240 => 'ә', 1242 => 'ӛ', 1244 => 'ӝ', 1246 => 'ӟ', 1248 => 'ӡ', 1250 => 'ӣ', 1252 => 'ӥ', 1254 => 'ӧ', 1256 => 'ө', 1258 => 'ӫ', 1260 => 'ӭ', 1262 => 'ӯ', 1264 => 'ӱ', 1266 => 'ӳ', 1268 => 'ӵ', 1270 => 'ӷ', 1272 => 'ӹ', 1274 => 'ӻ', 1276 => 'ӽ', 1278 => 'ӿ', 1280 => 'ԁ', 1282 => 'ԃ', 1284 => 'ԅ', 1286 => 'ԇ', 1288 => 'ԉ', 1290 => 'ԋ', 1292 => 'ԍ', 1294 => 'ԏ', 1296 => 'ԑ', 1298 => 'ԓ', 1300 => 'ԕ', 1302 => 'ԗ', 1304 => 'ԙ', 1306 => 'ԛ', 1308 => 'ԝ', 1310 => 'ԟ', 1312 => 'ԡ', 1314 => 'ԣ', 1316 => 'ԥ', 1318 => 'ԧ', 1320 => 'ԩ', 1322 => 'ԫ', 1324 => 'ԭ', 1326 => 'ԯ', 1329 => 'ա', 1330 => 'բ', 1331 => 'գ', 1332 => 'դ', 1333 => 'ե', 1334 => 'զ', 1335 => 'է', 1336 => 'ը', 1337 => 'թ', 1338 => 'ժ', 1339 => 'ի', 1340 => 'լ', 1341 => 'խ', 1342 => 'ծ', 1343 => 'կ', 1344 => 'հ', 1345 => 'ձ', 1346 => 'ղ', 1347 => 'ճ', 1348 => 'մ', 1349 => 'յ', 1350 => 'ն', 1351 => 'շ', 1352 => 'ո', 1353 => 'չ', 1354 => 'պ', 1355 => 'ջ', 1356 => 'ռ', 1357 => 'ս', 1358 => 'վ', 1359 => 'տ', 1360 => 'ր', 1361 => 'ց', 1362 => 'ւ', 1363 => 'փ', 1364 => 'ք', 1365 => 'օ', 1366 => 'ֆ', 1415 => 'եւ', 1653 => 'اٴ', 1654 => 'وٴ', 1655 => 'ۇٴ', 1656 => 'يٴ', 2392 => 'क़', 2393 => 'ख़', 2394 => 'ग़', 2395 => 'ज़', 2396 => 'ड़', 2397 => 'ढ़', 2398 => 'फ़', 2399 => 'य़', 2524 => 'ড়', 2525 => 'ঢ়', 2527 => 'য়', 2611 => 'ਲ਼', 2614 => 'ਸ਼', 2649 => 'ਖ਼', 2650 => 'ਗ਼', 2651 => 'ਜ਼', 2654 => 'ਫ਼', 2908 => 'ଡ଼', 2909 => 'ଢ଼', 3635 => 'ํา', 3763 => 'ໍາ', 3804 => 'ຫນ', 3805 => 'ຫມ', 3852 => '་', 3907 => 'གྷ', 3917 => 'ཌྷ', 3922 => 'དྷ', 3927 => 'བྷ', 3932 => 'ཛྷ', 3945 => 'ཀྵ', 3955 => 'ཱི', 3957 => 'ཱུ', 3958 => 'ྲྀ', 3959 => 'ྲཱྀ', 3960 => 'ླྀ', 3961 => 'ླཱྀ', 3969 => 'ཱྀ', 3987 => 'ྒྷ', 3997 => 'ྜྷ', 4002 => 'ྡྷ', 4007 => 'ྦྷ', 4012 => 'ྫྷ', 4025 => 'ྐྵ', 4295 => 'ⴧ', 4301 => 'ⴭ', 4348 => 'ნ', 5112 => 'Ᏸ', 5113 => 'Ᏹ', 5114 => 'Ᏺ', 5115 => 'Ᏻ', 5116 => 'Ᏼ', 5117 => 'Ᏽ', 7296 => 'в', 7297 => 'д', 7298 => 'о', 7299 => 'с', 7300 => 'т', 7301 => 'т', 7302 => 'ъ', 7303 => 'ѣ', 7304 => 'ꙋ', 7312 => 'ა', 7313 => 'ბ', 7314 => 'გ', 7315 => 'დ', 7316 => 'ე', 7317 => 'ვ', 7318 => 'ზ', 7319 => 'თ', 7320 => 'ი', 7321 => 'კ', 7322 => 'ლ', 7323 => 'მ', 7324 => 'ნ', 7325 => 'ო', 7326 => 'პ', 7327 => 'ჟ', 7328 => 'რ', 7329 => 'ს', 7330 => 'ტ', 7331 => 'უ', 7332 => 'ფ', 7333 => 'ქ', 7334 => 'ღ', 7335 => 'ყ', 7336 => 'შ', 7337 => 'ჩ', 7338 => 'ც', 7339 => 'ძ', 7340 => 'წ', 7341 => 'ჭ', 7342 => 'ხ', 7343 => 'ჯ', 7344 => 'ჰ', 7345 => 'ჱ', 7346 => 'ჲ', 7347 => 'ჳ', 7348 => 'ჴ', 7349 => 'ჵ', 7350 => 'ჶ', 7351 => 'ჷ', 7352 => 'ჸ', 7353 => 'ჹ', 7354 => 'ჺ', 7357 => 'ჽ', 7358 => 'ჾ', 7359 => 'ჿ', 7468 => 'a', 7469 => 'æ', 7470 => 'b', 7472 => 'd', 7473 => 'e', 7474 => 'ǝ', 7475 => 'g', 7476 => 'h', 7477 => 'i', 7478 => 'j', 7479 => 'k', 7480 => 'l', 7481 => 'm', 7482 => 'n', 7484 => 'o', 7485 => 'ȣ', 7486 => 'p', 7487 => 'r', 7488 => 't', 7489 => 'u', 7490 => 'w', 7491 => 'a', 7492 => 'ɐ', 7493 => 'ɑ', 7494 => 'ᴂ', 7495 => 'b', 7496 => 'd', 7497 => 'e', 7498 => 'ə', 7499 => 'ɛ', 7500 => 'ɜ', 7501 => 'g', 7503 => 'k', 7504 => 'm', 7505 => 'ŋ', 7506 => 'o', 7507 => 'ɔ', 7508 => 'ᴖ', 7509 => 'ᴗ', 7510 => 'p', 7511 => 't', 7512 => 'u', 7513 => 'ᴝ', 7514 => 'ɯ', 7515 => 'v', 7516 => 'ᴥ', 7517 => 'β', 7518 => 'γ', 7519 => 'δ', 7520 => 'φ', 7521 => 'χ', 7522 => 'i', 7523 => 'r', 7524 => 'u', 7525 => 'v', 7526 => 'β', 7527 => 'γ', 7528 => 'ρ', 7529 => 'φ', 7530 => 'χ', 7544 => 'н', 7579 => 'ɒ', 7580 => 'c', 7581 => 'ɕ', 7582 => 'ð', 7583 => 'ɜ', 7584 => 'f', 7585 => 'ɟ', 7586 => 'ɡ', 7587 => 'ɥ', 7588 => 'ɨ', 7589 => 'ɩ', 7590 => 'ɪ', 7591 => 'ᵻ', 7592 => 'ʝ', 7593 => 'ɭ', 7594 => 'ᶅ', 7595 => 'ʟ', 7596 => 'ɱ', 7597 => 'ɰ', 7598 => 'ɲ', 7599 => 'ɳ', 7600 => 'ɴ', 7601 => 'ɵ', 7602 => 'ɸ', 7603 => 'ʂ', 7604 => 'ʃ', 7605 => 'ƫ', 7606 => 'ʉ', 7607 => 'ʊ', 7608 => 'ᴜ', 7609 => 'ʋ', 7610 => 'ʌ', 7611 => 'z', 7612 => 'ʐ', 7613 => 'ʑ', 7614 => 'ʒ', 7615 => 'θ', 7680 => 'ḁ', 7682 => 'ḃ', 7684 => 'ḅ', 7686 => 'ḇ', 7688 => 'ḉ', 7690 => 'ḋ', 7692 => 'ḍ', 7694 => 'ḏ', 7696 => 'ḑ', 7698 => 'ḓ', 7700 => 'ḕ', 7702 => 'ḗ', 7704 => 'ḙ', 7706 => 'ḛ', 7708 => 'ḝ', 7710 => 'ḟ', 7712 => 'ḡ', 7714 => 'ḣ', 7716 => 'ḥ', 7718 => 'ḧ', 7720 => 'ḩ', 7722 => 'ḫ', 7724 => 'ḭ', 7726 => 'ḯ', 7728 => 'ḱ', 7730 => 'ḳ', 7732 => 'ḵ', 7734 => 'ḷ', 7736 => 'ḹ', 7738 => 'ḻ', 7740 => 'ḽ', 7742 => 'ḿ', 7744 => 'ṁ', 7746 => 'ṃ', 7748 => 'ṅ', 7750 => 'ṇ', 7752 => 'ṉ', 7754 => 'ṋ', 7756 => 'ṍ', 7758 => 'ṏ', 7760 => 'ṑ', 7762 => 'ṓ', 7764 => 'ṕ', 7766 => 'ṗ', 7768 => 'ṙ', 7770 => 'ṛ', 7772 => 'ṝ', 7774 => 'ṟ', 7776 => 'ṡ', 7778 => 'ṣ', 7780 => 'ṥ', 7782 => 'ṧ', 7784 => 'ṩ', 7786 => 'ṫ', 7788 => 'ṭ', 7790 => 'ṯ', 7792 => 'ṱ', 7794 => 'ṳ', 7796 => 'ṵ', 7798 => 'ṷ', 7800 => 'ṹ', 7802 => 'ṻ', 7804 => 'ṽ', 7806 => 'ṿ', 7808 => 'ẁ', 7810 => 'ẃ', 7812 => 'ẅ', 7814 => 'ẇ', 7816 => 'ẉ', 7818 => 'ẋ', 7820 => 'ẍ', 7822 => 'ẏ', 7824 => 'ẑ', 7826 => 'ẓ', 7828 => 'ẕ', 7834 => 'aʾ', 7835 => 'ṡ', 7838 => 'ss', 7840 => 'ạ', 7842 => 'ả', 7844 => 'ấ', 7846 => 'ầ', 7848 => 'ẩ', 7850 => 'ẫ', 7852 => 'ậ', 7854 => 'ắ', 7856 => 'ằ', 7858 => 'ẳ', 7860 => 'ẵ', 7862 => 'ặ', 7864 => 'ẹ', 7866 => 'ẻ', 7868 => 'ẽ', 7870 => 'ế', 7872 => 'ề', 7874 => 'ể', 7876 => 'ễ', 7878 => 'ệ', 7880 => 'ỉ', 7882 => 'ị', 7884 => 'ọ', 7886 => 'ỏ', 7888 => 'ố', 7890 => 'ồ', 7892 => 'ổ', 7894 => 'ỗ', 7896 => 'ộ', 7898 => 'ớ', 7900 => 'ờ', 7902 => 'ở', 7904 => 'ỡ', 7906 => 'ợ', 7908 => 'ụ', 7910 => 'ủ', 7912 => 'ứ', 7914 => 'ừ', 7916 => 'ử', 7918 => 'ữ', 7920 => 'ự', 7922 => 'ỳ', 7924 => 'ỵ', 7926 => 'ỷ', 7928 => 'ỹ', 7930 => 'ỻ', 7932 => 'ỽ', 7934 => 'ỿ', 7944 => 'ἀ', 7945 => 'ἁ', 7946 => 'ἂ', 7947 => 'ἃ', 7948 => 'ἄ', 7949 => 'ἅ', 7950 => 'ἆ', 7951 => 'ἇ', 7960 => 'ἐ', 7961 => 'ἑ', 7962 => 'ἒ', 7963 => 'ἓ', 7964 => 'ἔ', 7965 => 'ἕ', 7976 => 'ἠ', 7977 => 'ἡ', 7978 => 'ἢ', 7979 => 'ἣ', 7980 => 'ἤ', 7981 => 'ἥ', 7982 => 'ἦ', 7983 => 'ἧ', 7992 => 'ἰ', 7993 => 'ἱ', 7994 => 'ἲ', 7995 => 'ἳ', 7996 => 'ἴ', 7997 => 'ἵ', 7998 => 'ἶ', 7999 => 'ἷ', 8008 => 'ὀ', 8009 => 'ὁ', 8010 => 'ὂ', 8011 => 'ὃ', 8012 => 'ὄ', 8013 => 'ὅ', 8025 => 'ὑ', 8027 => 'ὓ', 8029 => 'ὕ', 8031 => 'ὗ', 8040 => 'ὠ', 8041 => 'ὡ', 8042 => 'ὢ', 8043 => 'ὣ', 8044 => 'ὤ', 8045 => 'ὥ', 8046 => 'ὦ', 8047 => 'ὧ', 8049 => 'ά', 8051 => 'έ', 8053 => 'ή', 8055 => 'ί', 8057 => 'ό', 8059 => 'ύ', 8061 => 'ώ', 8064 => 'ἀι', 8065 => 'ἁι', 8066 => 'ἂι', 8067 => 'ἃι', 8068 => 'ἄι', 8069 => 'ἅι', 8070 => 'ἆι', 8071 => 'ἇι', 8072 => 'ἀι', 8073 => 'ἁι', 8074 => 'ἂι', 8075 => 'ἃι', 8076 => 'ἄι', 8077 => 'ἅι', 8078 => 'ἆι', 8079 => 'ἇι', 8080 => 'ἠι', 8081 => 'ἡι', 8082 => 'ἢι', 8083 => 'ἣι', 8084 => 'ἤι', 8085 => 'ἥι', 8086 => 'ἦι', 8087 => 'ἧι', 8088 => 'ἠι', 8089 => 'ἡι', 8090 => 'ἢι', 8091 => 'ἣι', 8092 => 'ἤι', 8093 => 'ἥι', 8094 => 'ἦι', 8095 => 'ἧι', 8096 => 'ὠι', 8097 => 'ὡι', 8098 => 'ὢι', 8099 => 'ὣι', 8100 => 'ὤι', 8101 => 'ὥι', 8102 => 'ὦι', 8103 => 'ὧι', 8104 => 'ὠι', 8105 => 'ὡι', 8106 => 'ὢι', 8107 => 'ὣι', 8108 => 'ὤι', 8109 => 'ὥι', 8110 => 'ὦι', 8111 => 'ὧι', 8114 => 'ὰι', 8115 => 'αι', 8116 => 'άι', 8119 => 'ᾶι', 8120 => 'ᾰ', 8121 => 'ᾱ', 8122 => 'ὰ', 8123 => 'ά', 8124 => 'αι', 8126 => 'ι', 8130 => 'ὴι', 8131 => 'ηι', 8132 => 'ήι', 8135 => 'ῆι', 8136 => 'ὲ', 8137 => 'έ', 8138 => 'ὴ', 8139 => 'ή', 8140 => 'ηι', 8147 => 'ΐ', 8152 => 'ῐ', 8153 => 'ῑ', 8154 => 'ὶ', 8155 => 'ί', 8163 => 'ΰ', 8168 => 'ῠ', 8169 => 'ῡ', 8170 => 'ὺ', 8171 => 'ύ', 8172 => 'ῥ', 8178 => 'ὼι', 8179 => 'ωι', 8180 => 'ώι', 8183 => 'ῶι', 8184 => 'ὸ', 8185 => 'ό', 8186 => 'ὼ', 8187 => 'ώ', 8188 => 'ωι', 8209 => '‐', 8243 => '′′', 8244 => '′′′', 8246 => '‵‵', 8247 => '‵‵‵', 8279 => '′′′′', 8304 => '0', 8305 => 'i', 8308 => '4', 8309 => '5', 8310 => '6', 8311 => '7', 8312 => '8', 8313 => '9', 8315 => '−', 8319 => 'n', 8320 => '0', 8321 => '1', 8322 => '2', 8323 => '3', 8324 => '4', 8325 => '5', 8326 => '6', 8327 => '7', 8328 => '8', 8329 => '9', 8331 => '−', 8336 => 'a', 8337 => 'e', 8338 => 'o', 8339 => 'x', 8340 => 'ə', 8341 => 'h', 8342 => 'k', 8343 => 'l', 8344 => 'm', 8345 => 'n', 8346 => 'p', 8347 => 's', 8348 => 't', 8360 => 'rs', 8450 => 'c', 8451 => '°c', 8455 => 'ɛ', 8457 => '°f', 8458 => 'g', 8459 => 'h', 8460 => 'h', 8461 => 'h', 8462 => 'h', 8463 => 'ħ', 8464 => 'i', 8465 => 'i', 8466 => 'l', 8467 => 'l', 8469 => 'n', 8470 => 'no', 8473 => 'p', 8474 => 'q', 8475 => 'r', 8476 => 'r', 8477 => 'r', 8480 => 'sm', 8481 => 'tel', 8482 => 'tm', 8484 => 'z', 8486 => 'ω', 8488 => 'z', 8490 => 'k', 8491 => 'å', 8492 => 'b', 8493 => 'c', 8495 => 'e', 8496 => 'e', 8497 => 'f', 8499 => 'm', 8500 => 'o', 8501 => 'א', 8502 => 'ב', 8503 => 'ג', 8504 => 'ד', 8505 => 'i', 8507 => 'fax', 8508 => 'π', 8509 => 'γ', 8510 => 'γ', 8511 => 'π', 8512 => '∑', 8517 => 'd', 8518 => 'd', 8519 => 'e', 8520 => 'i', 8521 => 'j', 8528 => '1⁄7', 8529 => '1⁄9', 8530 => '1⁄10', 8531 => '1⁄3', 8532 => '2⁄3', 8533 => '1⁄5', 8534 => '2⁄5', 8535 => '3⁄5', 8536 => '4⁄5', 8537 => '1⁄6', 8538 => '5⁄6', 8539 => '1⁄8', 8540 => '3⁄8', 8541 => '5⁄8', 8542 => '7⁄8', 8543 => '1⁄', 8544 => 'i', 8545 => 'ii', 8546 => 'iii', 8547 => 'iv', 8548 => 'v', 8549 => 'vi', 8550 => 'vii', 8551 => 'viii', 8552 => 'ix', 8553 => 'x', 8554 => 'xi', 8555 => 'xii', 8556 => 'l', 8557 => 'c', 8558 => 'd', 8559 => 'm', 8560 => 'i', 8561 => 'ii', 8562 => 'iii', 8563 => 'iv', 8564 => 'v', 8565 => 'vi', 8566 => 'vii', 8567 => 'viii', 8568 => 'ix', 8569 => 'x', 8570 => 'xi', 8571 => 'xii', 8572 => 'l', 8573 => 'c', 8574 => 'd', 8575 => 'm', 8585 => '0⁄3', 8748 => '∫∫', 8749 => '∫∫∫', 8751 => '∮∮', 8752 => '∮∮∮', 9001 => '〈', 9002 => '〉', 9312 => '1', 9313 => '2', 9314 => '3', 9315 => '4', 9316 => '5', 9317 => '6', 9318 => '7', 9319 => '8', 9320 => '9', 9321 => '10', 9322 => '11', 9323 => '12', 9324 => '13', 9325 => '14', 9326 => '15', 9327 => '16', 9328 => '17', 9329 => '18', 9330 => '19', 9331 => '20', 9398 => 'a', 9399 => 'b', 9400 => 'c', 9401 => 'd', 9402 => 'e', 9403 => 'f', 9404 => 'g', 9405 => 'h', 9406 => 'i', 9407 => 'j', 9408 => 'k', 9409 => 'l', 9410 => 'm', 9411 => 'n', 9412 => 'o', 9413 => 'p', 9414 => 'q', 9415 => 'r', 9416 => 's', 9417 => 't', 9418 => 'u', 9419 => 'v', 9420 => 'w', 9421 => 'x', 9422 => 'y', 9423 => 'z', 9424 => 'a', 9425 => 'b', 9426 => 'c', 9427 => 'd', 9428 => 'e', 9429 => 'f', 9430 => 'g', 9431 => 'h', 9432 => 'i', 9433 => 'j', 9434 => 'k', 9435 => 'l', 9436 => 'm', 9437 => 'n', 9438 => 'o', 9439 => 'p', 9440 => 'q', 9441 => 'r', 9442 => 's', 9443 => 't', 9444 => 'u', 9445 => 'v', 9446 => 'w', 9447 => 'x', 9448 => 'y', 9449 => 'z', 9450 => '0', 10764 => '∫∫∫∫', 10972 => '⫝̸', 11264 => 'ⰰ', 11265 => 'ⰱ', 11266 => 'ⰲ', 11267 => 'ⰳ', 11268 => 'ⰴ', 11269 => 'ⰵ', 11270 => 'ⰶ', 11271 => 'ⰷ', 11272 => 'ⰸ', 11273 => 'ⰹ', 11274 => 'ⰺ', 11275 => 'ⰻ', 11276 => 'ⰼ', 11277 => 'ⰽ', 11278 => 'ⰾ', 11279 => 'ⰿ', 11280 => 'ⱀ', 11281 => 'ⱁ', 11282 => 'ⱂ', 11283 => 'ⱃ', 11284 => 'ⱄ', 11285 => 'ⱅ', 11286 => 'ⱆ', 11287 => 'ⱇ', 11288 => 'ⱈ', 11289 => 'ⱉ', 11290 => 'ⱊ', 11291 => 'ⱋ', 11292 => 'ⱌ', 11293 => 'ⱍ', 11294 => 'ⱎ', 11295 => 'ⱏ', 11296 => 'ⱐ', 11297 => 'ⱑ', 11298 => 'ⱒ', 11299 => 'ⱓ', 11300 => 'ⱔ', 11301 => 'ⱕ', 11302 => 'ⱖ', 11303 => 'ⱗ', 11304 => 'ⱘ', 11305 => 'ⱙ', 11306 => 'ⱚ', 11307 => 'ⱛ', 11308 => 'ⱜ', 11309 => 'ⱝ', 11310 => 'ⱞ', 11311 => 'ⱟ', 11360 => 'ⱡ', 11362 => 'ɫ', 11363 => 'ᵽ', 11364 => 'ɽ', 11367 => 'ⱨ', 11369 => 'ⱪ', 11371 => 'ⱬ', 11373 => 'ɑ', 11374 => 'ɱ', 11375 => 'ɐ', 11376 => 'ɒ', 11378 => 'ⱳ', 11381 => 'ⱶ', 11388 => 'j', 11389 => 'v', 11390 => 'ȿ', 11391 => 'ɀ', 11392 => 'ⲁ', 11394 => 'ⲃ', 11396 => 'ⲅ', 11398 => 'ⲇ', 11400 => 'ⲉ', 11402 => 'ⲋ', 11404 => 'ⲍ', 11406 => 'ⲏ', 11408 => 'ⲑ', 11410 => 'ⲓ', 11412 => 'ⲕ', 11414 => 'ⲗ', 11416 => 'ⲙ', 11418 => 'ⲛ', 11420 => 'ⲝ', 11422 => 'ⲟ', 11424 => 'ⲡ', 11426 => 'ⲣ', 11428 => 'ⲥ', 11430 => 'ⲧ', 11432 => 'ⲩ', 11434 => 'ⲫ', 11436 => 'ⲭ', 11438 => 'ⲯ', 11440 => 'ⲱ', 11442 => 'ⲳ', 11444 => 'ⲵ', 11446 => 'ⲷ', 11448 => 'ⲹ', 11450 => 'ⲻ', 11452 => 'ⲽ', 11454 => 'ⲿ', 11456 => 'ⳁ', 11458 => 'ⳃ', 11460 => 'ⳅ', 11462 => 'ⳇ', 11464 => 'ⳉ', 11466 => 'ⳋ', 11468 => 'ⳍ', 11470 => 'ⳏ', 11472 => 'ⳑ', 11474 => 'ⳓ', 11476 => 'ⳕ', 11478 => 'ⳗ', 11480 => 'ⳙ', 11482 => 'ⳛ', 11484 => 'ⳝ', 11486 => 'ⳟ', 11488 => 'ⳡ', 11490 => 'ⳣ', 11499 => 'ⳬ', 11501 => 'ⳮ', 11506 => 'ⳳ', 11631 => 'ⵡ', 11935 => '母', 12019 => '龟', 12032 => '一', 12033 => '丨', 12034 => '丶', 12035 => '丿', 12036 => '乙', 12037 => '亅', 12038 => '二', 12039 => '亠', 12040 => '人', 12041 => '儿', 12042 => '入', 12043 => '八', 12044 => '冂', 12045 => '冖', 12046 => '冫', 12047 => '几', 12048 => '凵', 12049 => '刀', 12050 => '力', 12051 => '勹', 12052 => '匕', 12053 => '匚', 12054 => '匸', 12055 => '十', 12056 => '卜', 12057 => '卩', 12058 => '厂', 12059 => '厶', 12060 => '又', 12061 => '口', 12062 => '囗', 12063 => '土', 12064 => '士', 12065 => '夂', 12066 => '夊', 12067 => '夕', 12068 => '大', 12069 => '女', 12070 => '子', 12071 => '宀', 12072 => '寸', 12073 => '小', 12074 => '尢', 12075 => '尸', 12076 => '屮', 12077 => '山', 12078 => '巛', 12079 => '工', 12080 => '己', 12081 => '巾', 12082 => '干', 12083 => '幺', 12084 => '广', 12085 => '廴', 12086 => '廾', 12087 => '弋', 12088 => '弓', 12089 => '彐', 12090 => '彡', 12091 => '彳', 12092 => '心', 12093 => '戈', 12094 => '戶', 12095 => '手', 12096 => '支', 12097 => '攴', 12098 => '文', 12099 => '斗', 12100 => '斤', 12101 => '方', 12102 => '无', 12103 => '日', 12104 => '曰', 12105 => '月', 12106 => '木', 12107 => '欠', 12108 => '止', 12109 => '歹', 12110 => '殳', 12111 => '毋', 12112 => '比', 12113 => '毛', 12114 => '氏', 12115 => '气', 12116 => '水', 12117 => '火', 12118 => '爪', 12119 => '父', 12120 => '爻', 12121 => '爿', 12122 => '片', 12123 => '牙', 12124 => '牛', 12125 => '犬', 12126 => '玄', 12127 => '玉', 12128 => '瓜', 12129 => '瓦', 12130 => '甘', 12131 => '生', 12132 => '用', 12133 => '田', 12134 => '疋', 12135 => '疒', 12136 => '癶', 12137 => '白', 12138 => '皮', 12139 => '皿', 12140 => '目', 12141 => '矛', 12142 => '矢', 12143 => '石', 12144 => '示', 12145 => '禸', 12146 => '禾', 12147 => '穴', 12148 => '立', 12149 => '竹', 12150 => '米', 12151 => '糸', 12152 => '缶', 12153 => '网', 12154 => '羊', 12155 => '羽', 12156 => '老', 12157 => '而', 12158 => '耒', 12159 => '耳', 12160 => '聿', 12161 => '肉', 12162 => '臣', 12163 => '自', 12164 => '至', 12165 => '臼', 12166 => '舌', 12167 => '舛', 12168 => '舟', 12169 => '艮', 12170 => '色', 12171 => '艸', 12172 => '虍', 12173 => '虫', 12174 => '血', 12175 => '行', 12176 => '衣', 12177 => '襾', 12178 => '見', 12179 => '角', 12180 => '言', 12181 => '谷', 12182 => '豆', 12183 => '豕', 12184 => '豸', 12185 => '貝', 12186 => '赤', 12187 => '走', 12188 => '足', 12189 => '身', 12190 => '車', 12191 => '辛', 12192 => '辰', 12193 => '辵', 12194 => '邑', 12195 => '酉', 12196 => '釆', 12197 => '里', 12198 => '金', 12199 => '長', 12200 => '門', 12201 => '阜', 12202 => '隶', 12203 => '隹', 12204 => '雨', 12205 => '靑', 12206 => '非', 12207 => '面', 12208 => '革', 12209 => '韋', 12210 => '韭', 12211 => '音', 12212 => '頁', 12213 => '風', 12214 => '飛', 12215 => '食', 12216 => '首', 12217 => '香', 12218 => '馬', 12219 => '骨', 12220 => '高', 12221 => '髟', 12222 => '鬥', 12223 => '鬯', 12224 => '鬲', 12225 => '鬼', 12226 => '魚', 12227 => '鳥', 12228 => '鹵', 12229 => '鹿', 12230 => '麥', 12231 => '麻', 12232 => '黃', 12233 => '黍', 12234 => '黑', 12235 => '黹', 12236 => '黽', 12237 => '鼎', 12238 => '鼓', 12239 => '鼠', 12240 => '鼻', 12241 => '齊', 12242 => '齒', 12243 => '龍', 12244 => '龜', 12245 => '龠', 12290 => '.', 12342 => '〒', 12344 => '十', 12345 => '卄', 12346 => '卅', 12447 => 'より', 12543 => 'コト', 12593 => 'ᄀ', 12594 => 'ᄁ', 12595 => 'ᆪ', 12596 => 'ᄂ', 12597 => 'ᆬ', 12598 => 'ᆭ', 12599 => 'ᄃ', 12600 => 'ᄄ', 12601 => 'ᄅ', 12602 => 'ᆰ', 12603 => 'ᆱ', 12604 => 'ᆲ', 12605 => 'ᆳ', 12606 => 'ᆴ', 12607 => 'ᆵ', 12608 => 'ᄚ', 12609 => 'ᄆ', 12610 => 'ᄇ', 12611 => 'ᄈ', 12612 => 'ᄡ', 12613 => 'ᄉ', 12614 => 'ᄊ', 12615 => 'ᄋ', 12616 => 'ᄌ', 12617 => 'ᄍ', 12618 => 'ᄎ', 12619 => 'ᄏ', 12620 => 'ᄐ', 12621 => 'ᄑ', 12622 => 'ᄒ', 12623 => 'ᅡ', 12624 => 'ᅢ', 12625 => 'ᅣ', 12626 => 'ᅤ', 12627 => 'ᅥ', 12628 => 'ᅦ', 12629 => 'ᅧ', 12630 => 'ᅨ', 12631 => 'ᅩ', 12632 => 'ᅪ', 12633 => 'ᅫ', 12634 => 'ᅬ', 12635 => 'ᅭ', 12636 => 'ᅮ', 12637 => 'ᅯ', 12638 => 'ᅰ', 12639 => 'ᅱ', 12640 => 'ᅲ', 12641 => 'ᅳ', 12642 => 'ᅴ', 12643 => 'ᅵ', 12645 => 'ᄔ', 12646 => 'ᄕ', 12647 => 'ᇇ', 12648 => 'ᇈ', 12649 => 'ᇌ', 12650 => 'ᇎ', 12651 => 'ᇓ', 12652 => 'ᇗ', 12653 => 'ᇙ', 12654 => 'ᄜ', 12655 => 'ᇝ', 12656 => 'ᇟ', 12657 => 'ᄝ', 12658 => 'ᄞ', 12659 => 'ᄠ', 12660 => 'ᄢ', 12661 => 'ᄣ', 12662 => 'ᄧ', 12663 => 'ᄩ', 12664 => 'ᄫ', 12665 => 'ᄬ', 12666 => 'ᄭ', 12667 => 'ᄮ', 12668 => 'ᄯ', 12669 => 'ᄲ', 12670 => 'ᄶ', 12671 => 'ᅀ', 12672 => 'ᅇ', 12673 => 'ᅌ', 12674 => 'ᇱ', 12675 => 'ᇲ', 12676 => 'ᅗ', 12677 => 'ᅘ', 12678 => 'ᅙ', 12679 => 'ᆄ', 12680 => 'ᆅ', 12681 => 'ᆈ', 12682 => 'ᆑ', 12683 => 'ᆒ', 12684 => 'ᆔ', 12685 => 'ᆞ', 12686 => 'ᆡ', 12690 => '一', 12691 => '二', 12692 => '三', 12693 => '四', 12694 => '上', 12695 => '中', 12696 => '下', 12697 => '甲', 12698 => '乙', 12699 => '丙', 12700 => '丁', 12701 => '天', 12702 => '地', 12703 => '人', 12868 => '問', 12869 => '幼', 12870 => '文', 12871 => '箏', 12880 => 'pte', 12881 => '21', 12882 => '22', 12883 => '23', 12884 => '24', 12885 => '25', 12886 => '26', 12887 => '27', 12888 => '28', 12889 => '29', 12890 => '30', 12891 => '31', 12892 => '32', 12893 => '33', 12894 => '34', 12895 => '35', 12896 => 'ᄀ', 12897 => 'ᄂ', 12898 => 'ᄃ', 12899 => 'ᄅ', 12900 => 'ᄆ', 12901 => 'ᄇ', 12902 => 'ᄉ', 12903 => 'ᄋ', 12904 => 'ᄌ', 12905 => 'ᄎ', 12906 => 'ᄏ', 12907 => 'ᄐ', 12908 => 'ᄑ', 12909 => 'ᄒ', 12910 => '가', 12911 => '나', 12912 => '다', 12913 => '라', 12914 => '마', 12915 => '바', 12916 => '사', 12917 => '아', 12918 => '자', 12919 => '차', 12920 => '카', 12921 => '타', 12922 => '파', 12923 => '하', 12924 => '참고', 12925 => '주의', 12926 => '우', 12928 => '一', 12929 => '二', 12930 => '三', 12931 => '四', 12932 => '五', 12933 => '六', 12934 => '七', 12935 => '八', 12936 => '九', 12937 => '十', 12938 => '月', 12939 => '火', 12940 => '水', 12941 => '木', 12942 => '金', 12943 => '土', 12944 => '日', 12945 => '株', 12946 => '有', 12947 => '社', 12948 => '名', 12949 => '特', 12950 => '財', 12951 => '祝', 12952 => '労', 12953 => '秘', 12954 => '男', 12955 => '女', 12956 => '適', 12957 => '優', 12958 => '印', 12959 => '注', 12960 => '項', 12961 => '休', 12962 => '写', 12963 => '正', 12964 => '上', 12965 => '中', 12966 => '下', 12967 => '左', 12968 => '右', 12969 => '医', 12970 => '宗', 12971 => '学', 12972 => '監', 12973 => '企', 12974 => '資', 12975 => '協', 12976 => '夜', 12977 => '36', 12978 => '37', 12979 => '38', 12980 => '39', 12981 => '40', 12982 => '41', 12983 => '42', 12984 => '43', 12985 => '44', 12986 => '45', 12987 => '46', 12988 => '47', 12989 => '48', 12990 => '49', 12991 => '50', 12992 => '1月', 12993 => '2月', 12994 => '3月', 12995 => '4月', 12996 => '5月', 12997 => '6月', 12998 => '7月', 12999 => '8月', 13000 => '9月', 13001 => '10月', 13002 => '11月', 13003 => '12月', 13004 => 'hg', 13005 => 'erg', 13006 => 'ev', 13007 => 'ltd', 13008 => 'ア', 13009 => 'イ', 13010 => 'ウ', 13011 => 'エ', 13012 => 'オ', 13013 => 'カ', 13014 => 'キ', 13015 => 'ク', 13016 => 'ケ', 13017 => 'コ', 13018 => 'サ', 13019 => 'シ', 13020 => 'ス', 13021 => 'セ', 13022 => 'ソ', 13023 => 'タ', 13024 => 'チ', 13025 => 'ツ', 13026 => 'テ', 13027 => 'ト', 13028 => 'ナ', 13029 => 'ニ', 13030 => 'ヌ', 13031 => 'ネ', 13032 => 'ノ', 13033 => 'ハ', 13034 => 'ヒ', 13035 => 'フ', 13036 => 'ヘ', 13037 => 'ホ', 13038 => 'マ', 13039 => 'ミ', 13040 => 'ム', 13041 => 'メ', 13042 => 'モ', 13043 => 'ヤ', 13044 => 'ユ', 13045 => 'ヨ', 13046 => 'ラ', 13047 => 'リ', 13048 => 'ル', 13049 => 'レ', 13050 => 'ロ', 13051 => 'ワ', 13052 => 'ヰ', 13053 => 'ヱ', 13054 => 'ヲ', 13055 => '令和', 13056 => 'アパート', 13057 => 'アルファ', 13058 => 'アンペア', 13059 => 'アール', 13060 => 'イニング', 13061 => 'インチ', 13062 => 'ウォン', 13063 => 'エスクード', 13064 => 'エーカー', 13065 => 'オンス', 13066 => 'オーム', 13067 => 'カイリ', 13068 => 'カラット', 13069 => 'カロリー', 13070 => 'ガロン', 13071 => 'ガンマ', 13072 => 'ギガ', 13073 => 'ギニー', 13074 => 'キュリー', 13075 => 'ギルダー', 13076 => 'キロ', 13077 => 'キログラム', 13078 => 'キロメートル', 13079 => 'キロワット', 13080 => 'グラム', 13081 => 'グラムトン', 13082 => 'クルゼイロ', 13083 => 'クローネ', 13084 => 'ケース', 13085 => 'コルナ', 13086 => 'コーポ', 13087 => 'サイクル', 13088 => 'サンチーム', 13089 => 'シリング', 13090 => 'センチ', 13091 => 'セント', 13092 => 'ダース', 13093 => 'デシ', 13094 => 'ドル', 13095 => 'トン', 13096 => 'ナノ', 13097 => 'ノット', 13098 => 'ハイツ', 13099 => 'パーセント', 13100 => 'パーツ', 13101 => 'バーレル', 13102 => 'ピアストル', 13103 => 'ピクル', 13104 => 'ピコ', 13105 => 'ビル', 13106 => 'ファラッド', 13107 => 'フィート', 13108 => 'ブッシェル', 13109 => 'フラン', 13110 => 'ヘクタール', 13111 => 'ペソ', 13112 => 'ペニヒ', 13113 => 'ヘルツ', 13114 => 'ペンス', 13115 => 'ページ', 13116 => 'ベータ', 13117 => 'ポイント', 13118 => 'ボルト', 13119 => 'ホン', 13120 => 'ポンド', 13121 => 'ホール', 13122 => 'ホーン', 13123 => 'マイクロ', 13124 => 'マイル', 13125 => 'マッハ', 13126 => 'マルク', 13127 => 'マンション', 13128 => 'ミクロン', 13129 => 'ミリ', 13130 => 'ミリバール', 13131 => 'メガ', 13132 => 'メガトン', 13133 => 'メートル', 13134 => 'ヤード', 13135 => 'ヤール', 13136 => 'ユアン', 13137 => 'リットル', 13138 => 'リラ', 13139 => 'ルピー', 13140 => 'ルーブル', 13141 => 'レム', 13142 => 'レントゲン', 13143 => 'ワット', 13144 => '0点', 13145 => '1点', 13146 => '2点', 13147 => '3点', 13148 => '4点', 13149 => '5点', 13150 => '6点', 13151 => '7点', 13152 => '8点', 13153 => '9点', 13154 => '10点', 13155 => '11点', 13156 => '12点', 13157 => '13点', 13158 => '14点', 13159 => '15点', 13160 => '16点', 13161 => '17点', 13162 => '18点', 13163 => '19点', 13164 => '20点', 13165 => '21点', 13166 => '22点', 13167 => '23点', 13168 => '24点', 13169 => 'hpa', 13170 => 'da', 13171 => 'au', 13172 => 'bar', 13173 => 'ov', 13174 => 'pc', 13175 => 'dm', 13176 => 'dm2', 13177 => 'dm3', 13178 => 'iu', 13179 => '平成', 13180 => '昭和', 13181 => '大正', 13182 => '明治', 13183 => '株式会社', 13184 => 'pa', 13185 => 'na', 13186 => 'μa', 13187 => 'ma', 13188 => 'ka', 13189 => 'kb', 13190 => 'mb', 13191 => 'gb', 13192 => 'cal', 13193 => 'kcal', 13194 => 'pf', 13195 => 'nf', 13196 => 'μf', 13197 => 'μg', 13198 => 'mg', 13199 => 'kg', 13200 => 'hz', 13201 => 'khz', 13202 => 'mhz', 13203 => 'ghz', 13204 => 'thz', 13205 => 'μl', 13206 => 'ml', 13207 => 'dl', 13208 => 'kl', 13209 => 'fm', 13210 => 'nm', 13211 => 'μm', 13212 => 'mm', 13213 => 'cm', 13214 => 'km', 13215 => 'mm2', 13216 => 'cm2', 13217 => 'm2', 13218 => 'km2', 13219 => 'mm3', 13220 => 'cm3', 13221 => 'm3', 13222 => 'km3', 13223 => 'm∕s', 13224 => 'm∕s2', 13225 => 'pa', 13226 => 'kpa', 13227 => 'mpa', 13228 => 'gpa', 13229 => 'rad', 13230 => 'rad∕s', 13231 => 'rad∕s2', 13232 => 'ps', 13233 => 'ns', 13234 => 'μs', 13235 => 'ms', 13236 => 'pv', 13237 => 'nv', 13238 => 'μv', 13239 => 'mv', 13240 => 'kv', 13241 => 'mv', 13242 => 'pw', 13243 => 'nw', 13244 => 'μw', 13245 => 'mw', 13246 => 'kw', 13247 => 'mw', 13248 => 'kω', 13249 => 'mω', 13251 => 'bq', 13252 => 'cc', 13253 => 'cd', 13254 => 'c∕kg', 13256 => 'db', 13257 => 'gy', 13258 => 'ha', 13259 => 'hp', 13260 => 'in', 13261 => 'kk', 13262 => 'km', 13263 => 'kt', 13264 => 'lm', 13265 => 'ln', 13266 => 'log', 13267 => 'lx', 13268 => 'mb', 13269 => 'mil', 13270 => 'mol', 13271 => 'ph', 13273 => 'ppm', 13274 => 'pr', 13275 => 'sr', 13276 => 'sv', 13277 => 'wb', 13278 => 'v∕m', 13279 => 'a∕m', 13280 => '1日', 13281 => '2日', 13282 => '3日', 13283 => '4日', 13284 => '5日', 13285 => '6日', 13286 => '7日', 13287 => '8日', 13288 => '9日', 13289 => '10日', 13290 => '11日', 13291 => '12日', 13292 => '13日', 13293 => '14日', 13294 => '15日', 13295 => '16日', 13296 => '17日', 13297 => '18日', 13298 => '19日', 13299 => '20日', 13300 => '21日', 13301 => '22日', 13302 => '23日', 13303 => '24日', 13304 => '25日', 13305 => '26日', 13306 => '27日', 13307 => '28日', 13308 => '29日', 13309 => '30日', 13310 => '31日', 13311 => 'gal', 42560 => 'ꙁ', 42562 => 'ꙃ', 42564 => 'ꙅ', 42566 => 'ꙇ', 42568 => 'ꙉ', 42570 => 'ꙋ', 42572 => 'ꙍ', 42574 => 'ꙏ', 42576 => 'ꙑ', 42578 => 'ꙓ', 42580 => 'ꙕ', 42582 => 'ꙗ', 42584 => 'ꙙ', 42586 => 'ꙛ', 42588 => 'ꙝ', 42590 => 'ꙟ', 42592 => 'ꙡ', 42594 => 'ꙣ', 42596 => 'ꙥ', 42598 => 'ꙧ', 42600 => 'ꙩ', 42602 => 'ꙫ', 42604 => 'ꙭ', 42624 => 'ꚁ', 42626 => 'ꚃ', 42628 => 'ꚅ', 42630 => 'ꚇ', 42632 => 'ꚉ', 42634 => 'ꚋ', 42636 => 'ꚍ', 42638 => 'ꚏ', 42640 => 'ꚑ', 42642 => 'ꚓ', 42644 => 'ꚕ', 42646 => 'ꚗ', 42648 => 'ꚙ', 42650 => 'ꚛ', 42652 => 'ъ', 42653 => 'ь', 42786 => 'ꜣ', 42788 => 'ꜥ', 42790 => 'ꜧ', 42792 => 'ꜩ', 42794 => 'ꜫ', 42796 => 'ꜭ', 42798 => 'ꜯ', 42802 => 'ꜳ', 42804 => 'ꜵ', 42806 => 'ꜷ', 42808 => 'ꜹ', 42810 => 'ꜻ', 42812 => 'ꜽ', 42814 => 'ꜿ', 42816 => 'ꝁ', 42818 => 'ꝃ', 42820 => 'ꝅ', 42822 => 'ꝇ', 42824 => 'ꝉ', 42826 => 'ꝋ', 42828 => 'ꝍ', 42830 => 'ꝏ', 42832 => 'ꝑ', 42834 => 'ꝓ', 42836 => 'ꝕ', 42838 => 'ꝗ', 42840 => 'ꝙ', 42842 => 'ꝛ', 42844 => 'ꝝ', 42846 => 'ꝟ', 42848 => 'ꝡ', 42850 => 'ꝣ', 42852 => 'ꝥ', 42854 => 'ꝧ', 42856 => 'ꝩ', 42858 => 'ꝫ', 42860 => 'ꝭ', 42862 => 'ꝯ', 42864 => 'ꝯ', 42873 => 'ꝺ', 42875 => 'ꝼ', 42877 => 'ᵹ', 42878 => 'ꝿ', 42880 => 'ꞁ', 42882 => 'ꞃ', 42884 => 'ꞅ', 42886 => 'ꞇ', 42891 => 'ꞌ', 42893 => 'ɥ', 42896 => 'ꞑ', 42898 => 'ꞓ', 42902 => 'ꞗ', 42904 => 'ꞙ', 42906 => 'ꞛ', 42908 => 'ꞝ', 42910 => 'ꞟ', 42912 => 'ꞡ', 42914 => 'ꞣ', 42916 => 'ꞥ', 42918 => 'ꞧ', 42920 => 'ꞩ', 42922 => 'ɦ', 42923 => 'ɜ', 42924 => 'ɡ', 42925 => 'ɬ', 42926 => 'ɪ', 42928 => 'ʞ', 42929 => 'ʇ', 42930 => 'ʝ', 42931 => 'ꭓ', 42932 => 'ꞵ', 42934 => 'ꞷ', 42936 => 'ꞹ', 42938 => 'ꞻ', 42940 => 'ꞽ', 42942 => 'ꞿ', 42944 => 'ꟁ', 42946 => 'ꟃ', 42948 => 'ꞔ', 42949 => 'ʂ', 42950 => 'ᶎ', 42951 => 'ꟈ', 42953 => 'ꟊ', 42960 => 'ꟑ', 42966 => 'ꟗ', 42968 => 'ꟙ', 42994 => 'c', 42995 => 'f', 42996 => 'q', 42997 => 'ꟶ', 43000 => 'ħ', 43001 => 'œ', 43868 => 'ꜧ', 43869 => 'ꬷ', 43870 => 'ɫ', 43871 => 'ꭒ', 43881 => 'ʍ', 43888 => 'Ꭰ', 43889 => 'Ꭱ', 43890 => 'Ꭲ', 43891 => 'Ꭳ', 43892 => 'Ꭴ', 43893 => 'Ꭵ', 43894 => 'Ꭶ', 43895 => 'Ꭷ', 43896 => 'Ꭸ', 43897 => 'Ꭹ', 43898 => 'Ꭺ', 43899 => 'Ꭻ', 43900 => 'Ꭼ', 43901 => 'Ꭽ', 43902 => 'Ꭾ', 43903 => 'Ꭿ', 43904 => 'Ꮀ', 43905 => 'Ꮁ', 43906 => 'Ꮂ', 43907 => 'Ꮃ', 43908 => 'Ꮄ', 43909 => 'Ꮅ', 43910 => 'Ꮆ', 43911 => 'Ꮇ', 43912 => 'Ꮈ', 43913 => 'Ꮉ', 43914 => 'Ꮊ', 43915 => 'Ꮋ', 43916 => 'Ꮌ', 43917 => 'Ꮍ', 43918 => 'Ꮎ', 43919 => 'Ꮏ', 43920 => 'Ꮐ', 43921 => 'Ꮑ', 43922 => 'Ꮒ', 43923 => 'Ꮓ', 43924 => 'Ꮔ', 43925 => 'Ꮕ', 43926 => 'Ꮖ', 43927 => 'Ꮗ', 43928 => 'Ꮘ', 43929 => 'Ꮙ', 43930 => 'Ꮚ', 43931 => 'Ꮛ', 43932 => 'Ꮜ', 43933 => 'Ꮝ', 43934 => 'Ꮞ', 43935 => 'Ꮟ', 43936 => 'Ꮠ', 43937 => 'Ꮡ', 43938 => 'Ꮢ', 43939 => 'Ꮣ', 43940 => 'Ꮤ', 43941 => 'Ꮥ', 43942 => 'Ꮦ', 43943 => 'Ꮧ', 43944 => 'Ꮨ', 43945 => 'Ꮩ', 43946 => 'Ꮪ', 43947 => 'Ꮫ', 43948 => 'Ꮬ', 43949 => 'Ꮭ', 43950 => 'Ꮮ', 43951 => 'Ꮯ', 43952 => 'Ꮰ', 43953 => 'Ꮱ', 43954 => 'Ꮲ', 43955 => 'Ꮳ', 43956 => 'Ꮴ', 43957 => 'Ꮵ', 43958 => 'Ꮶ', 43959 => 'Ꮷ', 43960 => 'Ꮸ', 43961 => 'Ꮹ', 43962 => 'Ꮺ', 43963 => 'Ꮻ', 43964 => 'Ꮼ', 43965 => 'Ꮽ', 43966 => 'Ꮾ', 43967 => 'Ꮿ', 63744 => '豈', 63745 => '更', 63746 => '車', 63747 => '賈', 63748 => '滑', 63749 => '串', 63750 => '句', 63751 => '龜', 63752 => '龜', 63753 => '契', 63754 => '金', 63755 => '喇', 63756 => '奈', 63757 => '懶', 63758 => '癩', 63759 => '羅', 63760 => '蘿', 63761 => '螺', 63762 => '裸', 63763 => '邏', 63764 => '樂', 63765 => '洛', 63766 => '烙', 63767 => '珞', 63768 => '落', 63769 => '酪', 63770 => '駱', 63771 => '亂', 63772 => '卵', 63773 => '欄', 63774 => '爛', 63775 => '蘭', 63776 => '鸞', 63777 => '嵐', 63778 => '濫', 63779 => '藍', 63780 => '襤', 63781 => '拉', 63782 => '臘', 63783 => '蠟', 63784 => '廊', 63785 => '朗', 63786 => '浪', 63787 => '狼', 63788 => '郎', 63789 => '來', 63790 => '冷', 63791 => '勞', 63792 => '擄', 63793 => '櫓', 63794 => '爐', 63795 => '盧', 63796 => '老', 63797 => '蘆', 63798 => '虜', 63799 => '路', 63800 => '露', 63801 => '魯', 63802 => '鷺', 63803 => '碌', 63804 => '祿', 63805 => '綠', 63806 => '菉', 63807 => '錄', 63808 => '鹿', 63809 => '論', 63810 => '壟', 63811 => '弄', 63812 => '籠', 63813 => '聾', 63814 => '牢', 63815 => '磊', 63816 => '賂', 63817 => '雷', 63818 => '壘', 63819 => '屢', 63820 => '樓', 63821 => '淚', 63822 => '漏', 63823 => '累', 63824 => '縷', 63825 => '陋', 63826 => '勒', 63827 => '肋', 63828 => '凜', 63829 => '凌', 63830 => '稜', 63831 => '綾', 63832 => '菱', 63833 => '陵', 63834 => '讀', 63835 => '拏', 63836 => '樂', 63837 => '諾', 63838 => '丹', 63839 => '寧', 63840 => '怒', 63841 => '率', 63842 => '異', 63843 => '北', 63844 => '磻', 63845 => '便', 63846 => '復', 63847 => '不', 63848 => '泌', 63849 => '數', 63850 => '索', 63851 => '參', 63852 => '塞', 63853 => '省', 63854 => '葉', 63855 => '說', 63856 => '殺', 63857 => '辰', 63858 => '沈', 63859 => '拾', 63860 => '若', 63861 => '掠', 63862 => '略', 63863 => '亮', 63864 => '兩', 63865 => '凉', 63866 => '梁', 63867 => '糧', 63868 => '良', 63869 => '諒', 63870 => '量', 63871 => '勵', 63872 => '呂', 63873 => '女', 63874 => '廬', 63875 => '旅', 63876 => '濾', 63877 => '礪', 63878 => '閭', 63879 => '驪', 63880 => '麗', 63881 => '黎', 63882 => '力', 63883 => '曆', 63884 => '歷', 63885 => '轢', 63886 => '年', 63887 => '憐', 63888 => '戀', 63889 => '撚', 63890 => '漣', 63891 => '煉', 63892 => '璉', 63893 => '秊', 63894 => '練', 63895 => '聯', 63896 => '輦', 63897 => '蓮', 63898 => '連', 63899 => '鍊', 63900 => '列', 63901 => '劣', 63902 => '咽', 63903 => '烈', 63904 => '裂', 63905 => '說', 63906 => '廉', 63907 => '念', 63908 => '捻', 63909 => '殮', 63910 => '簾', 63911 => '獵', 63912 => '令', 63913 => '囹', 63914 => '寧', 63915 => '嶺', 63916 => '怜', 63917 => '玲', 63918 => '瑩', 63919 => '羚', 63920 => '聆', 63921 => '鈴', 63922 => '零', 63923 => '靈', 63924 => '領', 63925 => '例', 63926 => '禮', 63927 => '醴', 63928 => '隸', 63929 => '惡', 63930 => '了', 63931 => '僚', 63932 => '寮', 63933 => '尿', 63934 => '料', 63935 => '樂', 63936 => '燎', 63937 => '療', 63938 => '蓼', 63939 => '遼', 63940 => '龍', 63941 => '暈', 63942 => '阮', 63943 => '劉', 63944 => '杻', 63945 => '柳', 63946 => '流', 63947 => '溜', 63948 => '琉', 63949 => '留', 63950 => '硫', 63951 => '紐', 63952 => '類', 63953 => '六', 63954 => '戮', 63955 => '陸', 63956 => '倫', 63957 => '崙', 63958 => '淪', 63959 => '輪', 63960 => '律', 63961 => '慄', 63962 => '栗', 63963 => '率', 63964 => '隆', 63965 => '利', 63966 => '吏', 63967 => '履', 63968 => '易', 63969 => '李', 63970 => '梨', 63971 => '泥', 63972 => '理', 63973 => '痢', 63974 => '罹', 63975 => '裏', 63976 => '裡', 63977 => '里', 63978 => '離', 63979 => '匿', 63980 => '溺', 63981 => '吝', 63982 => '燐', 63983 => '璘', 63984 => '藺', 63985 => '隣', 63986 => '鱗', 63987 => '麟', 63988 => '林', 63989 => '淋', 63990 => '臨', 63991 => '立', 63992 => '笠', 63993 => '粒', 63994 => '狀', 63995 => '炙', 63996 => '識', 63997 => '什', 63998 => '茶', 63999 => '刺', 64000 => '切', 64001 => '度', 64002 => '拓', 64003 => '糖', 64004 => '宅', 64005 => '洞', 64006 => '暴', 64007 => '輻', 64008 => '行', 64009 => '降', 64010 => '見', 64011 => '廓', 64012 => '兀', 64013 => '嗀', 64016 => '塚', 64018 => '晴', 64021 => '凞', 64022 => '猪', 64023 => '益', 64024 => '礼', 64025 => '神', 64026 => '祥', 64027 => '福', 64028 => '靖', 64029 => '精', 64030 => '羽', 64032 => '蘒', 64034 => '諸', 64037 => '逸', 64038 => '都', 64042 => '飯', 64043 => '飼', 64044 => '館', 64045 => '鶴', 64046 => '郞', 64047 => '隷', 64048 => '侮', 64049 => '僧', 64050 => '免', 64051 => '勉', 64052 => '勤', 64053 => '卑', 64054 => '喝', 64055 => '嘆', 64056 => '器', 64057 => '塀', 64058 => '墨', 64059 => '層', 64060 => '屮', 64061 => '悔', 64062 => '慨', 64063 => '憎', 64064 => '懲', 64065 => '敏', 64066 => '既', 64067 => '暑', 64068 => '梅', 64069 => '海', 64070 => '渚', 64071 => '漢', 64072 => '煮', 64073 => '爫', 64074 => '琢', 64075 => '碑', 64076 => '社', 64077 => '祉', 64078 => '祈', 64079 => '祐', 64080 => '祖', 64081 => '祝', 64082 => '禍', 64083 => '禎', 64084 => '穀', 64085 => '突', 64086 => '節', 64087 => '練', 64088 => '縉', 64089 => '繁', 64090 => '署', 64091 => '者', 64092 => '臭', 64093 => '艹', 64094 => '艹', 64095 => '著', 64096 => '褐', 64097 => '視', 64098 => '謁', 64099 => '謹', 64100 => '賓', 64101 => '贈', 64102 => '辶', 64103 => '逸', 64104 => '難', 64105 => '響', 64106 => '頻', 64107 => '恵', 64108 => '𤋮', 64109 => '舘', 64112 => '並', 64113 => '况', 64114 => '全', 64115 => '侀', 64116 => '充', 64117 => '冀', 64118 => '勇', 64119 => '勺', 64120 => '喝', 64121 => '啕', 64122 => '喙', 64123 => '嗢', 64124 => '塚', 64125 => '墳', 64126 => '奄', 64127 => '奔', 64128 => '婢', 64129 => '嬨', 64130 => '廒', 64131 => '廙', 64132 => '彩', 64133 => '徭', 64134 => '惘', 64135 => '慎', 64136 => '愈', 64137 => '憎', 64138 => '慠', 64139 => '懲', 64140 => '戴', 64141 => '揄', 64142 => '搜', 64143 => '摒', 64144 => '敖', 64145 => '晴', 64146 => '朗', 64147 => '望', 64148 => '杖', 64149 => '歹', 64150 => '殺', 64151 => '流', 64152 => '滛', 64153 => '滋', 64154 => '漢', 64155 => '瀞', 64156 => '煮', 64157 => '瞧', 64158 => '爵', 64159 => '犯', 64160 => '猪', 64161 => '瑱', 64162 => '甆', 64163 => '画', 64164 => '瘝', 64165 => '瘟', 64166 => '益', 64167 => '盛', 64168 => '直', 64169 => '睊', 64170 => '着', 64171 => '磌', 64172 => '窱', 64173 => '節', 64174 => '类', 64175 => '絛', 64176 => '練', 64177 => '缾', 64178 => '者', 64179 => '荒', 64180 => '華', 64181 => '蝹', 64182 => '襁', 64183 => '覆', 64184 => '視', 64185 => '調', 64186 => '諸', 64187 => '請', 64188 => '謁', 64189 => '諾', 64190 => '諭', 64191 => '謹', 64192 => '變', 64193 => '贈', 64194 => '輸', 64195 => '遲', 64196 => '醙', 64197 => '鉶', 64198 => '陼', 64199 => '難', 64200 => '靖', 64201 => '韛', 64202 => '響', 64203 => '頋', 64204 => '頻', 64205 => '鬒', 64206 => '龜', 64207 => '𢡊', 64208 => '𢡄', 64209 => '𣏕', 64210 => '㮝', 64211 => '䀘', 64212 => '䀹', 64213 => '𥉉', 64214 => '𥳐', 64215 => '𧻓', 64216 => '齃', 64217 => '龎', 64256 => 'ff', 64257 => 'fi', 64258 => 'fl', 64259 => 'ffi', 64260 => 'ffl', 64261 => 'st', 64262 => 'st', 64275 => 'մն', 64276 => 'մե', 64277 => 'մի', 64278 => 'վն', 64279 => 'մխ', 64285 => 'יִ', 64287 => 'ײַ', 64288 => 'ע', 64289 => 'א', 64290 => 'ד', 64291 => 'ה', 64292 => 'כ', 64293 => 'ל', 64294 => 'ם', 64295 => 'ר', 64296 => 'ת', 64298 => 'שׁ', 64299 => 'שׂ', 64300 => 'שּׁ', 64301 => 'שּׂ', 64302 => 'אַ', 64303 => 'אָ', 64304 => 'אּ', 64305 => 'בּ', 64306 => 'גּ', 64307 => 'דּ', 64308 => 'הּ', 64309 => 'וּ', 64310 => 'זּ', 64312 => 'טּ', 64313 => 'יּ', 64314 => 'ךּ', 64315 => 'כּ', 64316 => 'לּ', 64318 => 'מּ', 64320 => 'נּ', 64321 => 'סּ', 64323 => 'ףּ', 64324 => 'פּ', 64326 => 'צּ', 64327 => 'קּ', 64328 => 'רּ', 64329 => 'שּ', 64330 => 'תּ', 64331 => 'וֹ', 64332 => 'בֿ', 64333 => 'כֿ', 64334 => 'פֿ', 64335 => 'אל', 64336 => 'ٱ', 64337 => 'ٱ', 64338 => 'ٻ', 64339 => 'ٻ', 64340 => 'ٻ', 64341 => 'ٻ', 64342 => 'پ', 64343 => 'پ', 64344 => 'پ', 64345 => 'پ', 64346 => 'ڀ', 64347 => 'ڀ', 64348 => 'ڀ', 64349 => 'ڀ', 64350 => 'ٺ', 64351 => 'ٺ', 64352 => 'ٺ', 64353 => 'ٺ', 64354 => 'ٿ', 64355 => 'ٿ', 64356 => 'ٿ', 64357 => 'ٿ', 64358 => 'ٹ', 64359 => 'ٹ', 64360 => 'ٹ', 64361 => 'ٹ', 64362 => 'ڤ', 64363 => 'ڤ', 64364 => 'ڤ', 64365 => 'ڤ', 64366 => 'ڦ', 64367 => 'ڦ', 64368 => 'ڦ', 64369 => 'ڦ', 64370 => 'ڄ', 64371 => 'ڄ', 64372 => 'ڄ', 64373 => 'ڄ', 64374 => 'ڃ', 64375 => 'ڃ', 64376 => 'ڃ', 64377 => 'ڃ', 64378 => 'چ', 64379 => 'چ', 64380 => 'چ', 64381 => 'چ', 64382 => 'ڇ', 64383 => 'ڇ', 64384 => 'ڇ', 64385 => 'ڇ', 64386 => 'ڍ', 64387 => 'ڍ', 64388 => 'ڌ', 64389 => 'ڌ', 64390 => 'ڎ', 64391 => 'ڎ', 64392 => 'ڈ', 64393 => 'ڈ', 64394 => 'ژ', 64395 => 'ژ', 64396 => 'ڑ', 64397 => 'ڑ', 64398 => 'ک', 64399 => 'ک', 64400 => 'ک', 64401 => 'ک', 64402 => 'گ', 64403 => 'گ', 64404 => 'گ', 64405 => 'گ', 64406 => 'ڳ', 64407 => 'ڳ', 64408 => 'ڳ', 64409 => 'ڳ', 64410 => 'ڱ', 64411 => 'ڱ', 64412 => 'ڱ', 64413 => 'ڱ', 64414 => 'ں', 64415 => 'ں', 64416 => 'ڻ', 64417 => 'ڻ', 64418 => 'ڻ', 64419 => 'ڻ', 64420 => 'ۀ', 64421 => 'ۀ', 64422 => 'ہ', 64423 => 'ہ', 64424 => 'ہ', 64425 => 'ہ', 64426 => 'ھ', 64427 => 'ھ', 64428 => 'ھ', 64429 => 'ھ', 64430 => 'ے', 64431 => 'ے', 64432 => 'ۓ', 64433 => 'ۓ', 64467 => 'ڭ', 64468 => 'ڭ', 64469 => 'ڭ', 64470 => 'ڭ', 64471 => 'ۇ', 64472 => 'ۇ', 64473 => 'ۆ', 64474 => 'ۆ', 64475 => 'ۈ', 64476 => 'ۈ', 64477 => 'ۇٴ', 64478 => 'ۋ', 64479 => 'ۋ', 64480 => 'ۅ', 64481 => 'ۅ', 64482 => 'ۉ', 64483 => 'ۉ', 64484 => 'ې', 64485 => 'ې', 64486 => 'ې', 64487 => 'ې', 64488 => 'ى', 64489 => 'ى', 64490 => 'ئا', 64491 => 'ئا', 64492 => 'ئە', 64493 => 'ئە', 64494 => 'ئو', 64495 => 'ئو', 64496 => 'ئۇ', 64497 => 'ئۇ', 64498 => 'ئۆ', 64499 => 'ئۆ', 64500 => 'ئۈ', 64501 => 'ئۈ', 64502 => 'ئې', 64503 => 'ئې', 64504 => 'ئې', 64505 => 'ئى', 64506 => 'ئى', 64507 => 'ئى', 64508 => 'ی', 64509 => 'ی', 64510 => 'ی', 64511 => 'ی', 64512 => 'ئج', 64513 => 'ئح', 64514 => 'ئم', 64515 => 'ئى', 64516 => 'ئي', 64517 => 'بج', 64518 => 'بح', 64519 => 'بخ', 64520 => 'بم', 64521 => 'بى', 64522 => 'بي', 64523 => 'تج', 64524 => 'تح', 64525 => 'تخ', 64526 => 'تم', 64527 => 'تى', 64528 => 'تي', 64529 => 'ثج', 64530 => 'ثم', 64531 => 'ثى', 64532 => 'ثي', 64533 => 'جح', 64534 => 'جم', 64535 => 'حج', 64536 => 'حم', 64537 => 'خج', 64538 => 'خح', 64539 => 'خم', 64540 => 'سج', 64541 => 'سح', 64542 => 'سخ', 64543 => 'سم', 64544 => 'صح', 64545 => 'صم', 64546 => 'ضج', 64547 => 'ضح', 64548 => 'ضخ', 64549 => 'ضم', 64550 => 'طح', 64551 => 'طم', 64552 => 'ظم', 64553 => 'عج', 64554 => 'عم', 64555 => 'غج', 64556 => 'غم', 64557 => 'فج', 64558 => 'فح', 64559 => 'فخ', 64560 => 'فم', 64561 => 'فى', 64562 => 'في', 64563 => 'قح', 64564 => 'قم', 64565 => 'قى', 64566 => 'قي', 64567 => 'كا', 64568 => 'كج', 64569 => 'كح', 64570 => 'كخ', 64571 => 'كل', 64572 => 'كم', 64573 => 'كى', 64574 => 'كي', 64575 => 'لج', 64576 => 'لح', 64577 => 'لخ', 64578 => 'لم', 64579 => 'لى', 64580 => 'لي', 64581 => 'مج', 64582 => 'مح', 64583 => 'مخ', 64584 => 'مم', 64585 => 'مى', 64586 => 'مي', 64587 => 'نج', 64588 => 'نح', 64589 => 'نخ', 64590 => 'نم', 64591 => 'نى', 64592 => 'ني', 64593 => 'هج', 64594 => 'هم', 64595 => 'هى', 64596 => 'هي', 64597 => 'يج', 64598 => 'يح', 64599 => 'يخ', 64600 => 'يم', 64601 => 'يى', 64602 => 'يي', 64603 => 'ذٰ', 64604 => 'رٰ', 64605 => 'ىٰ', 64612 => 'ئر', 64613 => 'ئز', 64614 => 'ئم', 64615 => 'ئن', 64616 => 'ئى', 64617 => 'ئي', 64618 => 'بر', 64619 => 'بز', 64620 => 'بم', 64621 => 'بن', 64622 => 'بى', 64623 => 'بي', 64624 => 'تر', 64625 => 'تز', 64626 => 'تم', 64627 => 'تن', 64628 => 'تى', 64629 => 'تي', 64630 => 'ثر', 64631 => 'ثز', 64632 => 'ثم', 64633 => 'ثن', 64634 => 'ثى', 64635 => 'ثي', 64636 => 'فى', 64637 => 'في', 64638 => 'قى', 64639 => 'قي', 64640 => 'كا', 64641 => 'كل', 64642 => 'كم', 64643 => 'كى', 64644 => 'كي', 64645 => 'لم', 64646 => 'لى', 64647 => 'لي', 64648 => 'ما', 64649 => 'مم', 64650 => 'نر', 64651 => 'نز', 64652 => 'نم', 64653 => 'نن', 64654 => 'نى', 64655 => 'ني', 64656 => 'ىٰ', 64657 => 'ير', 64658 => 'يز', 64659 => 'يم', 64660 => 'ين', 64661 => 'يى', 64662 => 'يي', 64663 => 'ئج', 64664 => 'ئح', 64665 => 'ئخ', 64666 => 'ئم', 64667 => 'ئه', 64668 => 'بج', 64669 => 'بح', 64670 => 'بخ', 64671 => 'بم', 64672 => 'به', 64673 => 'تج', 64674 => 'تح', 64675 => 'تخ', 64676 => 'تم', 64677 => 'ته', 64678 => 'ثم', 64679 => 'جح', 64680 => 'جم', 64681 => 'حج', 64682 => 'حم', 64683 => 'خج', 64684 => 'خم', 64685 => 'سج', 64686 => 'سح', 64687 => 'سخ', 64688 => 'سم', 64689 => 'صح', 64690 => 'صخ', 64691 => 'صم', 64692 => 'ضج', 64693 => 'ضح', 64694 => 'ضخ', 64695 => 'ضم', 64696 => 'طح', 64697 => 'ظم', 64698 => 'عج', 64699 => 'عم', 64700 => 'غج', 64701 => 'غم', 64702 => 'فج', 64703 => 'فح', 64704 => 'فخ', 64705 => 'فم', 64706 => 'قح', 64707 => 'قم', 64708 => 'كج', 64709 => 'كح', 64710 => 'كخ', 64711 => 'كل', 64712 => 'كم', 64713 => 'لج', 64714 => 'لح', 64715 => 'لخ', 64716 => 'لم', 64717 => 'له', 64718 => 'مج', 64719 => 'مح', 64720 => 'مخ', 64721 => 'مم', 64722 => 'نج', 64723 => 'نح', 64724 => 'نخ', 64725 => 'نم', 64726 => 'نه', 64727 => 'هج', 64728 => 'هم', 64729 => 'هٰ', 64730 => 'يج', 64731 => 'يح', 64732 => 'يخ', 64733 => 'يم', 64734 => 'يه', 64735 => 'ئم', 64736 => 'ئه', 64737 => 'بم', 64738 => 'به', 64739 => 'تم', 64740 => 'ته', 64741 => 'ثم', 64742 => 'ثه', 64743 => 'سم', 64744 => 'سه', 64745 => 'شم', 64746 => 'شه', 64747 => 'كل', 64748 => 'كم', 64749 => 'لم', 64750 => 'نم', 64751 => 'نه', 64752 => 'يم', 64753 => 'يه', 64754 => 'ـَّ', 64755 => 'ـُّ', 64756 => 'ـِّ', 64757 => 'طى', 64758 => 'طي', 64759 => 'عى', 64760 => 'عي', 64761 => 'غى', 64762 => 'غي', 64763 => 'سى', 64764 => 'سي', 64765 => 'شى', 64766 => 'شي', 64767 => 'حى', 64768 => 'حي', 64769 => 'جى', 64770 => 'جي', 64771 => 'خى', 64772 => 'خي', 64773 => 'صى', 64774 => 'صي', 64775 => 'ضى', 64776 => 'ضي', 64777 => 'شج', 64778 => 'شح', 64779 => 'شخ', 64780 => 'شم', 64781 => 'شر', 64782 => 'سر', 64783 => 'صر', 64784 => 'ضر', 64785 => 'طى', 64786 => 'طي', 64787 => 'عى', 64788 => 'عي', 64789 => 'غى', 64790 => 'غي', 64791 => 'سى', 64792 => 'سي', 64793 => 'شى', 64794 => 'شي', 64795 => 'حى', 64796 => 'حي', 64797 => 'جى', 64798 => 'جي', 64799 => 'خى', 64800 => 'خي', 64801 => 'صى', 64802 => 'صي', 64803 => 'ضى', 64804 => 'ضي', 64805 => 'شج', 64806 => 'شح', 64807 => 'شخ', 64808 => 'شم', 64809 => 'شر', 64810 => 'سر', 64811 => 'صر', 64812 => 'ضر', 64813 => 'شج', 64814 => 'شح', 64815 => 'شخ', 64816 => 'شم', 64817 => 'سه', 64818 => 'شه', 64819 => 'طم', 64820 => 'سج', 64821 => 'سح', 64822 => 'سخ', 64823 => 'شج', 64824 => 'شح', 64825 => 'شخ', 64826 => 'طم', 64827 => 'ظم', 64828 => 'اً', 64829 => 'اً', 64848 => 'تجم', 64849 => 'تحج', 64850 => 'تحج', 64851 => 'تحم', 64852 => 'تخم', 64853 => 'تمج', 64854 => 'تمح', 64855 => 'تمخ', 64856 => 'جمح', 64857 => 'جمح', 64858 => 'حمي', 64859 => 'حمى', 64860 => 'سحج', 64861 => 'سجح', 64862 => 'سجى', 64863 => 'سمح', 64864 => 'سمح', 64865 => 'سمج', 64866 => 'سمم', 64867 => 'سمم', 64868 => 'صحح', 64869 => 'صحح', 64870 => 'صمم', 64871 => 'شحم', 64872 => 'شحم', 64873 => 'شجي', 64874 => 'شمخ', 64875 => 'شمخ', 64876 => 'شمم', 64877 => 'شمم', 64878 => 'ضحى', 64879 => 'ضخم', 64880 => 'ضخم', 64881 => 'طمح', 64882 => 'طمح', 64883 => 'طمم', 64884 => 'طمي', 64885 => 'عجم', 64886 => 'عمم', 64887 => 'عمم', 64888 => 'عمى', 64889 => 'غمم', 64890 => 'غمي', 64891 => 'غمى', 64892 => 'فخم', 64893 => 'فخم', 64894 => 'قمح', 64895 => 'قمم', 64896 => 'لحم', 64897 => 'لحي', 64898 => 'لحى', 64899 => 'لجج', 64900 => 'لجج', 64901 => 'لخم', 64902 => 'لخم', 64903 => 'لمح', 64904 => 'لمح', 64905 => 'محج', 64906 => 'محم', 64907 => 'محي', 64908 => 'مجح', 64909 => 'مجم', 64910 => 'مخج', 64911 => 'مخم', 64914 => 'مجخ', 64915 => 'همج', 64916 => 'همم', 64917 => 'نحم', 64918 => 'نحى', 64919 => 'نجم', 64920 => 'نجم', 64921 => 'نجى', 64922 => 'نمي', 64923 => 'نمى', 64924 => 'يمم', 64925 => 'يمم', 64926 => 'بخي', 64927 => 'تجي', 64928 => 'تجى', 64929 => 'تخي', 64930 => 'تخى', 64931 => 'تمي', 64932 => 'تمى', 64933 => 'جمي', 64934 => 'جحى', 64935 => 'جمى', 64936 => 'سخى', 64937 => 'صحي', 64938 => 'شحي', 64939 => 'ضحي', 64940 => 'لجي', 64941 => 'لمي', 64942 => 'يحي', 64943 => 'يجي', 64944 => 'يمي', 64945 => 'ممي', 64946 => 'قمي', 64947 => 'نحي', 64948 => 'قمح', 64949 => 'لحم', 64950 => 'عمي', 64951 => 'كمي', 64952 => 'نجح', 64953 => 'مخي', 64954 => 'لجم', 64955 => 'كمم', 64956 => 'لجم', 64957 => 'نجح', 64958 => 'جحي', 64959 => 'حجي', 64960 => 'مجي', 64961 => 'فمي', 64962 => 'بحي', 64963 => 'كمم', 64964 => 'عجم', 64965 => 'صمم', 64966 => 'سخي', 64967 => 'نجي', 65008 => 'صلے', 65009 => 'قلے', 65010 => 'الله', 65011 => 'اكبر', 65012 => 'محمد', 65013 => 'صلعم', 65014 => 'رسول', 65015 => 'عليه', 65016 => 'وسلم', 65017 => 'صلى', 65020 => 'ریال', 65041 => '、', 65047 => '〖', 65048 => '〗', 65073 => '—', 65074 => '–', 65081 => '〔', 65082 => '〕', 65083 => '【', 65084 => '】', 65085 => '《', 65086 => '》', 65087 => '〈', 65088 => '〉', 65089 => '「', 65090 => '」', 65091 => '『', 65092 => '』', 65105 => '、', 65112 => '—', 65117 => '〔', 65118 => '〕', 65123 => '-', 65137 => 'ـً', 65143 => 'ـَ', 65145 => 'ـُ', 65147 => 'ـِ', 65149 => 'ـّ', 65151 => 'ـْ', 65152 => 'ء', 65153 => 'آ', 65154 => 'آ', 65155 => 'أ', 65156 => 'أ', 65157 => 'ؤ', 65158 => 'ؤ', 65159 => 'إ', 65160 => 'إ', 65161 => 'ئ', 65162 => 'ئ', 65163 => 'ئ', 65164 => 'ئ', 65165 => 'ا', 65166 => 'ا', 65167 => 'ب', 65168 => 'ب', 65169 => 'ب', 65170 => 'ب', 65171 => 'ة', 65172 => 'ة', 65173 => 'ت', 65174 => 'ت', 65175 => 'ت', 65176 => 'ت', 65177 => 'ث', 65178 => 'ث', 65179 => 'ث', 65180 => 'ث', 65181 => 'ج', 65182 => 'ج', 65183 => 'ج', 65184 => 'ج', 65185 => 'ح', 65186 => 'ح', 65187 => 'ح', 65188 => 'ح', 65189 => 'خ', 65190 => 'خ', 65191 => 'خ', 65192 => 'خ', 65193 => 'د', 65194 => 'د', 65195 => 'ذ', 65196 => 'ذ', 65197 => 'ر', 65198 => 'ر', 65199 => 'ز', 65200 => 'ز', 65201 => 'س', 65202 => 'س', 65203 => 'س', 65204 => 'س', 65205 => 'ش', 65206 => 'ش', 65207 => 'ش', 65208 => 'ش', 65209 => 'ص', 65210 => 'ص', 65211 => 'ص', 65212 => 'ص', 65213 => 'ض', 65214 => 'ض', 65215 => 'ض', 65216 => 'ض', 65217 => 'ط', 65218 => 'ط', 65219 => 'ط', 65220 => 'ط', 65221 => 'ظ', 65222 => 'ظ', 65223 => 'ظ', 65224 => 'ظ', 65225 => 'ع', 65226 => 'ع', 65227 => 'ع', 65228 => 'ع', 65229 => 'غ', 65230 => 'غ', 65231 => 'غ', 65232 => 'غ', 65233 => 'ف', 65234 => 'ف', 65235 => 'ف', 65236 => 'ف', 65237 => 'ق', 65238 => 'ق', 65239 => 'ق', 65240 => 'ق', 65241 => 'ك', 65242 => 'ك', 65243 => 'ك', 65244 => 'ك', 65245 => 'ل', 65246 => 'ل', 65247 => 'ل', 65248 => 'ل', 65249 => 'م', 65250 => 'م', 65251 => 'م', 65252 => 'م', 65253 => 'ن', 65254 => 'ن', 65255 => 'ن', 65256 => 'ن', 65257 => 'ه', 65258 => 'ه', 65259 => 'ه', 65260 => 'ه', 65261 => 'و', 65262 => 'و', 65263 => 'ى', 65264 => 'ى', 65265 => 'ي', 65266 => 'ي', 65267 => 'ي', 65268 => 'ي', 65269 => 'لآ', 65270 => 'لآ', 65271 => 'لأ', 65272 => 'لأ', 65273 => 'لإ', 65274 => 'لإ', 65275 => 'لا', 65276 => 'لا', 65293 => '-', 65294 => '.', 65296 => '0', 65297 => '1', 65298 => '2', 65299 => '3', 65300 => '4', 65301 => '5', 65302 => '6', 65303 => '7', 65304 => '8', 65305 => '9', 65313 => 'a', 65314 => 'b', 65315 => 'c', 65316 => 'd', 65317 => 'e', 65318 => 'f', 65319 => 'g', 65320 => 'h', 65321 => 'i', 65322 => 'j', 65323 => 'k', 65324 => 'l', 65325 => 'm', 65326 => 'n', 65327 => 'o', 65328 => 'p', 65329 => 'q', 65330 => 'r', 65331 => 's', 65332 => 't', 65333 => 'u', 65334 => 'v', 65335 => 'w', 65336 => 'x', 65337 => 'y', 65338 => 'z', 65345 => 'a', 65346 => 'b', 65347 => 'c', 65348 => 'd', 65349 => 'e', 65350 => 'f', 65351 => 'g', 65352 => 'h', 65353 => 'i', 65354 => 'j', 65355 => 'k', 65356 => 'l', 65357 => 'm', 65358 => 'n', 65359 => 'o', 65360 => 'p', 65361 => 'q', 65362 => 'r', 65363 => 's', 65364 => 't', 65365 => 'u', 65366 => 'v', 65367 => 'w', 65368 => 'x', 65369 => 'y', 65370 => 'z', 65375 => '⦅', 65376 => '⦆', 65377 => '.', 65378 => '「', 65379 => '」', 65380 => '、', 65381 => '・', 65382 => 'ヲ', 65383 => 'ァ', 65384 => 'ィ', 65385 => 'ゥ', 65386 => 'ェ', 65387 => 'ォ', 65388 => 'ャ', 65389 => 'ュ', 65390 => 'ョ', 65391 => 'ッ', 65392 => 'ー', 65393 => 'ア', 65394 => 'イ', 65395 => 'ウ', 65396 => 'エ', 65397 => 'オ', 65398 => 'カ', 65399 => 'キ', 65400 => 'ク', 65401 => 'ケ', 65402 => 'コ', 65403 => 'サ', 65404 => 'シ', 65405 => 'ス', 65406 => 'セ', 65407 => 'ソ', 65408 => 'タ', 65409 => 'チ', 65410 => 'ツ', 65411 => 'テ', 65412 => 'ト', 65413 => 'ナ', 65414 => 'ニ', 65415 => 'ヌ', 65416 => 'ネ', 65417 => 'ノ', 65418 => 'ハ', 65419 => 'ヒ', 65420 => 'フ', 65421 => 'ヘ', 65422 => 'ホ', 65423 => 'マ', 65424 => 'ミ', 65425 => 'ム', 65426 => 'メ', 65427 => 'モ', 65428 => 'ヤ', 65429 => 'ユ', 65430 => 'ヨ', 65431 => 'ラ', 65432 => 'リ', 65433 => 'ル', 65434 => 'レ', 65435 => 'ロ', 65436 => 'ワ', 65437 => 'ン', 65438 => '゙', 65439 => '゚', 65441 => 'ᄀ', 65442 => 'ᄁ', 65443 => 'ᆪ', 65444 => 'ᄂ', 65445 => 'ᆬ', 65446 => 'ᆭ', 65447 => 'ᄃ', 65448 => 'ᄄ', 65449 => 'ᄅ', 65450 => 'ᆰ', 65451 => 'ᆱ', 65452 => 'ᆲ', 65453 => 'ᆳ', 65454 => 'ᆴ', 65455 => 'ᆵ', 65456 => 'ᄚ', 65457 => 'ᄆ', 65458 => 'ᄇ', 65459 => 'ᄈ', 65460 => 'ᄡ', 65461 => 'ᄉ', 65462 => 'ᄊ', 65463 => 'ᄋ', 65464 => 'ᄌ', 65465 => 'ᄍ', 65466 => 'ᄎ', 65467 => 'ᄏ', 65468 => 'ᄐ', 65469 => 'ᄑ', 65470 => 'ᄒ', 65474 => 'ᅡ', 65475 => 'ᅢ', 65476 => 'ᅣ', 65477 => 'ᅤ', 65478 => 'ᅥ', 65479 => 'ᅦ', 65482 => 'ᅧ', 65483 => 'ᅨ', 65484 => 'ᅩ', 65485 => 'ᅪ', 65486 => 'ᅫ', 65487 => 'ᅬ', 65490 => 'ᅭ', 65491 => 'ᅮ', 65492 => 'ᅯ', 65493 => 'ᅰ', 65494 => 'ᅱ', 65495 => 'ᅲ', 65498 => 'ᅳ', 65499 => 'ᅴ', 65500 => 'ᅵ', 65504 => '¢', 65505 => '£', 65506 => '¬', 65508 => '¦', 65509 => '¥', 65510 => '₩', 65512 => '│', 65513 => '←', 65514 => '↑', 65515 => '→', 65516 => '↓', 65517 => '■', 65518 => '○', 66560 => '𐐨', 66561 => '𐐩', 66562 => '𐐪', 66563 => '𐐫', 66564 => '𐐬', 66565 => '𐐭', 66566 => '𐐮', 66567 => '𐐯', 66568 => '𐐰', 66569 => '𐐱', 66570 => '𐐲', 66571 => '𐐳', 66572 => '𐐴', 66573 => '𐐵', 66574 => '𐐶', 66575 => '𐐷', 66576 => '𐐸', 66577 => '𐐹', 66578 => '𐐺', 66579 => '𐐻', 66580 => '𐐼', 66581 => '𐐽', 66582 => '𐐾', 66583 => '𐐿', 66584 => '𐑀', 66585 => '𐑁', 66586 => '𐑂', 66587 => '𐑃', 66588 => '𐑄', 66589 => '𐑅', 66590 => '𐑆', 66591 => '𐑇', 66592 => '𐑈', 66593 => '𐑉', 66594 => '𐑊', 66595 => '𐑋', 66596 => '𐑌', 66597 => '𐑍', 66598 => '𐑎', 66599 => '𐑏', 66736 => '𐓘', 66737 => '𐓙', 66738 => '𐓚', 66739 => '𐓛', 66740 => '𐓜', 66741 => '𐓝', 66742 => '𐓞', 66743 => '𐓟', 66744 => '𐓠', 66745 => '𐓡', 66746 => '𐓢', 66747 => '𐓣', 66748 => '𐓤', 66749 => '𐓥', 66750 => '𐓦', 66751 => '𐓧', 66752 => '𐓨', 66753 => '𐓩', 66754 => '𐓪', 66755 => '𐓫', 66756 => '𐓬', 66757 => '𐓭', 66758 => '𐓮', 66759 => '𐓯', 66760 => '𐓰', 66761 => '𐓱', 66762 => '𐓲', 66763 => '𐓳', 66764 => '𐓴', 66765 => '𐓵', 66766 => '𐓶', 66767 => '𐓷', 66768 => '𐓸', 66769 => '𐓹', 66770 => '𐓺', 66771 => '𐓻', 66928 => '𐖗', 66929 => '𐖘', 66930 => '𐖙', 66931 => '𐖚', 66932 => '𐖛', 66933 => '𐖜', 66934 => '𐖝', 66935 => '𐖞', 66936 => '𐖟', 66937 => '𐖠', 66938 => '𐖡', 66940 => '𐖣', 66941 => '𐖤', 66942 => '𐖥', 66943 => '𐖦', 66944 => '𐖧', 66945 => '𐖨', 66946 => '𐖩', 66947 => '𐖪', 66948 => '𐖫', 66949 => '𐖬', 66950 => '𐖭', 66951 => '𐖮', 66952 => '𐖯', 66953 => '𐖰', 66954 => '𐖱', 66956 => '𐖳', 66957 => '𐖴', 66958 => '𐖵', 66959 => '𐖶', 66960 => '𐖷', 66961 => '𐖸', 66962 => '𐖹', 66964 => '𐖻', 66965 => '𐖼', 67457 => 'ː', 67458 => 'ˑ', 67459 => 'æ', 67460 => 'ʙ', 67461 => 'ɓ', 67463 => 'ʣ', 67464 => 'ꭦ', 67465 => 'ʥ', 67466 => 'ʤ', 67467 => 'ɖ', 67468 => 'ɗ', 67469 => 'ᶑ', 67470 => 'ɘ', 67471 => 'ɞ', 67472 => 'ʩ', 67473 => 'ɤ', 67474 => 'ɢ', 67475 => 'ɠ', 67476 => 'ʛ', 67477 => 'ħ', 67478 => 'ʜ', 67479 => 'ɧ', 67480 => 'ʄ', 67481 => 'ʪ', 67482 => 'ʫ', 67483 => 'ɬ', 67484 => '𝼄', 67485 => 'ꞎ', 67486 => 'ɮ', 67487 => '𝼅', 67488 => 'ʎ', 67489 => '𝼆', 67490 => 'ø', 67491 => 'ɶ', 67492 => 'ɷ', 67493 => 'q', 67494 => 'ɺ', 67495 => '𝼈', 67496 => 'ɽ', 67497 => 'ɾ', 67498 => 'ʀ', 67499 => 'ʨ', 67500 => 'ʦ', 67501 => 'ꭧ', 67502 => 'ʧ', 67503 => 'ʈ', 67504 => 'ⱱ', 67506 => 'ʏ', 67507 => 'ʡ', 67508 => 'ʢ', 67509 => 'ʘ', 67510 => 'ǀ', 67511 => 'ǁ', 67512 => 'ǂ', 67513 => '𝼊', 67514 => '𝼞', 68736 => '𐳀', 68737 => '𐳁', 68738 => '𐳂', 68739 => '𐳃', 68740 => '𐳄', 68741 => '𐳅', 68742 => '𐳆', 68743 => '𐳇', 68744 => '𐳈', 68745 => '𐳉', 68746 => '𐳊', 68747 => '𐳋', 68748 => '𐳌', 68749 => '𐳍', 68750 => '𐳎', 68751 => '𐳏', 68752 => '𐳐', 68753 => '𐳑', 68754 => '𐳒', 68755 => '𐳓', 68756 => '𐳔', 68757 => '𐳕', 68758 => '𐳖', 68759 => '𐳗', 68760 => '𐳘', 68761 => '𐳙', 68762 => '𐳚', 68763 => '𐳛', 68764 => '𐳜', 68765 => '𐳝', 68766 => '𐳞', 68767 => '𐳟', 68768 => '𐳠', 68769 => '𐳡', 68770 => '𐳢', 68771 => '𐳣', 68772 => '𐳤', 68773 => '𐳥', 68774 => '𐳦', 68775 => '𐳧', 68776 => '𐳨', 68777 => '𐳩', 68778 => '𐳪', 68779 => '𐳫', 68780 => '𐳬', 68781 => '𐳭', 68782 => '𐳮', 68783 => '𐳯', 68784 => '𐳰', 68785 => '𐳱', 68786 => '𐳲', 71840 => '𑣀', 71841 => '𑣁', 71842 => '𑣂', 71843 => '𑣃', 71844 => '𑣄', 71845 => '𑣅', 71846 => '𑣆', 71847 => '𑣇', 71848 => '𑣈', 71849 => '𑣉', 71850 => '𑣊', 71851 => '𑣋', 71852 => '𑣌', 71853 => '𑣍', 71854 => '𑣎', 71855 => '𑣏', 71856 => '𑣐', 71857 => '𑣑', 71858 => '𑣒', 71859 => '𑣓', 71860 => '𑣔', 71861 => '𑣕', 71862 => '𑣖', 71863 => '𑣗', 71864 => '𑣘', 71865 => '𑣙', 71866 => '𑣚', 71867 => '𑣛', 71868 => '𑣜', 71869 => '𑣝', 71870 => '𑣞', 71871 => '𑣟', 93760 => '𖹠', 93761 => '𖹡', 93762 => '𖹢', 93763 => '𖹣', 93764 => '𖹤', 93765 => '𖹥', 93766 => '𖹦', 93767 => '𖹧', 93768 => '𖹨', 93769 => '𖹩', 93770 => '𖹪', 93771 => '𖹫', 93772 => '𖹬', 93773 => '𖹭', 93774 => '𖹮', 93775 => '𖹯', 93776 => '𖹰', 93777 => '𖹱', 93778 => '𖹲', 93779 => '𖹳', 93780 => '𖹴', 93781 => '𖹵', 93782 => '𖹶', 93783 => '𖹷', 93784 => '𖹸', 93785 => '𖹹', 93786 => '𖹺', 93787 => '𖹻', 93788 => '𖹼', 93789 => '𖹽', 93790 => '𖹾', 93791 => '𖹿', 119134 => '𝅗𝅥', 119135 => '𝅘𝅥', 119136 => '𝅘𝅥𝅮', 119137 => '𝅘𝅥𝅯', 119138 => '𝅘𝅥𝅰', 119139 => '𝅘𝅥𝅱', 119140 => '𝅘𝅥𝅲', 119227 => '𝆹𝅥', 119228 => '𝆺𝅥', 119229 => '𝆹𝅥𝅮', 119230 => '𝆺𝅥𝅮', 119231 => '𝆹𝅥𝅯', 119232 => '𝆺𝅥𝅯', 119808 => 'a', 119809 => 'b', 119810 => 'c', 119811 => 'd', 119812 => 'e', 119813 => 'f', 119814 => 'g', 119815 => 'h', 119816 => 'i', 119817 => 'j', 119818 => 'k', 119819 => 'l', 119820 => 'm', 119821 => 'n', 119822 => 'o', 119823 => 'p', 119824 => 'q', 119825 => 'r', 119826 => 's', 119827 => 't', 119828 => 'u', 119829 => 'v', 119830 => 'w', 119831 => 'x', 119832 => 'y', 119833 => 'z', 119834 => 'a', 119835 => 'b', 119836 => 'c', 119837 => 'd', 119838 => 'e', 119839 => 'f', 119840 => 'g', 119841 => 'h', 119842 => 'i', 119843 => 'j', 119844 => 'k', 119845 => 'l', 119846 => 'm', 119847 => 'n', 119848 => 'o', 119849 => 'p', 119850 => 'q', 119851 => 'r', 119852 => 's', 119853 => 't', 119854 => 'u', 119855 => 'v', 119856 => 'w', 119857 => 'x', 119858 => 'y', 119859 => 'z', 119860 => 'a', 119861 => 'b', 119862 => 'c', 119863 => 'd', 119864 => 'e', 119865 => 'f', 119866 => 'g', 119867 => 'h', 119868 => 'i', 119869 => 'j', 119870 => 'k', 119871 => 'l', 119872 => 'm', 119873 => 'n', 119874 => 'o', 119875 => 'p', 119876 => 'q', 119877 => 'r', 119878 => 's', 119879 => 't', 119880 => 'u', 119881 => 'v', 119882 => 'w', 119883 => 'x', 119884 => 'y', 119885 => 'z', 119886 => 'a', 119887 => 'b', 119888 => 'c', 119889 => 'd', 119890 => 'e', 119891 => 'f', 119892 => 'g', 119894 => 'i', 119895 => 'j', 119896 => 'k', 119897 => 'l', 119898 => 'm', 119899 => 'n', 119900 => 'o', 119901 => 'p', 119902 => 'q', 119903 => 'r', 119904 => 's', 119905 => 't', 119906 => 'u', 119907 => 'v', 119908 => 'w', 119909 => 'x', 119910 => 'y', 119911 => 'z', 119912 => 'a', 119913 => 'b', 119914 => 'c', 119915 => 'd', 119916 => 'e', 119917 => 'f', 119918 => 'g', 119919 => 'h', 119920 => 'i', 119921 => 'j', 119922 => 'k', 119923 => 'l', 119924 => 'm', 119925 => 'n', 119926 => 'o', 119927 => 'p', 119928 => 'q', 119929 => 'r', 119930 => 's', 119931 => 't', 119932 => 'u', 119933 => 'v', 119934 => 'w', 119935 => 'x', 119936 => 'y', 119937 => 'z', 119938 => 'a', 119939 => 'b', 119940 => 'c', 119941 => 'd', 119942 => 'e', 119943 => 'f', 119944 => 'g', 119945 => 'h', 119946 => 'i', 119947 => 'j', 119948 => 'k', 119949 => 'l', 119950 => 'm', 119951 => 'n', 119952 => 'o', 119953 => 'p', 119954 => 'q', 119955 => 'r', 119956 => 's', 119957 => 't', 119958 => 'u', 119959 => 'v', 119960 => 'w', 119961 => 'x', 119962 => 'y', 119963 => 'z', 119964 => 'a', 119966 => 'c', 119967 => 'd', 119970 => 'g', 119973 => 'j', 119974 => 'k', 119977 => 'n', 119978 => 'o', 119979 => 'p', 119980 => 'q', 119982 => 's', 119983 => 't', 119984 => 'u', 119985 => 'v', 119986 => 'w', 119987 => 'x', 119988 => 'y', 119989 => 'z', 119990 => 'a', 119991 => 'b', 119992 => 'c', 119993 => 'd', 119995 => 'f', 119997 => 'h', 119998 => 'i', 119999 => 'j', 120000 => 'k', 120001 => 'l', 120002 => 'm', 120003 => 'n', 120005 => 'p', 120006 => 'q', 120007 => 'r', 120008 => 's', 120009 => 't', 120010 => 'u', 120011 => 'v', 120012 => 'w', 120013 => 'x', 120014 => 'y', 120015 => 'z', 120016 => 'a', 120017 => 'b', 120018 => 'c', 120019 => 'd', 120020 => 'e', 120021 => 'f', 120022 => 'g', 120023 => 'h', 120024 => 'i', 120025 => 'j', 120026 => 'k', 120027 => 'l', 120028 => 'm', 120029 => 'n', 120030 => 'o', 120031 => 'p', 120032 => 'q', 120033 => 'r', 120034 => 's', 120035 => 't', 120036 => 'u', 120037 => 'v', 120038 => 'w', 120039 => 'x', 120040 => 'y', 120041 => 'z', 120042 => 'a', 120043 => 'b', 120044 => 'c', 120045 => 'd', 120046 => 'e', 120047 => 'f', 120048 => 'g', 120049 => 'h', 120050 => 'i', 120051 => 'j', 120052 => 'k', 120053 => 'l', 120054 => 'm', 120055 => 'n', 120056 => 'o', 120057 => 'p', 120058 => 'q', 120059 => 'r', 120060 => 's', 120061 => 't', 120062 => 'u', 120063 => 'v', 120064 => 'w', 120065 => 'x', 120066 => 'y', 120067 => 'z', 120068 => 'a', 120069 => 'b', 120071 => 'd', 120072 => 'e', 120073 => 'f', 120074 => 'g', 120077 => 'j', 120078 => 'k', 120079 => 'l', 120080 => 'm', 120081 => 'n', 120082 => 'o', 120083 => 'p', 120084 => 'q', 120086 => 's', 120087 => 't', 120088 => 'u', 120089 => 'v', 120090 => 'w', 120091 => 'x', 120092 => 'y', 120094 => 'a', 120095 => 'b', 120096 => 'c', 120097 => 'd', 120098 => 'e', 120099 => 'f', 120100 => 'g', 120101 => 'h', 120102 => 'i', 120103 => 'j', 120104 => 'k', 120105 => 'l', 120106 => 'm', 120107 => 'n', 120108 => 'o', 120109 => 'p', 120110 => 'q', 120111 => 'r', 120112 => 's', 120113 => 't', 120114 => 'u', 120115 => 'v', 120116 => 'w', 120117 => 'x', 120118 => 'y', 120119 => 'z', 120120 => 'a', 120121 => 'b', 120123 => 'd', 120124 => 'e', 120125 => 'f', 120126 => 'g', 120128 => 'i', 120129 => 'j', 120130 => 'k', 120131 => 'l', 120132 => 'm', 120134 => 'o', 120138 => 's', 120139 => 't', 120140 => 'u', 120141 => 'v', 120142 => 'w', 120143 => 'x', 120144 => 'y', 120146 => 'a', 120147 => 'b', 120148 => 'c', 120149 => 'd', 120150 => 'e', 120151 => 'f', 120152 => 'g', 120153 => 'h', 120154 => 'i', 120155 => 'j', 120156 => 'k', 120157 => 'l', 120158 => 'm', 120159 => 'n', 120160 => 'o', 120161 => 'p', 120162 => 'q', 120163 => 'r', 120164 => 's', 120165 => 't', 120166 => 'u', 120167 => 'v', 120168 => 'w', 120169 => 'x', 120170 => 'y', 120171 => 'z', 120172 => 'a', 120173 => 'b', 120174 => 'c', 120175 => 'd', 120176 => 'e', 120177 => 'f', 120178 => 'g', 120179 => 'h', 120180 => 'i', 120181 => 'j', 120182 => 'k', 120183 => 'l', 120184 => 'm', 120185 => 'n', 120186 => 'o', 120187 => 'p', 120188 => 'q', 120189 => 'r', 120190 => 's', 120191 => 't', 120192 => 'u', 120193 => 'v', 120194 => 'w', 120195 => 'x', 120196 => 'y', 120197 => 'z', 120198 => 'a', 120199 => 'b', 120200 => 'c', 120201 => 'd', 120202 => 'e', 120203 => 'f', 120204 => 'g', 120205 => 'h', 120206 => 'i', 120207 => 'j', 120208 => 'k', 120209 => 'l', 120210 => 'm', 120211 => 'n', 120212 => 'o', 120213 => 'p', 120214 => 'q', 120215 => 'r', 120216 => 's', 120217 => 't', 120218 => 'u', 120219 => 'v', 120220 => 'w', 120221 => 'x', 120222 => 'y', 120223 => 'z', 120224 => 'a', 120225 => 'b', 120226 => 'c', 120227 => 'd', 120228 => 'e', 120229 => 'f', 120230 => 'g', 120231 => 'h', 120232 => 'i', 120233 => 'j', 120234 => 'k', 120235 => 'l', 120236 => 'm', 120237 => 'n', 120238 => 'o', 120239 => 'p', 120240 => 'q', 120241 => 'r', 120242 => 's', 120243 => 't', 120244 => 'u', 120245 => 'v', 120246 => 'w', 120247 => 'x', 120248 => 'y', 120249 => 'z', 120250 => 'a', 120251 => 'b', 120252 => 'c', 120253 => 'd', 120254 => 'e', 120255 => 'f', 120256 => 'g', 120257 => 'h', 120258 => 'i', 120259 => 'j', 120260 => 'k', 120261 => 'l', 120262 => 'm', 120263 => 'n', 120264 => 'o', 120265 => 'p', 120266 => 'q', 120267 => 'r', 120268 => 's', 120269 => 't', 120270 => 'u', 120271 => 'v', 120272 => 'w', 120273 => 'x', 120274 => 'y', 120275 => 'z', 120276 => 'a', 120277 => 'b', 120278 => 'c', 120279 => 'd', 120280 => 'e', 120281 => 'f', 120282 => 'g', 120283 => 'h', 120284 => 'i', 120285 => 'j', 120286 => 'k', 120287 => 'l', 120288 => 'm', 120289 => 'n', 120290 => 'o', 120291 => 'p', 120292 => 'q', 120293 => 'r', 120294 => 's', 120295 => 't', 120296 => 'u', 120297 => 'v', 120298 => 'w', 120299 => 'x', 120300 => 'y', 120301 => 'z', 120302 => 'a', 120303 => 'b', 120304 => 'c', 120305 => 'd', 120306 => 'e', 120307 => 'f', 120308 => 'g', 120309 => 'h', 120310 => 'i', 120311 => 'j', 120312 => 'k', 120313 => 'l', 120314 => 'm', 120315 => 'n', 120316 => 'o', 120317 => 'p', 120318 => 'q', 120319 => 'r', 120320 => 's', 120321 => 't', 120322 => 'u', 120323 => 'v', 120324 => 'w', 120325 => 'x', 120326 => 'y', 120327 => 'z', 120328 => 'a', 120329 => 'b', 120330 => 'c', 120331 => 'd', 120332 => 'e', 120333 => 'f', 120334 => 'g', 120335 => 'h', 120336 => 'i', 120337 => 'j', 120338 => 'k', 120339 => 'l', 120340 => 'm', 120341 => 'n', 120342 => 'o', 120343 => 'p', 120344 => 'q', 120345 => 'r', 120346 => 's', 120347 => 't', 120348 => 'u', 120349 => 'v', 120350 => 'w', 120351 => 'x', 120352 => 'y', 120353 => 'z', 120354 => 'a', 120355 => 'b', 120356 => 'c', 120357 => 'd', 120358 => 'e', 120359 => 'f', 120360 => 'g', 120361 => 'h', 120362 => 'i', 120363 => 'j', 120364 => 'k', 120365 => 'l', 120366 => 'm', 120367 => 'n', 120368 => 'o', 120369 => 'p', 120370 => 'q', 120371 => 'r', 120372 => 's', 120373 => 't', 120374 => 'u', 120375 => 'v', 120376 => 'w', 120377 => 'x', 120378 => 'y', 120379 => 'z', 120380 => 'a', 120381 => 'b', 120382 => 'c', 120383 => 'd', 120384 => 'e', 120385 => 'f', 120386 => 'g', 120387 => 'h', 120388 => 'i', 120389 => 'j', 120390 => 'k', 120391 => 'l', 120392 => 'm', 120393 => 'n', 120394 => 'o', 120395 => 'p', 120396 => 'q', 120397 => 'r', 120398 => 's', 120399 => 't', 120400 => 'u', 120401 => 'v', 120402 => 'w', 120403 => 'x', 120404 => 'y', 120405 => 'z', 120406 => 'a', 120407 => 'b', 120408 => 'c', 120409 => 'd', 120410 => 'e', 120411 => 'f', 120412 => 'g', 120413 => 'h', 120414 => 'i', 120415 => 'j', 120416 => 'k', 120417 => 'l', 120418 => 'm', 120419 => 'n', 120420 => 'o', 120421 => 'p', 120422 => 'q', 120423 => 'r', 120424 => 's', 120425 => 't', 120426 => 'u', 120427 => 'v', 120428 => 'w', 120429 => 'x', 120430 => 'y', 120431 => 'z', 120432 => 'a', 120433 => 'b', 120434 => 'c', 120435 => 'd', 120436 => 'e', 120437 => 'f', 120438 => 'g', 120439 => 'h', 120440 => 'i', 120441 => 'j', 120442 => 'k', 120443 => 'l', 120444 => 'm', 120445 => 'n', 120446 => 'o', 120447 => 'p', 120448 => 'q', 120449 => 'r', 120450 => 's', 120451 => 't', 120452 => 'u', 120453 => 'v', 120454 => 'w', 120455 => 'x', 120456 => 'y', 120457 => 'z', 120458 => 'a', 120459 => 'b', 120460 => 'c', 120461 => 'd', 120462 => 'e', 120463 => 'f', 120464 => 'g', 120465 => 'h', 120466 => 'i', 120467 => 'j', 120468 => 'k', 120469 => 'l', 120470 => 'm', 120471 => 'n', 120472 => 'o', 120473 => 'p', 120474 => 'q', 120475 => 'r', 120476 => 's', 120477 => 't', 120478 => 'u', 120479 => 'v', 120480 => 'w', 120481 => 'x', 120482 => 'y', 120483 => 'z', 120484 => 'ı', 120485 => 'ȷ', 120488 => 'α', 120489 => 'β', 120490 => 'γ', 120491 => 'δ', 120492 => 'ε', 120493 => 'ζ', 120494 => 'η', 120495 => 'θ', 120496 => 'ι', 120497 => 'κ', 120498 => 'λ', 120499 => 'μ', 120500 => 'ν', 120501 => 'ξ', 120502 => 'ο', 120503 => 'π', 120504 => 'ρ', 120505 => 'θ', 120506 => 'σ', 120507 => 'τ', 120508 => 'υ', 120509 => 'φ', 120510 => 'χ', 120511 => 'ψ', 120512 => 'ω', 120513 => '∇', 120514 => 'α', 120515 => 'β', 120516 => 'γ', 120517 => 'δ', 120518 => 'ε', 120519 => 'ζ', 120520 => 'η', 120521 => 'θ', 120522 => 'ι', 120523 => 'κ', 120524 => 'λ', 120525 => 'μ', 120526 => 'ν', 120527 => 'ξ', 120528 => 'ο', 120529 => 'π', 120530 => 'ρ', 120531 => 'σ', 120532 => 'σ', 120533 => 'τ', 120534 => 'υ', 120535 => 'φ', 120536 => 'χ', 120537 => 'ψ', 120538 => 'ω', 120539 => '∂', 120540 => 'ε', 120541 => 'θ', 120542 => 'κ', 120543 => 'φ', 120544 => 'ρ', 120545 => 'π', 120546 => 'α', 120547 => 'β', 120548 => 'γ', 120549 => 'δ', 120550 => 'ε', 120551 => 'ζ', 120552 => 'η', 120553 => 'θ', 120554 => 'ι', 120555 => 'κ', 120556 => 'λ', 120557 => 'μ', 120558 => 'ν', 120559 => 'ξ', 120560 => 'ο', 120561 => 'π', 120562 => 'ρ', 120563 => 'θ', 120564 => 'σ', 120565 => 'τ', 120566 => 'υ', 120567 => 'φ', 120568 => 'χ', 120569 => 'ψ', 120570 => 'ω', 120571 => '∇', 120572 => 'α', 120573 => 'β', 120574 => 'γ', 120575 => 'δ', 120576 => 'ε', 120577 => 'ζ', 120578 => 'η', 120579 => 'θ', 120580 => 'ι', 120581 => 'κ', 120582 => 'λ', 120583 => 'μ', 120584 => 'ν', 120585 => 'ξ', 120586 => 'ο', 120587 => 'π', 120588 => 'ρ', 120589 => 'σ', 120590 => 'σ', 120591 => 'τ', 120592 => 'υ', 120593 => 'φ', 120594 => 'χ', 120595 => 'ψ', 120596 => 'ω', 120597 => '∂', 120598 => 'ε', 120599 => 'θ', 120600 => 'κ', 120601 => 'φ', 120602 => 'ρ', 120603 => 'π', 120604 => 'α', 120605 => 'β', 120606 => 'γ', 120607 => 'δ', 120608 => 'ε', 120609 => 'ζ', 120610 => 'η', 120611 => 'θ', 120612 => 'ι', 120613 => 'κ', 120614 => 'λ', 120615 => 'μ', 120616 => 'ν', 120617 => 'ξ', 120618 => 'ο', 120619 => 'π', 120620 => 'ρ', 120621 => 'θ', 120622 => 'σ', 120623 => 'τ', 120624 => 'υ', 120625 => 'φ', 120626 => 'χ', 120627 => 'ψ', 120628 => 'ω', 120629 => '∇', 120630 => 'α', 120631 => 'β', 120632 => 'γ', 120633 => 'δ', 120634 => 'ε', 120635 => 'ζ', 120636 => 'η', 120637 => 'θ', 120638 => 'ι', 120639 => 'κ', 120640 => 'λ', 120641 => 'μ', 120642 => 'ν', 120643 => 'ξ', 120644 => 'ο', 120645 => 'π', 120646 => 'ρ', 120647 => 'σ', 120648 => 'σ', 120649 => 'τ', 120650 => 'υ', 120651 => 'φ', 120652 => 'χ', 120653 => 'ψ', 120654 => 'ω', 120655 => '∂', 120656 => 'ε', 120657 => 'θ', 120658 => 'κ', 120659 => 'φ', 120660 => 'ρ', 120661 => 'π', 120662 => 'α', 120663 => 'β', 120664 => 'γ', 120665 => 'δ', 120666 => 'ε', 120667 => 'ζ', 120668 => 'η', 120669 => 'θ', 120670 => 'ι', 120671 => 'κ', 120672 => 'λ', 120673 => 'μ', 120674 => 'ν', 120675 => 'ξ', 120676 => 'ο', 120677 => 'π', 120678 => 'ρ', 120679 => 'θ', 120680 => 'σ', 120681 => 'τ', 120682 => 'υ', 120683 => 'φ', 120684 => 'χ', 120685 => 'ψ', 120686 => 'ω', 120687 => '∇', 120688 => 'α', 120689 => 'β', 120690 => 'γ', 120691 => 'δ', 120692 => 'ε', 120693 => 'ζ', 120694 => 'η', 120695 => 'θ', 120696 => 'ι', 120697 => 'κ', 120698 => 'λ', 120699 => 'μ', 120700 => 'ν', 120701 => 'ξ', 120702 => 'ο', 120703 => 'π', 120704 => 'ρ', 120705 => 'σ', 120706 => 'σ', 120707 => 'τ', 120708 => 'υ', 120709 => 'φ', 120710 => 'χ', 120711 => 'ψ', 120712 => 'ω', 120713 => '∂', 120714 => 'ε', 120715 => 'θ', 120716 => 'κ', 120717 => 'φ', 120718 => 'ρ', 120719 => 'π', 120720 => 'α', 120721 => 'β', 120722 => 'γ', 120723 => 'δ', 120724 => 'ε', 120725 => 'ζ', 120726 => 'η', 120727 => 'θ', 120728 => 'ι', 120729 => 'κ', 120730 => 'λ', 120731 => 'μ', 120732 => 'ν', 120733 => 'ξ', 120734 => 'ο', 120735 => 'π', 120736 => 'ρ', 120737 => 'θ', 120738 => 'σ', 120739 => 'τ', 120740 => 'υ', 120741 => 'φ', 120742 => 'χ', 120743 => 'ψ', 120744 => 'ω', 120745 => '∇', 120746 => 'α', 120747 => 'β', 120748 => 'γ', 120749 => 'δ', 120750 => 'ε', 120751 => 'ζ', 120752 => 'η', 120753 => 'θ', 120754 => 'ι', 120755 => 'κ', 120756 => 'λ', 120757 => 'μ', 120758 => 'ν', 120759 => 'ξ', 120760 => 'ο', 120761 => 'π', 120762 => 'ρ', 120763 => 'σ', 120764 => 'σ', 120765 => 'τ', 120766 => 'υ', 120767 => 'φ', 120768 => 'χ', 120769 => 'ψ', 120770 => 'ω', 120771 => '∂', 120772 => 'ε', 120773 => 'θ', 120774 => 'κ', 120775 => 'φ', 120776 => 'ρ', 120777 => 'π', 120778 => 'ϝ', 120779 => 'ϝ', 120782 => '0', 120783 => '1', 120784 => '2', 120785 => '3', 120786 => '4', 120787 => '5', 120788 => '6', 120789 => '7', 120790 => '8', 120791 => '9', 120792 => '0', 120793 => '1', 120794 => '2', 120795 => '3', 120796 => '4', 120797 => '5', 120798 => '6', 120799 => '7', 120800 => '8', 120801 => '9', 120802 => '0', 120803 => '1', 120804 => '2', 120805 => '3', 120806 => '4', 120807 => '5', 120808 => '6', 120809 => '7', 120810 => '8', 120811 => '9', 120812 => '0', 120813 => '1', 120814 => '2', 120815 => '3', 120816 => '4', 120817 => '5', 120818 => '6', 120819 => '7', 120820 => '8', 120821 => '9', 120822 => '0', 120823 => '1', 120824 => '2', 120825 => '3', 120826 => '4', 120827 => '5', 120828 => '6', 120829 => '7', 120830 => '8', 120831 => '9', 125184 => '𞤢', 125185 => '𞤣', 125186 => '𞤤', 125187 => '𞤥', 125188 => '𞤦', 125189 => '𞤧', 125190 => '𞤨', 125191 => '𞤩', 125192 => '𞤪', 125193 => '𞤫', 125194 => '𞤬', 125195 => '𞤭', 125196 => '𞤮', 125197 => '𞤯', 125198 => '𞤰', 125199 => '𞤱', 125200 => '𞤲', 125201 => '𞤳', 125202 => '𞤴', 125203 => '𞤵', 125204 => '𞤶', 125205 => '𞤷', 125206 => '𞤸', 125207 => '𞤹', 125208 => '𞤺', 125209 => '𞤻', 125210 => '𞤼', 125211 => '𞤽', 125212 => '𞤾', 125213 => '𞤿', 125214 => '𞥀', 125215 => '𞥁', 125216 => '𞥂', 125217 => '𞥃', 126464 => 'ا', 126465 => 'ب', 126466 => 'ج', 126467 => 'د', 126469 => 'و', 126470 => 'ز', 126471 => 'ح', 126472 => 'ط', 126473 => 'ي', 126474 => 'ك', 126475 => 'ل', 126476 => 'م', 126477 => 'ن', 126478 => 'س', 126479 => 'ع', 126480 => 'ف', 126481 => 'ص', 126482 => 'ق', 126483 => 'ر', 126484 => 'ش', 126485 => 'ت', 126486 => 'ث', 126487 => 'خ', 126488 => 'ذ', 126489 => 'ض', 126490 => 'ظ', 126491 => 'غ', 126492 => 'ٮ', 126493 => 'ں', 126494 => 'ڡ', 126495 => 'ٯ', 126497 => 'ب', 126498 => 'ج', 126500 => 'ه', 126503 => 'ح', 126505 => 'ي', 126506 => 'ك', 126507 => 'ل', 126508 => 'م', 126509 => 'ن', 126510 => 'س', 126511 => 'ع', 126512 => 'ف', 126513 => 'ص', 126514 => 'ق', 126516 => 'ش', 126517 => 'ت', 126518 => 'ث', 126519 => 'خ', 126521 => 'ض', 126523 => 'غ', 126530 => 'ج', 126535 => 'ح', 126537 => 'ي', 126539 => 'ل', 126541 => 'ن', 126542 => 'س', 126543 => 'ع', 126545 => 'ص', 126546 => 'ق', 126548 => 'ش', 126551 => 'خ', 126553 => 'ض', 126555 => 'غ', 126557 => 'ں', 126559 => 'ٯ', 126561 => 'ب', 126562 => 'ج', 126564 => 'ه', 126567 => 'ح', 126568 => 'ط', 126569 => 'ي', 126570 => 'ك', 126572 => 'م', 126573 => 'ن', 126574 => 'س', 126575 => 'ع', 126576 => 'ف', 126577 => 'ص', 126578 => 'ق', 126580 => 'ش', 126581 => 'ت', 126582 => 'ث', 126583 => 'خ', 126585 => 'ض', 126586 => 'ظ', 126587 => 'غ', 126588 => 'ٮ', 126590 => 'ڡ', 126592 => 'ا', 126593 => 'ب', 126594 => 'ج', 126595 => 'د', 126596 => 'ه', 126597 => 'و', 126598 => 'ز', 126599 => 'ح', 126600 => 'ط', 126601 => 'ي', 126603 => 'ل', 126604 => 'م', 126605 => 'ن', 126606 => 'س', 126607 => 'ع', 126608 => 'ف', 126609 => 'ص', 126610 => 'ق', 126611 => 'ر', 126612 => 'ش', 126613 => 'ت', 126614 => 'ث', 126615 => 'خ', 126616 => 'ذ', 126617 => 'ض', 126618 => 'ظ', 126619 => 'غ', 126625 => 'ب', 126626 => 'ج', 126627 => 'د', 126629 => 'و', 126630 => 'ز', 126631 => 'ح', 126632 => 'ط', 126633 => 'ي', 126635 => 'ل', 126636 => 'م', 126637 => 'ن', 126638 => 'س', 126639 => 'ع', 126640 => 'ف', 126641 => 'ص', 126642 => 'ق', 126643 => 'ر', 126644 => 'ش', 126645 => 'ت', 126646 => 'ث', 126647 => 'خ', 126648 => 'ذ', 126649 => 'ض', 126650 => 'ظ', 126651 => 'غ', 127274 => '〔s〕', 127275 => 'c', 127276 => 'r', 127277 => 'cd', 127278 => 'wz', 127280 => 'a', 127281 => 'b', 127282 => 'c', 127283 => 'd', 127284 => 'e', 127285 => 'f', 127286 => 'g', 127287 => 'h', 127288 => 'i', 127289 => 'j', 127290 => 'k', 127291 => 'l', 127292 => 'm', 127293 => 'n', 127294 => 'o', 127295 => 'p', 127296 => 'q', 127297 => 'r', 127298 => 's', 127299 => 't', 127300 => 'u', 127301 => 'v', 127302 => 'w', 127303 => 'x', 127304 => 'y', 127305 => 'z', 127306 => 'hv', 127307 => 'mv', 127308 => 'sd', 127309 => 'ss', 127310 => 'ppv', 127311 => 'wc', 127338 => 'mc', 127339 => 'md', 127340 => 'mr', 127376 => 'dj', 127488 => 'ほか', 127489 => 'ココ', 127490 => 'サ', 127504 => '手', 127505 => '字', 127506 => '双', 127507 => 'デ', 127508 => '二', 127509 => '多', 127510 => '解', 127511 => '天', 127512 => '交', 127513 => '映', 127514 => '無', 127515 => '料', 127516 => '前', 127517 => '後', 127518 => '再', 127519 => '新', 127520 => '初', 127521 => '終', 127522 => '生', 127523 => '販', 127524 => '声', 127525 => '吹', 127526 => '演', 127527 => '投', 127528 => '捕', 127529 => '一', 127530 => '三', 127531 => '遊', 127532 => '左', 127533 => '中', 127534 => '右', 127535 => '指', 127536 => '走', 127537 => '打', 127538 => '禁', 127539 => '空', 127540 => '合', 127541 => '満', 127542 => '有', 127543 => '月', 127544 => '申', 127545 => '割', 127546 => '営', 127547 => '配', 127552 => '〔本〕', 127553 => '〔三〕', 127554 => '〔二〕', 127555 => '〔安〕', 127556 => '〔点〕', 127557 => '〔打〕', 127558 => '〔盗〕', 127559 => '〔勝〕', 127560 => '〔敗〕', 127568 => '得', 127569 => '可', 130032 => '0', 130033 => '1', 130034 => '2', 130035 => '3', 130036 => '4', 130037 => '5', 130038 => '6', 130039 => '7', 130040 => '8', 130041 => '9', 194560 => '丽', 194561 => '丸', 194562 => '乁', 194563 => '𠄢', 194564 => '你', 194565 => '侮', 194566 => '侻', 194567 => '倂', 194568 => '偺', 194569 => '備', 194570 => '僧', 194571 => '像', 194572 => '㒞', 194573 => '𠘺', 194574 => '免', 194575 => '兔', 194576 => '兤', 194577 => '具', 194578 => '𠔜', 194579 => '㒹', 194580 => '內', 194581 => '再', 194582 => '𠕋', 194583 => '冗', 194584 => '冤', 194585 => '仌', 194586 => '冬', 194587 => '况', 194588 => '𩇟', 194589 => '凵', 194590 => '刃', 194591 => '㓟', 194592 => '刻', 194593 => '剆', 194594 => '割', 194595 => '剷', 194596 => '㔕', 194597 => '勇', 194598 => '勉', 194599 => '勤', 194600 => '勺', 194601 => '包', 194602 => '匆', 194603 => '北', 194604 => '卉', 194605 => '卑', 194606 => '博', 194607 => '即', 194608 => '卽', 194609 => '卿', 194610 => '卿', 194611 => '卿', 194612 => '𠨬', 194613 => '灰', 194614 => '及', 194615 => '叟', 194616 => '𠭣', 194617 => '叫', 194618 => '叱', 194619 => '吆', 194620 => '咞', 194621 => '吸', 194622 => '呈', 194623 => '周', 194624 => '咢', 194625 => '哶', 194626 => '唐', 194627 => '啓', 194628 => '啣', 194629 => '善', 194630 => '善', 194631 => '喙', 194632 => '喫', 194633 => '喳', 194634 => '嗂', 194635 => '圖', 194636 => '嘆', 194637 => '圗', 194638 => '噑', 194639 => '噴', 194640 => '切', 194641 => '壮', 194642 => '城', 194643 => '埴', 194644 => '堍', 194645 => '型', 194646 => '堲', 194647 => '報', 194648 => '墬', 194649 => '𡓤', 194650 => '売', 194651 => '壷', 194652 => '夆', 194653 => '多', 194654 => '夢', 194655 => '奢', 194656 => '𡚨', 194657 => '𡛪', 194658 => '姬', 194659 => '娛', 194660 => '娧', 194661 => '姘', 194662 => '婦', 194663 => '㛮', 194665 => '嬈', 194666 => '嬾', 194667 => '嬾', 194668 => '𡧈', 194669 => '寃', 194670 => '寘', 194671 => '寧', 194672 => '寳', 194673 => '𡬘', 194674 => '寿', 194675 => '将', 194677 => '尢', 194678 => '㞁', 194679 => '屠', 194680 => '屮', 194681 => '峀', 194682 => '岍', 194683 => '𡷤', 194684 => '嵃', 194685 => '𡷦', 194686 => '嵮', 194687 => '嵫', 194688 => '嵼', 194689 => '巡', 194690 => '巢', 194691 => '㠯', 194692 => '巽', 194693 => '帨', 194694 => '帽', 194695 => '幩', 194696 => '㡢', 194697 => '𢆃', 194698 => '㡼', 194699 => '庰', 194700 => '庳', 194701 => '庶', 194702 => '廊', 194703 => '𪎒', 194704 => '廾', 194705 => '𢌱', 194706 => '𢌱', 194707 => '舁', 194708 => '弢', 194709 => '弢', 194710 => '㣇', 194711 => '𣊸', 194712 => '𦇚', 194713 => '形', 194714 => '彫', 194715 => '㣣', 194716 => '徚', 194717 => '忍', 194718 => '志', 194719 => '忹', 194720 => '悁', 194721 => '㤺', 194722 => '㤜', 194723 => '悔', 194724 => '𢛔', 194725 => '惇', 194726 => '慈', 194727 => '慌', 194728 => '慎', 194729 => '慌', 194730 => '慺', 194731 => '憎', 194732 => '憲', 194733 => '憤', 194734 => '憯', 194735 => '懞', 194736 => '懲', 194737 => '懶', 194738 => '成', 194739 => '戛', 194740 => '扝', 194741 => '抱', 194742 => '拔', 194743 => '捐', 194744 => '𢬌', 194745 => '挽', 194746 => '拼', 194747 => '捨', 194748 => '掃', 194749 => '揤', 194750 => '𢯱', 194751 => '搢', 194752 => '揅', 194753 => '掩', 194754 => '㨮', 194755 => '摩', 194756 => '摾', 194757 => '撝', 194758 => '摷', 194759 => '㩬', 194760 => '敏', 194761 => '敬', 194762 => '𣀊', 194763 => '旣', 194764 => '書', 194765 => '晉', 194766 => '㬙', 194767 => '暑', 194768 => '㬈', 194769 => '㫤', 194770 => '冒', 194771 => '冕', 194772 => '最', 194773 => '暜', 194774 => '肭', 194775 => '䏙', 194776 => '朗', 194777 => '望', 194778 => '朡', 194779 => '杞', 194780 => '杓', 194781 => '𣏃', 194782 => '㭉', 194783 => '柺', 194784 => '枅', 194785 => '桒', 194786 => '梅', 194787 => '𣑭', 194788 => '梎', 194789 => '栟', 194790 => '椔', 194791 => '㮝', 194792 => '楂', 194793 => '榣', 194794 => '槪', 194795 => '檨', 194796 => '𣚣', 194797 => '櫛', 194798 => '㰘', 194799 => '次', 194800 => '𣢧', 194801 => '歔', 194802 => '㱎', 194803 => '歲', 194804 => '殟', 194805 => '殺', 194806 => '殻', 194807 => '𣪍', 194808 => '𡴋', 194809 => '𣫺', 194810 => '汎', 194811 => '𣲼', 194812 => '沿', 194813 => '泍', 194814 => '汧', 194815 => '洖', 194816 => '派', 194817 => '海', 194818 => '流', 194819 => '浩', 194820 => '浸', 194821 => '涅', 194822 => '𣴞', 194823 => '洴', 194824 => '港', 194825 => '湮', 194826 => '㴳', 194827 => '滋', 194828 => '滇', 194829 => '𣻑', 194830 => '淹', 194831 => '潮', 194832 => '𣽞', 194833 => '𣾎', 194834 => '濆', 194835 => '瀹', 194836 => '瀞', 194837 => '瀛', 194838 => '㶖', 194839 => '灊', 194840 => '災', 194841 => '灷', 194842 => '炭', 194843 => '𠔥', 194844 => '煅', 194845 => '𤉣', 194846 => '熜', 194848 => '爨', 194849 => '爵', 194850 => '牐', 194851 => '𤘈', 194852 => '犀', 194853 => '犕', 194854 => '𤜵', 194855 => '𤠔', 194856 => '獺', 194857 => '王', 194858 => '㺬', 194859 => '玥', 194860 => '㺸', 194861 => '㺸', 194862 => '瑇', 194863 => '瑜', 194864 => '瑱', 194865 => '璅', 194866 => '瓊', 194867 => '㼛', 194868 => '甤', 194869 => '𤰶', 194870 => '甾', 194871 => '𤲒', 194872 => '異', 194873 => '𢆟', 194874 => '瘐', 194875 => '𤾡', 194876 => '𤾸', 194877 => '𥁄', 194878 => '㿼', 194879 => '䀈', 194880 => '直', 194881 => '𥃳', 194882 => '𥃲', 194883 => '𥄙', 194884 => '𥄳', 194885 => '眞', 194886 => '真', 194887 => '真', 194888 => '睊', 194889 => '䀹', 194890 => '瞋', 194891 => '䁆', 194892 => '䂖', 194893 => '𥐝', 194894 => '硎', 194895 => '碌', 194896 => '磌', 194897 => '䃣', 194898 => '𥘦', 194899 => '祖', 194900 => '𥚚', 194901 => '𥛅', 194902 => '福', 194903 => '秫', 194904 => '䄯', 194905 => '穀', 194906 => '穊', 194907 => '穏', 194908 => '𥥼', 194909 => '𥪧', 194910 => '𥪧', 194912 => '䈂', 194913 => '𥮫', 194914 => '篆', 194915 => '築', 194916 => '䈧', 194917 => '𥲀', 194918 => '糒', 194919 => '䊠', 194920 => '糨', 194921 => '糣', 194922 => '紀', 194923 => '𥾆', 194924 => '絣', 194925 => '䌁', 194926 => '緇', 194927 => '縂', 194928 => '繅', 194929 => '䌴', 194930 => '𦈨', 194931 => '𦉇', 194932 => '䍙', 194933 => '𦋙', 194934 => '罺', 194935 => '𦌾', 194936 => '羕', 194937 => '翺', 194938 => '者', 194939 => '𦓚', 194940 => '𦔣', 194941 => '聠', 194942 => '𦖨', 194943 => '聰', 194944 => '𣍟', 194945 => '䏕', 194946 => '育', 194947 => '脃', 194948 => '䐋', 194949 => '脾', 194950 => '媵', 194951 => '𦞧', 194952 => '𦞵', 194953 => '𣎓', 194954 => '𣎜', 194955 => '舁', 194956 => '舄', 194957 => '辞', 194958 => '䑫', 194959 => '芑', 194960 => '芋', 194961 => '芝', 194962 => '劳', 194963 => '花', 194964 => '芳', 194965 => '芽', 194966 => '苦', 194967 => '𦬼', 194968 => '若', 194969 => '茝', 194970 => '荣', 194971 => '莭', 194972 => '茣', 194973 => '莽', 194974 => '菧', 194975 => '著', 194976 => '荓', 194977 => '菊', 194978 => '菌', 194979 => '菜', 194980 => '𦰶', 194981 => '𦵫', 194982 => '𦳕', 194983 => '䔫', 194984 => '蓱', 194985 => '蓳', 194986 => '蔖', 194987 => '𧏊', 194988 => '蕤', 194989 => '𦼬', 194990 => '䕝', 194991 => '䕡', 194992 => '𦾱', 194993 => '𧃒', 194994 => '䕫', 194995 => '虐', 194996 => '虜', 194997 => '虧', 194998 => '虩', 194999 => '蚩', 195000 => '蚈', 195001 => '蜎', 195002 => '蛢', 195003 => '蝹', 195004 => '蜨', 195005 => '蝫', 195006 => '螆', 195008 => '蟡', 195009 => '蠁', 195010 => '䗹', 195011 => '衠', 195012 => '衣', 195013 => '𧙧', 195014 => '裗', 195015 => '裞', 195016 => '䘵', 195017 => '裺', 195018 => '㒻', 195019 => '𧢮', 195020 => '𧥦', 195021 => '䚾', 195022 => '䛇', 195023 => '誠', 195024 => '諭', 195025 => '變', 195026 => '豕', 195027 => '𧲨', 195028 => '貫', 195029 => '賁', 195030 => '贛', 195031 => '起', 195032 => '𧼯', 195033 => '𠠄', 195034 => '跋', 195035 => '趼', 195036 => '跰', 195037 => '𠣞', 195038 => '軔', 195039 => '輸', 195040 => '𨗒', 195041 => '𨗭', 195042 => '邔', 195043 => '郱', 195044 => '鄑', 195045 => '𨜮', 195046 => '鄛', 195047 => '鈸', 195048 => '鋗', 195049 => '鋘', 195050 => '鉼', 195051 => '鏹', 195052 => '鐕', 195053 => '𨯺', 195054 => '開', 195055 => '䦕', 195056 => '閷', 195057 => '𨵷', 195058 => '䧦', 195059 => '雃', 195060 => '嶲', 195061 => '霣', 195062 => '𩅅', 195063 => '𩈚', 195064 => '䩮', 195065 => '䩶', 195066 => '韠', 195067 => '𩐊', 195068 => '䪲', 195069 => '𩒖', 195070 => '頋', 195071 => '頋', 195072 => '頩', 195073 => '𩖶', 195074 => '飢', 195075 => '䬳', 195076 => '餩', 195077 => '馧', 195078 => '駂', 195079 => '駾', 195080 => '䯎', 195081 => '𩬰', 195082 => '鬒', 195083 => '鱀', 195084 => '鳽', 195085 => '䳎', 195086 => '䳭', 195087 => '鵧', 195088 => '𪃎', 195089 => '䳸', 195090 => '𪄅', 195091 => '𪈎', 195092 => '𪊑', 195093 => '麻', 195094 => '䵖', 195095 => '黹', 195096 => '黾', 195097 => '鼅', 195098 => '鼏', 195099 => '鼖', 195100 => '鼻', 195101 => '𪘀', ); php-toolkit/DataLiberation/vendor-patched/rowbot/url/phpstan.neon 0000777 00000001353 15251156632 0021276 0 ustar 00 includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-deprecation-rules/rules.neon parameters: level: max treatPhpDocTypesAsCertain: false paths: - src ignoreErrors: - message: '#Call to function is_object\(\) with object will always evaluate to true\.#' path: src/URLSearchParams.php count: 1 - message: '#Cannot use array destructuring on Countable&iterable<int\|string, bool\|float\|int\|string\|Stringable>\.#' path: src/URLSearchParams.php count: 1 # Note to self: You can't escape single quotes in neon. Another possible alternative would be to replace single quotes with \x27. php-toolkit/DataLiberation/vendor-patched/rowbot/url/README.md 0000777 00000026501 15251156632 0020221 0 ustar 00 # URL-Parser [](https://github.com/TRowbotham/URL-Parser/blob/master/LICENSE) [](https://github.com/TRowbotham/URL-Parser/actions) [](https://codecov.io/gh/TRowbotham/URL-Parser) [](https://packagist.org/packages/rowbot/url) [](https://packagist.org/packages/rowbot/url) A [WHATWG URL](https://url.spec.whatwg.org/) spec compliant URL parser for working with URLs and their query strings. This API offers 2 objects that you can use to help you work with URLs; [URL](#url) and [URLSearchParams](#urlsearchparams). ## Demo Checkout an interactive demo [here](https://url-demo.trowbotham.com/). ## Requirements - PHP >= 8.1 - `ext-mbstring` - `brick/math` - `rowbot/idna` ## Installation ```bash composer require rowbot/url ``` ## URL The URL object is the primary object for working with a URL. ### The URL constructor `URL(string|\Stringable $url[, null|string|\Stringable $base = null, array $options = []])` The `$options` argument accepts an array with a key `logger` whose value is an object implementing `\Psr\Log\LoggerInterface`. See [Logging](#logging) for more information. #### URL constructor throws - `\Rowbot\URL\Exception\TypeError` - When the URL parser determines that the given input is not a valid URL. ```php use Rowbot\URL\URL; // Construct a new URL object. $url = new URL('https://example.com/'); // Construct a new URL object using a relative URL, by also providing the constructor with the base URL. $url = new URL('path/to/file.php?query=string', 'http://example.com'); echo $url->href; // Outputs: "http://example.com/path/to/file.php?query=string" // You can also pass an existing URL object to either the $url or $base arguments. $url = new URL('https://example.org:123'); $url1 = new URL('foo/bar/', $url); echo $url1->href; // Outputs: "https://example.org:123/foo/bar/" // Catch the error when URL parsing fails. try { $url = new URL('http://2001::1]'); } catch (\Rowbot\URL\Exception\TypeError $e) { echo 'Invalid URL'; } ``` ### URL Members Note: As a convience, both the `__get()` and `__set()` methods will throw an `\InvalidArgumentException` if you try to get or set an invalid property. #### `string URL::href` The `href` getter returns the serialization of the URL. The `href` setter will parse the entire string updating all the components of the URL with the new values. Providing an invalid URL will cause the setter to throw a `\Rowbot\URL\TypeError`. #### `readonly string URL::origin` The `origin` member is readonly. Its output is in the form of `scheme://host:port`. If a URL does not have a port, then that will be excluded from the output. #### `string URL::protocol` The `protocol` getter, also known as a scheme, returns the protocol of the URL, such as http, ftp, or ssh. The `protocol` setter is used to change the URLs protocol. #### `string URL::username` The `username` getter returns the username portion of the URL, or an empty string if the URL does not contain a username. The `username` setter changes the URLs username. #### `string URL::password` The `password` getter returns the password portion of the URL, or an empty string if the URL does not contain a password. The `password` setter changes the URLs password. #### `string URL::host` The `host` getter returns the combination of `hostname` and `port`. The output would look like `hostname:port`. If the URL does not have a port, then the port is not present in the output. The `host` setter allows you to change both the `hostname` and `port` at the same time. #### `string URL::hostname` The `hostname` getter returns the hostname of the URL. For example, the hostname of `https://example.com:31` would be `example.com`. The `hostname` setter will change the hostname portion of the URL. #### `string URL::port` The `port` getter returns an integer as a string representing the URLs port. If the URL does not have a port, the empty string will be returned instead. The `port` setter updates the URLs port. #### `string URL::pathname` The `pathname` getter returns the URLs path. The `pathname` setter updates the URLs path. #### `string URL::search` The `search` getter returns the URLs query string. The `search` setter updates the URLs URLSearchParams list. #### `readonly URLSearchParams URL::searchParams` Returns the URLSearchParams object associated with this URL allowing you to modify the query parameters without having to clobber the entire query string. This will always return the same object. #### `string URL::hash` The `hash` getter, also known as a URLs fragment, returns the portion of the URL that follows the "#" character. The `hash` setter updates the portion of the URL that follows the "#". #### `bool URL::canParse(string|\Stringable $url[, null|string|\Stringable $base = null])` A static method that allows the user to quickly check if a URL is parsable, without needing to construct a new URL object and wrapping it with a try/catch statement. #### `string URL::toJSON()` Returns a JSON encoded string of the URL. Note that this method escapes forward slashes, which is not the default for PHPs `json_encode()`, but matches the default behavior of JavaScripts `JSON.stringify()`. If you wish to control the serialization, then pass the URL obect to the `json_encode()` function. #### `string URL::jsonSerialize()` The URL object implements the `JsonSerializable` interface allowing you to pass the object as a whole to the json_encode() function. #### `string URL::toString()` Returns the serialization of the URL. #### `string URL::__toString()` See [URL::toString()](#string-urltostring) ## URLSearchParams The URLSearchParams object allows you to work with query strings when you don't need a full URL. The URLSearchParams object implements the `Iterator` interface so that you may iterate over the list of search parameters. The iterator will return an array containing exactly 2 items. The first item is the parameter name and the second item is the parameter value. ### The URLSearchParams constructor `URLSearchParams([iterable<int|string, array<int|string, scalar|\Stringable>|(\Traversable<int|string, scalar|\Stringable>&\Countable)>|object|string|\Stringable $init])` #### URLSearchParams constructor throws - `\Rowbot\URL\Exception\TypeError` - When an iterable is passed and one if its values is not iterable. - When an iterable is passed and one of its values is not countable, such as an object that implements `\Iterator`, but not `\Countable`. - When an iterable is passed and one of its sequences does not contain exactly 2 items, such as an array that contains only 1 string. ```php use Rowbot\URL\URLSearchParams; // Construct an empty list of search params. $params = new URLSearchParams(); // Construct a new list from a query string. Remember that a leading "?" will be stripped. $params = new URLSearchParams('?foo=bar'); // Construct a new list using an array of arrays containing strings. Alternatively, you could pass an // object that implements the Traversable interface and whose iterator returns an array of arrays, // with each array containing exactly 2 items. $params = new URLSearchParams([ ['foo', 'bar'], ['foo', 'bar'] // Duplicates are allowed! ['one', 'two'] ]); // Iterate over a URLSearchParams object. foreach ($params as $index => $param) { if ($index > 0) { echo '&'; } echo $param[0] . '=' . $param[1]; } // Above loop prints "foo=bar&foo=bar&one=two". // Construct a new list using an object $obj = new \stdClass(); $obj->foo = 'bar'; $params = new URLSearchParams($obj); // Copy an existing URLSearchParams object into a new one. $params1 = new URLSearchParams($params); ``` ### URLSearchParams Members #### `void URLSearchParams::append(string $name, string $value)` Appends a new name-value pair to the list. #### `void URLSearchParams::delete(string $name[, string $value])` Deletes all name-value pairs whose name is `$name` from the list. If the optional `$value` is provided, then only pairs with the same name and value are removed. #### `string|null URLSearchParams::get(string $name)` Returns the value of the first name-value pair whose name is `$name` in the list or null if there are no name-value pairs whose name is `$name` in the list. #### `string[] URLSearchParams::getAll(string $name)` Returns a list of values of all name-value pairs whose name is `$name`, in list order, or the empty list if there are no name-value pairs whose name is `$name` in the list. #### `bool URLSearchParams::has(string $name[, string $value])` Returns true if there is a name-value pair in the list, and false otherwise. #### `void URLSearchParams::set(string $name, string $value)` If the list contains name-value pairs whose name is `$name`, the first name-value pair in the list whose name is `$name` will have its value changed to `$value` and all others following it in the list will be removed. If the list does not contain a name-value pair whose name is `$name` then the new name-value pair will be appended to the list. #### `void URLSearchParams::sort()` Sorts the list of search params by comparing code units. The relative order of name-value pairs with the same name are preserved. #### `string URLSearchParams::toString()` Returns the serialization of the list of name-value pairs. ### `int URLSearchParams::$size` Returns the number of query parameters in the list. #### `string URLSearchParams::__toString()` See [URLSearchParams::toString()](#string-urlsearchparamstostring) ## Logging The given logger logs validation errors. Entries with a level of `warning` are fatal errors that cause the parser to fail. Entries with a level of `notice` are not fatal. All validation errors have an `input` key and either a `column` or `column_range` offset key. Column offsets start at 1. ### Logging context | Key | Type | Description | | ---------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `input` | `string` | The input string that the parser is operating on at the time of error. | | `column` | `positive-int` | The column offset of the error. | | `column_range` | `array{0: positive-int, 1: positive-int}` | Index 0 is the starting column offset, and index 1 is the end column offset. The range is inclusive. | | `idn_errors` | `list<string>` | A list of strings that represent IDN error constant names. | | `unicode_domain` | `string` | The domain name as a Unicode string. | php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/QueryListTest.php 0000777 00000000733 15251156632 0023415 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\TestCase; use Rowbot\URL\Component\QueryList; class QueryListTest extends TestCase { public function testSetDoesNothingWhenNoMatchingNameExists(): void { $input = [ [ 'name' => 'a', 'value' => 'b' ], [ 'name' => 'a', 'value' => 'c' ], ]; $list = new QueryList( $input ); $list->set( 'Foo', 'Bar' ); self::assertSame( $input, $list->getIterator()->getArrayCopy() ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/StringsTest.php 0000777 00000005377 15251156632 0023116 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rowbot\URL\String\Exception\RegexException; use Rowbot\URL\String\Exception\UndefinedIndexException; use Rowbot\URL\String\StringList; use Rowbot\URL\String\Utf8String; use ValueError; class StringsTest extends TestCase { public function testTranscodeUnknownEncoding(): void { $this->expectException( ValueError::class ); Utf8String::transcode( 'stuff', 'gallifreyan', 'utf-8' ); } public static function startsWithTwoAsciiHexDigitsProvider(): array { return [ [ 'ab', true ], [ 'a', false ], [ '99', true ], [ 'a3', true ], [ '3a', true ], [ 'a4x', true ], [ 'AB', true ], [ '3F', true ], [ 'gab', false ], [ '', false ], ]; } public function testStartsWithTwoAsciiHexDigits( string $input, bool $expected ): void { $s = new Utf8String( $input ); self::assertSame( $expected, $s->startsWithTwoAsciiHexDigits() ); } public static function startsWithWindowsDriveLetterProvider(): array { return [ [ 'c:', true ], [ 'c:/', true ], [ 'c:a', false ], [ '4:', false ], [ 'az:', false ], [ 'a|', true ], [ 'a:|', false ], [ '', false ], [ 'c:\\', true ], [ 'c:?', true ], [ 'c:#', true ], [ 'c:/f', true ], ]; } public function testStartsWithWindowsDriveLetter( string $input, bool $expected ): void { $s = new Utf8String( $input ); self::assertSame( $expected, $s->startsWithWindowsDriveLetter() ); } public function testMatchesThrowsWhenOffsetExceedsLength(): void { $this->expectException( RegexException::class ); $s = new Utf8String( '' ); $s->matches( '/[A-Z]/', $matches, 0, 1 ); } public function testMatchesThrowsOnInvalidUtf8Text(): void { $this->expectException( RegexException::class ); $s = new Utf8String( "\xC3\x7F" ); $s->matches( '/[A-Z]/u' ); } public function testReplaceRegexThrowsOnInvalidUtf8Text(): void { $this->expectException( RegexException::class ); $s = new Utf8String( "\xC3\x7F" ); $s->replaceRegex( '/[A-Z]/u', 'foo' ); } public function testSplitReturnsEmptyListWithEmptyDelimiter(): void { $s = new Utf8String( '' ); self::assertTrue( $s->split( '' )->isEmpty() ); } public function testStringListFirstThrowsWithEmptyList(): void { $this->expectException( UndefinedIndexException::class ); $list = new StringList(); $list->first(); } public function testStringListLastThrowsWithEmptyList(): void { $this->expectException( UndefinedIndexException::class ); $list = new StringList(); $list->last(); } public function testStringListKeyReturnsInteger(): void { $s = new Utf8String( 'a=b=c=d' ); foreach ( $s->split( '=' ) as $key => $string ) { self::assertIsInt( $key ); } } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/URLTest.php 0000777 00000006455 15251156632 0022125 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use InvalidArgumentException; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\URL; use TypeError as NativeTypeError; class URLTest extends TestCase { public function testCloningUrl(): void { $url1 = new URL( 'http://127.0.0.1' ); $url2 = clone $url1; $url2->href = 'https://foo:bar@foo.com/foo/bar/?foo=bar#foo'; self::assertSame( 'http:', $url1->protocol ); self::assertEmpty( $url1->username ); self::assertEmpty( $url1->password ); self::assertSame( '127.0.0.1', $url1->host ); self::assertSame( '127.0.0.1', $url1->hostname ); self::assertEmpty( $url1->port ); self::assertSame( '/', $url1->pathname ); self::assertEmpty( $url1->search ); self::assertEmpty( $url1->hash ); } /** * Test variations of percent encoded dot path segements not covered by the WHATWG tests. */ public function testPercentEncodedDotPathSegments(): void { $url = new URL( 'http://example.com/foo/bar/%2e%2E/%2E%2e' ); self::assertSame( 'http://example.com/', $url->href ); self::assertSame( '/', $url->pathname ); } public function testInvalidGetterPropertyName(): void { $this->expectException( InvalidArgumentException::class ); $url = new URL( 'http://example.com' ); $url->nonExistantProperty; } public function testInvalidSetterPropertyName(): void { $this->expectException( InvalidArgumentException::class ); $url = new URL( 'http://example.com' ); $url->nonExistantProperty = 'foo'; } public function testHrefSetterFailure(): void { $this->expectException( TypeError::class ); $url = new URL( 'http://example.com' ); $url->href = 'foo'; } public function testCastingURLObjectToString(): void { $url = new URL( 'http://example.com' ); self::assertSame( 'http://example.com/', (string) $url ); self::assertSame( 'http://example.com/', $url->toString() ); } public function testHrefSetterWithNoQueryString(): void { $url = new URL( 'http://example.com' ); $url->href = 'ssh://example.org'; self::assertSame( 'ssh://example.org', $url->href ); } public function testValidLoggerDoesNotThrow(): void { $url = 'https://example.com'; self::assertInstanceOf( URL::class, new URL( $url, null, [] ) ); self::assertInstanceOf( URL::class, new URL( $url, null, [ 'logger' => null ] ) ); self::assertInstanceOf( URL::class, new URL( $url, null, [ 'logger' => new ValidationErrorLogger() ] ) ); } public function testInvalidLoggerThrows( $value ): void { $this->expectException( TypeError::class ); new URL( 'https://example.com', null, [ 'logger' => $value ] ); } public function testURLConstructorAcceptsStringable(): void { $foo = new class { public function __toString(): string { return 'https://foo.com'; } }; $bar = new class { public function __toString(): string { return 'https://bar.com'; } }; self::assertInstanceOf( URL::class, new URL( $foo ) ); self::assertInstanceOf( URL::class, new URL( $foo, $bar ) ); } /** * @param null|object|string $url * @param null|object|string $base */ public function testURLConstructorWithNonStringableObject( $url, $base ): void { $this->expectException( NativeTypeError::class ); new URL( $url, $base ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/URLRecordTest.php 0000777 00000002372 15251156632 0023256 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Rowbot\URL\BasicURLParser; use Rowbot\URL\String\Utf8String; class URLRecordTest extends TestCase { public function testUnknownBlobUrlCreatesOpaqueOrigin(): void { $parser = new BasicURLParser(); $record = $parser->parse( new Utf8String( 'blob:failure' ) ); $origin = $record->getOrigin(); self::assertTrue( $origin->isOpaque() ); self::assertNull( $origin->getEffectiveDomain() ); } public function testFileSchemeCreatesOpaqueOrigin(): void { $parser = new BasicURLParser(); $record = $parser->parse( new Utf8String( 'file:///C:/Users/Desktop/' ) ); $origin = $record->getOrigin(); self::assertTrue( $origin->isOpaque() ); self::assertNull( $origin->getEffectiveDomain() ); } public function testEquality( string $urlA, string $urlB, bool $isEqualWithHash, bool $isEqualWithoutHash ): void { $parser = new BasicURLParser(); $recordA = $parser->parse( new Utf8String( $urlA ) ); $recordB = $parser->parse( new Utf8String( $urlB ) ); self::assertSame( $isEqualWithHash, $recordA->isEqual( $recordB, false ) ); self::assertSame( $isEqualWithoutHash, $recordA->isEqual( $recordB, true ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/PathTest.php 0000777 00000002352 15251156632 0022347 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rowbot\URL\Component\OpaquePath; use Rowbot\URL\Component\PathSegment; use Rowbot\URL\Component\Scheme; use Rowbot\URL\Exception\URLException; class PathTest extends TestCase { public static function isNormalizedWindowsDriveLetterProvider(): array { return [ [ 'c:', true ], [ 'c:/', false ], [ 'c:a', false ], [ '4:', false ], [ 'az:', false ], [ 'a|', false ], [ 'a:|', false ], [ '', false ], [ 'c:\\', false ], [ 'c:?', false ], [ 'c:#', false ], [ 'c:/f', false ], ]; } public function testIsNormalizedWindowsDriveLetter( string $input, bool $expected ): void { $s = new PathSegment( $input ); self::assertSame( $expected, $s->isNormalizedWindowsDriveLetter() ); } public function testOpaquePathThrowsOnShorten(): void { $path = new OpaquePath( new PathSegment() ); $this->expectException( URLException::class ); $path->shorten( new Scheme( 'file' ) ); } public function testOpaquePathThrowsOnPush(): void { $path = new OpaquePath( new PathSegment() ); $this->expectException( URLException::class ); $path->push( new PathSegment() ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/QueryStateTest.php 0000777 00000002542 15251156632 0023562 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\TestCase; use Rowbot\URL\BasicURLParser; use Rowbot\URL\String\Utf8String; use Rowbot\URL\URLRecord; use function html_entity_decode; use const ENT_HTML5; class QueryStateTest extends TestCase { /** * @see https://url.spec.whatwg.org/#query-encoding-example */ public function testParsingNonUtf8EncodedQueryString(): void { $string = 'http://example.com/?' . html_entity_decode( 'smörgåsbord', ENT_HTML5, 'utf-8' ); $input = new Utf8String( $string ); $parser = new BasicURLParser(); $record = $parser->parse( $input, null, 'windows-1252' ); self::assertInstanceOf( URLRecord::class, $record ); self::assertSame( 'sm%F6rg%E5sbord', $record->query ); $record = $parser->parse( $input, null ); self::assertInstanceOf( URLRecord::class, $record ); self::assertSame( 'sm%C3%B6rg%C3%A5sbord', $record->query ); } public function testParsingWebsocketForcesUtf8EncodingInQueryString(): void { $string = 'wss://example.com/?' . html_entity_decode( 'smörgåsbord', ENT_HTML5, 'utf-8' ); $input = new Utf8String( $string ); $parser = new BasicURLParser(); $record = $parser->parse( $input, null, 'windows-1252' ); self::assertInstanceOf( URLRecord::class, $record ); self::assertSame( 'sm%C3%B6rg%C3%A5sbord', $record->query ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/OriginTest.php 0000777 00000012651 15251156632 0022705 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rowbot\URL\BasicURLParser; use Rowbot\URL\Component\Host\HostParser; use Rowbot\URL\Component\OpaqueOrigin; use Rowbot\URL\Component\TupleOrigin; use Rowbot\URL\Origin; use Rowbot\URL\ParserContext; use Rowbot\URL\String\StringBuffer; use Rowbot\URL\String\Utf8String; use Rowbot\URL\String\Utf8StringIterator; use Rowbot\URL\URLRecord; class OriginTest extends TestCase { public static function originProvider(): array { $hostParser = new HostParser(); $context = new ParserContext( new Utf8String( '' ), new Utf8StringIterator( '' ), new StringBuffer( '' ), new URLRecord(), null, null, null, null ); $tuple = new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), null, null ); $tupleDomain = new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), null, 'example.org' ); $opaque = new OpaqueOrigin(); $urlParser = new BasicURLParser(); return [ [ $tuple, new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), null, null ), 'isSameOrigin' => true, 'isSameOriginDomain' => true, ], [ new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), 314, null ), new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), 420, null ), 'isSameOrigin' => false, 'isSameOriginDomain' => false, ], [ new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), 314, 'example.org' ), new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), 420, 'example.org' ), 'isSameOrigin' => false, 'isSameOriginDomain' => true, ], [ new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), null, null ), new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), null, 'example.org' ), 'isSameOrigin' => true, 'isSameOriginDomain' => false, ], [ new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), null, 'example.org' ), new TupleOrigin( 'http', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), null, 'example.org' ), 'isSameOrigin' => false, 'isSameOriginDomain' => false, ], [ new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( '127.0.0.1' ), false ), null, null ), new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( '1.1.1.1' ), false ), null, null ), 'isSameOrigin' => false, 'isSameOriginDomain' => false, ], [ new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( '[::1]' ), false ), null, null ), new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( '[1::1]' ), false ), null, null ), 'isSameOrigin' => false, 'isSameOriginDomain' => false, ], [ $urlParser->parse( new Utf8String( 'blob:https://foo.com' ) )->getOrigin(), $urlParser->parse( new Utf8String( 'https://foo.com' ) )->getOrigin(), 'isSameOrigin' => true, 'isSameOriginDomain' => true, ], [ $tuple, $tuple, 'isSameOrigin' => true, 'isSameOriginDomain' => true, ], [ $tuple, $tupleDomain, 'isSameOrigin' => true, 'isSameOriginDomain' => false, ], [ $opaque, new OpaqueOrigin(), 'isSameOrigin' => false, 'isSameOriginDomain' => false, ], [ $opaque, $opaque, 'isSameOrigin' => true, 'isSameOriginDomain' => true, ], [ $tuple, $opaque, 'isSameOrigin' => false, 'isSameOriginDomain' => false, ], ]; } public function testSameOriginConcept( Origin $originA, Origin $originB, bool $isSameOrigin, bool $isSameOriginDomain ): void { self::assertSame( $isSameOrigin, $originA->isSameOrigin( $originB ) ); self::assertSame( $isSameOriginDomain, $originA->isSameOriginDomain( $originB ) ); } public function testEffectiveDomainConcept(): void { $origin = new OpaqueOrigin(); self::assertTrue( $origin->isOpaque() ); self::assertNull( $origin->getEffectiveDomain() ); $parser = new BasicURLParser(); $record = $parser->parse( new Utf8String( 'blob:https://foo.com' ) ); $origin = $record->getOrigin(); self::assertFalse( $origin->isOpaque() ); self::assertNotNull( $origin->getEffectiveDomain() ); self::assertSame( 'foo.com', $origin->getEffectiveDomain() ); $hostParser = new HostParser(); $context = new ParserContext( new Utf8String( '' ), new Utf8StringIterator( '' ), new StringBuffer( '' ), new URLRecord(), null, null, null, null ); $origin = new TupleOrigin( 'https', $hostParser->parse( $context, new Utf8String( 'example.org' ), false ), 314, 'example.org' ); self::assertFalse( $origin->isOpaque() ); self::assertNotNull( $origin->getEffectiveDomain() ); self::assertSame( 'example.org', $origin->getEffectiveDomain() ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/SchemeTest.php 0000777 00000001774 15251156632 0022666 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use ReflectionClass; use Rowbot\URL\Component\Scheme; class SchemeTest extends TestCase { public static function specialSchemeNonNullDefaultPortProvider(): iterable { $reflection = new ReflectionClass( Scheme::class ); $schemes = $reflection->getConstant( 'SPECIAL_SCHEMES' ); foreach ( $schemes as $scheme => $port ) { if ( $port === null ) { continue; } yield [ $scheme, $port ]; } } public function testIsDefaultPortReturnsTrueForNonNullPortSpecialSchemes( string $scheme, int $port ): void { $scheme = new Scheme( $scheme ); self::assertTrue( $scheme->isDefaultPort( $port ) ); } public function testIsDefaultPortReturnsFalseForNonSpecialSchemesAndNullPorts( string $scheme, ?int $port ): void { $scheme = new Scheme( $scheme ); self::assertFalse( $scheme->isDefaultPort( $port ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/URLSearchParamsTest.php 0000777 00000005523 15251156632 0024412 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use ArrayObject; use Countable; use Generator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\URL; use Rowbot\URL\URLSearchParams; class URLSearchParamsTest extends TestCase { public function testCloningStandaloneURLSearchParams(): void { $query = new URLSearchParams( 'foo=bar' ); $query2 = clone $query; $query2->append( 'foo', 'bar' ); self::assertSame( 'foo=bar', $query->toString() ); } public function testCloningAttachedURLSearchParams(): void { $url = new URL( 'http://example.com/?foo=bar' ); $query = clone $url->searchParams; $query->append( 'foo', 'bar' ); self::assertSame( '?foo=bar', $url->search ); self::assertSame( 'foo=bar', $url->searchParams->toString() ); } public function testIterationKey(): void { $query = new URLSearchParams( 'foo=bar&qux=baz' ); $result = [ [ 'foo', 'bar' ], [ 'qux', 'baz' ], ]; foreach ( $query as $index => $pair ) { self::assertSame( $result[ $index ], $pair ); } } public static function getInvalidIteratorInput(): array { $generator = static function (): Generator { yield 'foo'; yield 'bar'; }; $anonClass = new class ( $generator() ) implements Countable { /** * @var Generator */ public $foo; public function __construct( Generator $foo ) { $this->foo = $foo; } public function count(): int { return 2; } }; return [ 'sequences not equal to 2' => [ [ [ 'foo', 'bar' ], [ 'baz' ] ] ], 'non-iterable' => [ new ArrayObject( [ 'x', 'y' ] ) ], 'generator' => [ [ $generator() ] ], 'invalid-name' => [ [ [ null, 'foo' ] ] ], 'invalid-value' => [ [ [ 'foo', null ] ] ], 'countable-only' => [ [ [ $anonClass ] ] ], 'invalid-property-value' => [ $anonClass ], 'iterable-non-countable' => [ new ArrayObject( [ $generator(), $generator() ] ) ], ]; } /** * @param mixed[]|object $input */ public function testInvalidIteratorInput( $input ): void { $this->expectException( TypeError::class ); new URLSearchParams( $input ); } public static function unhandledInputProvider(): array { return [ [ static function (): void { return; }, ], ]; } public function testUnhandledInputDoesNothing( $input ): void { $params = new URLSearchParams( $input ); self::assertFalse( $params->valid() ); } public function testInvalidIteratorReturnsNull(): void { $params = new URLSearchParams(); self::assertNull( $params->current() ); self::assertFalse( $params->valid() ); } public function testSortingPairWithEmptyName(): void { $params = new URLSearchParams( '=foo&x=bar&c=bar' ); $params->sort(); self::assertSame( '=foo&c=bar&x=bar', $params->toString() ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLEncodedParserTest.php 0000777 00000005654 15251156632 0025765 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rowbot\URL\URLSearchParams; use function in_array; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlencoded-parser.any.js */ class URLEncodedParserTest extends TestCase { public static function getTestData(): array { return [ [ 'input' => 'test', 'output' => [ [ 'test', '' ] ] ], [ 'input' => "\u{FEFF}test=\u{FEFF}", 'output' => [ [ "\u{FEFF}test", "\u{FEFF}" ] ] ], [ 'input' => '%EF%BB%BFtest=%EF%BB%BF', 'output' => [ [ "\u{FEFF}test", "\u{FEFF}" ] ] ], [ 'input' => '%EF%BF%BF=%EF%BF%BF', 'output' => [ [ "\u{FFFF}", "\u{FFFF}" ] ] ], [ 'input' => '%FE%FF', 'output' => [ [ "\u{FFFD}\u{FFFD}", '' ] ] ], [ 'input' => '†&†=x', 'output' => [ [ '†', '' ], [ '†', 'x' ] ] ], [ 'input' => '%C2', 'output' => [ [ "\u{FFFD}", '' ] ] ], [ 'input' => '%C2x', 'output' => [ [ "\u{FFFD}x", '' ] ] ], [ 'input' => '_charset_=windows-1252&test=%C2x', 'output' => [ [ '_charset_', 'windows-1252' ], [ 'test', "\u{FFFD}x" ] ], ], [ 'input' => '', 'output' => [] ], [ 'input' => 'a', 'output' => [ [ 'a', '' ] ] ], [ 'input' => 'a=b', 'output' => [ [ 'a', 'b' ] ] ], [ 'input' => 'a=', 'output' => [ [ 'a', '' ] ] ], [ 'input' => '=b', 'output' => [ [ '', 'b' ] ] ], [ 'input' => '&', 'output' => [] ], [ 'input' => '&a', 'output' => [ [ 'a', '' ] ] ], [ 'input' => 'a&', 'output' => [ [ 'a', '' ] ] ], [ 'input' => 'a&a', 'output' => [ [ 'a', '' ], [ 'a', '' ] ] ], [ 'input' => 'a&b&c', 'output' => [ [ 'a', '' ], [ 'b', '' ], [ 'c', '' ] ] ], [ 'input' => 'a=b&c=d', 'output' => [ [ 'a', 'b' ], [ 'c', 'd' ] ] ], [ 'input' => 'a=b&c=d&', 'output' => [ [ 'a', 'b' ], [ 'c', 'd' ] ] ], [ 'input' => '&&&a=b&&&&c=d&', 'output' => [ [ 'a', 'b' ], [ 'c', 'd' ] ] ], [ 'input' => 'a=a&a=b&a=c', 'output' => [ [ 'a', 'a' ], [ 'a', 'b' ], [ 'a', 'c' ] ] ], [ 'input' => 'a==a', 'output' => [ [ 'a', '=a' ] ] ], [ 'input' => 'a=a+b+c+d', 'output' => [ [ 'a', 'a b c d' ] ] ], [ 'input' => '%=a', 'output' => [ [ '%', 'a' ] ] ], [ 'input' => '%a=a', 'output' => [ [ '%a', 'a' ] ] ], [ 'input' => '%a_=a', 'output' => [ [ '%a_', 'a' ] ] ], [ 'input' => '%61=a', 'output' => [ [ 'a', 'a' ] ] ], [ 'input' => '%61+%4d%4D=', 'output' => [ [ 'a MM', '' ] ] ], [ 'input' => 'id=0&value=%', 'output' => [ [ 'id', '0' ], [ 'value', '%' ] ] ], [ 'input' => 'b=%2sf%2a', 'output' => [ [ 'b', '%2sf*' ] ] ], [ 'input' => 'b=%2%2af%2a', 'output' => [ [ 'b', '%2*f*' ] ] ], [ 'input' => 'b=%%2a', 'output' => [ [ 'b', '%*' ] ] ], ]; } public function test( string $input, array $output ): void { $sp = new URLSearchParams( $input ); $i = 0; if ( in_array( $input, [ '', '&' ], true ) ) { self::assertFalse( $sp->valid() ); return; } foreach ( $sp as $item ) { self::assertSame( $output[ $i ++ ], $item ); } } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsForeachTest.php 0000777 00000004633 15251156632 0027104 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URL; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-foreach.any.js */ class URLSearchParamsForeachTest extends TestCase { public function test1(): void { $params = new URLSearchParams( 'a=1&b=2&c=3' ); $keys = []; $values = []; foreach ( $params as $param ) { $keys[] = $param[0]; $values[] = $param[1]; } self::assertSame( [ 'a', 'b', 'c' ], $keys ); self::assertSame( [ '1', '2', '3' ], $values ); } public function test2(): void { $a = new URL( 'http://a.b/c?a=1&b=2&c=3&d=4' ); $b = $a->searchParams; $c = []; foreach ( $b as $i ) { $a->search = 'x=1&y=2&z=3'; $c[] = $i; } self::assertSame( [ 'a', '1' ], $c[0] ); self::assertSame( [ 'y', '2' ], $c[1] ); self::assertSame( [ 'z', '3' ], $c[2] ); } public function test3(): void { $a = new URL( 'http://a.b/c' ); $b = $a->searchParams; self::assertFalse( $b->valid() ); } public function testDeleteNextParamDuringIteration(): void { $url = new URL( 'http://localhost/query?param0=0¶m1=1¶m2=2' ); $searchParams = $url->searchParams; $seen = []; foreach ( $searchParams as $param ) { if ( $param[0] === 'param0' ) { $searchParams->delete( 'param1' ); } $seen[] = $param; } self::assertSame( [ 'param0', '0' ], $seen[0] ); self::assertSame( [ 'param2', '2' ], $seen[1] ); } public function testDeleteCurrentParamDuringIteration(): void { $url = new URL( 'http://localhost/query?param0=0¶m1=1¶m2=2' ); $searchParams = $url->searchParams; $seen = []; foreach ( $searchParams as $param ) { if ( $param[0] === 'param0' ) { $searchParams->delete( 'param1' ); // 'param1=1' is now in the first slot, so the next iteration will see 'param2=2'. } else { $seen[] = $param; } } self::assertSame( [ 'param2', '2' ], $seen[0] ); } public function testDeleteEveryParamSeenDuringIteration(): void { $url = new URL( 'http://localhost/query?param0=0¶m1=1¶m2=2' ); $searchParams = $url->searchParams; $seen = []; foreach ( $searchParams as $param ) { $seen[] = $param[0]; $searchParams->delete( $param[0] ); } self::assertSame( [ 'param0', 'param2' ], $seen ); self::assertSame( 'param1=1', (string) $searchParams ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSettersTest.php 0000777 00000001541 15251156632 0024667 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\Attributes\DataProvider; use Rowbot\URL\URL; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/url-setters.html */ class URLSettersTest extends WhatwgTestCase { public static function urlSetterGetterDataProvider(): iterable { foreach ( self::loadTestData( 'setters_tests.json' ) as $key => $tests ) { if ( $key === 'comment' ) { continue; } foreach ( $tests as $inputs ) { unset( $inputs['comment'] ); $inputs['setter'] = $key; yield [ $inputs ]; } } } public function testSetters( array $input ): void { $url = new URL( $input['href'] ); $url->{$input['setter']} = $input['new_value']; foreach ( $input['expected'] as $attribute => $value ) { self::assertSame( $value, $url->$attribute, $attribute ); } } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsAppendTest.php 0000777 00000002511 15251156632 0026735 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-append.any.js */ class URLSearchParamsAppendTest extends TestCase { public function testAppendSameName(): void { $params = new URLSearchParams(); $params->append( 'a', 'b' ); self::assertSame( 'a=b', $params . '' ); $params->append( 'a', 'b' ); self::assertSame( 'a=b&a=b', $params . '' ); $params->append( 'a', 'c' ); self::assertSame( 'a=b&a=b&a=c', $params . '' ); } public function testAppendEmptyString(): void { $params = new URLSearchParams(); $params->append( '', '' ); self::assertSame( '=', $params . '' ); $params->append( '', '' ); self::assertSame( '=&=', $params . '' ); $params->append( 'a', 'c' ); } public function testAppendMultiple(): void { $params = new URLSearchParams(); $params->append( 'first', 1 ); $params->append( 'second', 2 ); $params->append( 'third', '' ); $params->append( 'first', 10 ); self::assertTrue( $params->has( 'first' ) ); self::assertSame( '1', $params->get( 'first' ) ); self::assertSame( '2', $params->get( 'second' ) ); self::assertSame( '', $params->get( 'third' ) ); $params->append( 'first', 10 ); self::assertSame( '1', $params->get( 'first' ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsDeleteTest.php 0000777 00000005272 15251156632 0026737 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URL; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-delete.any.js */ class URLSearchParamsDeleteTest extends TestCase { public function testDeleteBasics(): void { $params = new URLSearchParams( 'a=b&c=d' ); $params->delete( 'a' ); self::assertSame( 'c=d', $params . '' ); $params = new URLSearchParams( 'a=a&b=b&a=a&c=c' ); $params->delete( 'a' ); self::assertSame( 'b=b&c=c', $params . '' ); $params = new URLSearchParams( 'a=a&=&b=b&c=c' ); $params->delete( '' ); self::assertSame( 'a=a&b=b&c=c', $params . '' ); } public function testDeleteAppendMultiple(): void { $params = new URLSearchParams(); $params->append( 'first', 1 ); self::assertTrue( $params->has( 'first' ) ); self::assertSame( '1', $params->get( 'first' ) ); $params->delete( 'first' ); self::assertFalse( $params->has( 'first' ) ); $params->append( 'first', 1 ); $params->append( 'first', 10 ); $params->delete( 'first' ); self::assertFalse( $params->has( 'first' ) ); } public function testDeleteAllRemovesQuestionMark(): void { $url = new URL( 'http://example.com/?param1¶m2' ); $url->searchParams->delete( 'param1' ); $url->searchParams->delete( 'param2' ); self::assertSame( 'http://example.com/', $url->href ); self::assertSame( '', $url->search ); } public function testDeleteNonExistentParamRemovesQuestionMark(): void { $url = new URL( 'http://example.com/?' ); $url->searchParams->delete( 'param1' ); self::assertSame( 'http://example.com/', $url->href ); self::assertSame( '', $url->search ); } public function testChangingTheQueryOfAUrlWithAnOpaquePathCanImpactThePath(): void { $url = new URL( 'data:space ?test' ); self::assertTrue( $url->searchParams->has( 'test' ) ); $url->searchParams->delete( 'test' ); self::assertFalse( $url->searchParams->has( 'test' ) ); self::assertSame( '', $url->search ); self::assertSame( 'space', $url->pathname ); self::assertSame( 'data:space', $url->href ); } public function testChangingTheQueryOfAUrlWithAnOpaquePathCanImpactThePathIfTheUrlHasNoFragment(): void { $url = new URL( 'data:space ?test#test' ); $url->searchParams->delete( 'test' ); self::assertSame( '', $url->search ); self::assertSame( 'space ', $url->pathname ); self::assertSame( 'data:space #test', $url->href ); } public function testTwoArgumentDelete(): void { $params = new URLSearchParams(); $params->append( 'a', 'b' ); $params->append( 'a', 'c' ); $params->append( 'a', 'd' ); $params->delete( 'a', 'c' ); self::assertSame( 'a=b&a=d', $params->toString() ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsTest.php 0000777 00000005752 15251156632 0025617 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\URL; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/url-searchparams.any.js */ class URLSearchParamsTest extends WhatwgTestCase { public function testURLSearchParamsGetter(): void { $url = new URL( 'http://example.org/?a=b' ); self::assertNotNull( $url->searchParams ); $searchParams = $url->searchParams; self::assertSame( $searchParams, $url->searchParams ); } /** * Test URL.searchParams updating, clearing. */ public function testURLSearchParamsUpdatingClearing(): void { $url = new URL( 'http://example.org/?a=b', 'about:blank' ); self::assertNotNull( $url->searchParams ); $searchParams = $url->searchParams; self::assertSame( 'a=b', $searchParams->toString() ); $searchParams->set( 'a', 'b' ); self::assertSame( 'a=b', $url->searchParams->toString() ); self::assertSame( '?a=b', $url->search ); $url->search = ''; self::assertSame( '', $url->searchParams->toString() ); self::assertSame( '', $url->search ); self::assertSame( '', $searchParams->toString() ); } public function testURLSearchParamsSetterInvalidValues(): void { $this->expectException( TypeError::class ); $urlString = 'http://example.org'; $url = new URL( $urlString, 'about:blank' ); $url->searchParams = new URLSearchParams( $urlString ); } public function testURLSearchParamsAndURLSearchSettersUpdatePropagation(): void { $url = new URL( 'http://example.org/file?a=b&c=d' ); self::assertInstanceOf( URLSearchParams::class, $url->searchParams ); $searchParams = $url->searchParams; self::assertSame( '?a=b&c=d', $url->search ); self::assertSame( 'a=b&c=d', $searchParams->toString() ); // Test that setting 'search' propagates to the URL object's query // object $url->search = 'e=f&g=h'; self::assertSame( '?e=f&g=h', $url->search ); self::assertSame( 'e=f&g=h', $url->searchParams->toString() ); // ...and same, but with a leading '?' $url->search = '?e=f&g=h'; self::assertSame( '?e=f&g=h', $url->search ); self::assertSame( 'e=f&g=h', $url->searchParams->toString() ); // And in the other direction, altering searchParams propagates back // to 'search' $searchParams->append( 'i', ' j ' ); self::assertSame( '?e=f&g=h&i=+j+', $url->search ); self::assertSame( 'e=f&g=h&i=+j+', $url->searchParams->toString() ); self::assertSame( ' j ', $searchParams->get( 'i' ) ); $searchParams->set( 'e', 'updated' ); self::assertSame( '?e=updated&g=h&i=+j+', $url->search ); self::assertSame( 'e=updated&g=h&i=+j+', $url->searchParams->__toString() ); $url2 = new URL( 'http://example.org/file??a=b&c=d', 'about:blank' ); self::assertSame( '??a=b&c=d', $url2->search ); self::assertSame( '%3Fa=b&c=d', $url2->searchParams->toString() ); $url2->href = 'http://example.org/file??a=b'; self::assertSame( '??a=b', $url2->search ); self::assertSame( '%3Fa=b', $url2->searchParams->toString() ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsHasTest.php 0000777 00000003077 15251156632 0026251 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-has.any.js */ class URLSearchParamsHasTest extends TestCase { public function testHasBasics(): void { $params = new URLSearchParams( 'a=b&c=d' ); self::assertTrue( $params->has( 'a' ) ); self::assertTrue( $params->has( 'c' ) ); self::assertFalse( $params->has( 'e' ) ); $params = new URLSearchParams( 'a=b&c=d&a=e' ); self::assertTrue( $params->has( 'a' ) ); $params = new URLSearchParams( '=b&c=d' ); self::assertTrue( $params->has( '' ) ); } public function testHasFollowingDelete(): void { $params = new URLSearchParams( 'a=b&c=d&&' ); $params->append( 'first', 1 ); $params->append( 'first', 2 ); self::assertTrue( $params->has( 'a' ) ); self::assertTrue( $params->has( 'c' ) ); self::assertTrue( $params->has( 'first' ) ); self::assertFalse( $params->has( 'd' ) ); $params->delete( 'first' ); self::assertFalse( $params->has( 'first' ) ); } public function testTwoArgumentHas(): void { $params = new URLSearchParams( 'a=b&a=d&c&e&' ); self::assertTrue( $params->has( 'a', 'b' ) ); self::assertFalse( $params->has( 'a', 'c' ) ); self::assertTrue( $params->has( 'a', 'd' ) ); self::assertTrue( $params->has( 'e', '' ) ); $params->append( 'first', 'null' ); self::assertFalse( $params->has( 'first', '' ) ); self::assertTrue( $params->has( 'first', 'null' ) ); $params->delete( 'a', 'b' ); self::assertTrue( $params->has( 'a', 'd' ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsGetTest.php 0000777 00000002450 15251156632 0026247 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-get.any.js */ class URLSearchParamsGetTest extends TestCase { public function testGetBasics(): void { $params = new URLSearchParams( 'a=b&c=d' ); self::assertSame( 'b', $params->get( 'a' ) ); self::assertSame( 'd', $params->get( 'c' ) ); self::assertNull( $params->get( 'e' ) ); $params = new URLSearchParams( 'a=b&c=d&a=e' ); self::assertSame( 'b', $params->get( 'a' ) ); $params = new URLSearchParams( '=b&c=d' ); self::assertSame( 'b', $params->get( '' ) ); $params = new URLSearchParams( 'a=&c=d&a=e' ); self::assertSame( '', $params->get( 'a' ) ); } public function testMoreGetBasics(): void { $params = new URLSearchParams( 'first=second&third&&' ); self::assertNotNull( $params ); self::assertTrue( $params->has( 'first' ), 'constructor returned non-null value.' ); self::assertSame( 'second', $params->get( 'first' ), 'Search params object has name "first"' ); self::assertSame( '', $params->get( 'third' ), 'Search params object has name "third" with the empty value.' ); self::assertNull( $params->get( 'fourth' ), 'Search params object has no "fourth" name and value.' ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsStringifierTest.php0000777 00000011663 15251156632 0030023 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URL; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-stringifier.any.js */ class URLSearchParamsStringifierTest extends TestCase { public function testSerializeSpace(): void { $params = new URLSearchParams(); $params->append( 'a', 'b c' ); self::assertSame( 'a=b+c', $params . '' ); $params->delete( 'a' ); $params->append( 'a b', 'c' ); self::assertSame( 'a+b=c', $params . '' ); } public function testSerializeEmptyValue(): void { $params = new URLSearchParams(); $params->append( 'a', '' ); self::assertSame( 'a=', $params . '' ); $params->append( 'a', '' ); self::assertSame( 'a=&a=', $params . '' ); $params->append( '', 'b' ); self::assertSame( 'a=&a=&=b', $params . '' ); $params->append( '', '' ); self::assertSame( 'a=&a=&=b&=', $params . '' ); $params->append( '', '' ); self::assertSame( 'a=&a=&=b&=&=', $params . '' ); } public function testSerializeEmptyName(): void { $params = new URLSearchParams(); $params->append( '', 'b' ); self::assertSame( '=b', $params . '' ); $params->append( '', 'b' ); self::assertSame( '=b&=b', $params . '' ); } public function testSerialzieEmptyNameAndValue(): void { $params = new URLSearchParams(); $params->append( '', '' ); self::assertSame( '=', $params . '' ); $params->append( '', '' ); self::assertSame( '=&=', $params . '' ); } public function testSerialziePlusSign(): void { $params = new URLSearchParams(); $params->append( 'a', 'b+c' ); self::assertSame( 'a=b%2Bc', $params . '' ); $params->delete( 'a' ); $params->append( 'a+b', 'c' ); self::assertSame( 'a%2Bb=c', $params . '' ); } public function testSerializeEqualSign(): void { $params = new URLSearchParams(); $params->append( '=', 'a' ); self::assertSame( '%3D=a', $params . '' ); $params->append( 'b', '=' ); self::assertSame( '%3D=a&b=%3D', $params . '' ); } public function testSerializeAmpersand(): void { $params = new URLSearchParams(); $params->append( '&', 'a' ); self::assertSame( '%26=a', $params . '' ); $params->append( 'b', '&' ); self::assertSame( '%26=a&b=%26', $params . '' ); } public function testSerializeSpecialChars(): void { $params = new URLSearchParams(); $params->append( 'a', '*-._' ); self::assertSame( 'a=*-._', $params . '' ); $params->delete( 'a' ); $params->append( '*-._', 'c' ); self::assertSame( '*-._=c', $params . '' ); } public function testSerializePercentSign(): void { $params = new URLSearchParams(); $params->append( 'a', 'b%c' ); self::assertSame( 'a=b%25c', $params . '' ); $params->delete( 'a' ); $params->append( 'a%b', 'c' ); self::assertSame( 'a%25b=c', $params . '' ); $params = new URLSearchParams( 'id=0&value=%' ); self::assertSame( 'id=0&value=%25', $params . '' ); } public function testSerializeNullByte(): void { $params = new URLSearchParams(); $params->append( 'a', "b\0c" ); self::assertSame( 'a=b%00c', $params . '' ); $params->delete( 'a' ); $params->append( "a\0b", 'c' ); self::assertSame( 'a%00b=c', $params . '' ); } public function testSerializeUnicodePileOfPoo(): void { $params = new URLSearchParams(); $params->append( 'a', "b\u{1F4A9}c" ); self::assertSame( 'a=b%F0%9F%92%A9c', $params . '' ); $params->delete( 'a' ); $params->append( "a\u{1F4A9}b", 'c' ); self::assertSame( 'a%F0%9F%92%A9b=c', $params . '' ); } public function testStringification(): void { $params = new URLSearchParams( 'a=b&c=d&&e&&' ); self::assertSame( 'a=b&c=d&e=', $params->toString() ); $params = new URLSearchParams( 'a = b &a=b&c=d%20' ); self::assertSame( 'a+=+b+&a=b&c=d+', $params->toString() ); // The lone '=' _does_ survive the roundtrip. $params = new URLSearchParams( 'a=&a=b' ); self::assertSame( 'a=&a=b', $params->toString() ); $params = new URLSearchParams( 'b=%2sf%2a' ); self::assertSame( 'b=%252sf*', $params->toString() ); $params = new URLSearchParams( 'b=%2%2af%2a' ); self::assertSame( 'b=%252*f*', $params->toString() ); $params = new URLSearchParams( 'b=%%2a' ); self::assertSame( 'b=%25*', $params->toString() ); } public function testURLSearchParamsConnectedToURL(): void { $url = new URL( 'http://www.example.com/?a=b,c' ); $params = $url->searchParams; self::assertSame( 'http://www.example.com/?a=b,c', $url->toString() ); self::assertSame( 'a=b%2Cc', $params->toString() ); $params->append( 'x', 'y' ); self::assertSame( 'http://www.example.com/?a=b%2Cc&x=y', $url->toString() ); self::assertSame( 'a=b%2Cc&x=y', $params->toString() ); } public function testURLSearchParamsMustNotDoNewlineNormalization(): void { $url = new URL( 'http://www.example.com/' ); $params = $url->searchParams; $params->append( "a\nb", "c\rd" ); $params->append( "e\n\rf", "g\r\nh" ); self::assertSame( 'a%0Ab=c%0Dd&e%0A%0Df=g%0D%0Ah', $params->toString() ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/WhatwgTestCase.php 0000777 00000003676 15251156632 0024723 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use GuzzleHttp\Client; use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use function array_filter; use function json_decode; use function json_encode; use function preg_replace; abstract class WhatwgTestCase extends TestCase { private const WHATWG_BASE_URI = 'https://raw.githubusercontent.com/web-platform-tests/wpt/master/url/resources/'; private const CACHE_TTL = 86400 * 7; // 7 DAYS private const JSON_DEPTH = 512; /** * @var Client|null */ private static $client = null; protected static function loadTestData( string $url ): array { $cache = new FilesystemAdapter( 'whatwg-test-suite', self::CACHE_TTL, __DIR__ . '/data' ); $data = $cache->get( $url, static function () use ( $url ): string { self::$client = self::$client ?? new Client( [ 'base_uri' => self::WHATWG_BASE_URI, 'http_errors' => true, ] ); $response = self::$client->get( $url ); // Replace all unpaired surrogate escape sequences with a \uFFFD escape sequence to avoid // json_decode() having a stroke and emitting a JSON_ERROR_UTF16 error causing the decode // to fail $body = preg_replace( '/ (?(DEFINE) (?<high>\\\u[Dd][89AaBb][[:xdigit:]][[:xdigit:]]) (?<low>\\\u[Dd][C-Fc-f][[:xdigit:]][[:xdigit:]]) ) # Match a low surrogate not preceded by a high surrogate (?<!(?&high))(?&low) # Match a high surrogate not followed by a low surrogate |(?&high)(?!(?&low)) /x', '\\uFFFD', (string) $response->getBody() ); // Remove comments and check to make sure it is valid JSON. $json = array_filter( json_decode( $body, true, self::JSON_DEPTH, 0 ), 'is_array' ); return json_encode( $json, 0 ); } ); return json_decode( $data, true, self::JSON_DEPTH, 0 ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsSortTest.php 0000777 00000004326 15251156632 0026463 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rowbot\URL\URL; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-sort.any.js */ class URLSearchParamsSortTest extends TestCase { public static function getTestData(): array { return [ [ 'input' => 'z=b&a=b&z=a&a=a', 'output' => [ [ 'a', 'b' ], [ 'a', 'a' ], [ 'z', 'b' ], [ 'z', 'a' ], ], ], [ 'input' => "\u{FFFD}=x&\u{FFFC}&\u{FFFD}=a", 'output' => [ [ "\u{FFFC}", '' ], [ "\u{FFFD}", 'x' ], [ "\u{FFFD}", 'a' ], ], ], [ 'input' => 'ffi&🌈', 'output' => [ [ '🌈', '' ], [ 'ffi', '' ] ], ], [ 'input' => "é&e\u{FFFD}&e\u{0301}", 'output' => [ [ "e\u{0301}", '' ], [ "e\u{FFFD}", '' ], [ 'é', '' ], ], ], [ 'input' => 'z=z&a=a&z=y&a=b&z=x&a=c&z=w&a=d&z=v&a=e&z=u&a=f&z=t&a=g', 'output' => [ [ 'a', 'a' ], [ 'a', 'b' ], [ 'a', 'c' ], [ 'a', 'd' ], [ 'a', 'e' ], [ 'a', 'f' ], [ 'a', 'g' ], [ 'z', 'z' ], [ 'z', 'y' ], [ 'z', 'x' ], [ 'z', 'w' ], [ 'z', 'v' ], [ 'z', 'u' ], [ 'z', 't' ], ], ], [ 'input' => 'bbb&bb&aaa&aa=x&aa=y', 'output' => [ [ 'aa', 'x' ], [ 'aa', 'y' ], [ 'aaa', '' ], [ 'bb', '' ], [ 'bbb', '' ], ], ], [ 'input' => 'z=z&=f&=t&=x', 'output' => [ [ '', 'f' ], [ '', 't' ], [ '', 'x' ], [ 'z', 'z' ] ], ], [ 'input' => 'a🌈&a💩', 'output' => [ [ 'a🌈', '' ], [ 'a💩', '' ] ], ], ]; } public function testSort( string $input, array $output ): void { $url = new URL( '?' . $input, 'https://example/' ); $url->searchParams->sort(); $params = new URLSearchParams( $url->search ); $i = 0; foreach ( $params as $param ) { self::assertSame( $output[ $i ++ ], $param ); } } public function testSortingNonExistentParamsRemovesQuestionMark(): void { $url = new URL( 'http://example.com/?' ); $url->searchParams->sort(); self::assertSame( 'http://example.com/', $url->href ); self::assertSame( '', $url->search ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLOriginTest.php 0000777 00000001262 15251156632 0024465 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\Attributes\DataProvider; use Rowbot\URL\URL; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/url-origin.html */ class URLOriginTest extends WhatwgTestCase { public static function urlTestDataOriginProvider(): iterable { foreach ( self::loadTestData( 'urltestdata.json' ) as $inputs ) { if ( isset( $inputs['origin'] ) ) { yield [ $inputs ]; } } } public function testOrigin( array $expected ): void { $url = isset( $expected['base'] ) ? new URL( $expected['input'], $expected['base'] ) : new URL( $expected['input'] ); self::assertSame( $expected['origin'], $url->origin ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLStaticsCanParseTest.php 0000777 00000000716 15251156632 0026270 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Rowbot\URL\URL; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/url-statics-canparse.any.js */ class URLStaticsCanParseTest extends TestCase { public function testCanParse( string $url, ?string $base, bool $expected ): void { self::assertSame( $expected, URL::canParse( $url, $base ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLToJSONTest.php 0000777 00000001007 15251156632 0024307 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URL; use function json_encode; use function str_replace; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/url-tojson.any.js */ class URLToJSONTest extends TestCase { public function testBasicToJSON(): void { $a = new URL( 'https://example.com' ); self::assertSame( '"' . str_replace( '/', '\/', $a->href ) . '"', json_encode( $a, 0 ) ); self::assertSame( '"' . $a->href . '"', $a->toJSON() ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsConstructorTest.php0000777 00000017721 15251156632 0030064 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use Exception; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\URLSearchParams; use stdClass; use function json_decode; use function json_encode; use const JSON_INVALID_UTF8_SUBSTITUTE; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-constructor.any.js */ class URLSearchParamsConstructorTest extends TestCase { public function testBasicConstruction(): void { $params = new URLSearchParams(); self::assertSame( '', $params . '' ); $params = new URLSearchParams( '' ); self::assertSame( '', $params . '' ); $params = new URLSearchParams( 'a=b' ); self::assertSame( 'a=b', $params . '' ); $params = new URLSearchParams( $params ); self::assertSame( 'a=b', $params . '' ); } public function testConstructorNoArguments(): void { $params = new URLSearchParams(); self::assertSame( '', $params->toString() ); } public function testRemovingLeadingQuestionMark(): void { $params = new URLSearchParams( '?a=b' ); self::assertSame( 'a=b', $params->toString() ); } public function testConstructorEmptyObject(): void { $params = new URLSearchParams( new stdClass() ); self::assertSame( '', (string) $params ); } public function testConstructorString(): void { $params = new URLSearchParams( 'a=b' ); self::assertNotNull( $params ); self::assertTrue( $params->has( 'a' ) ); self::assertFalse( $params->has( 'b' ) ); $params = new URLSearchParams( 'a=b&c' ); self::assertNotNull( $params ); self::assertTrue( $params->has( 'a' ) ); self::assertTrue( $params->has( 'c' ) ); $params = new URLSearchParams( '&a&&& &&&&&a+b=& c&m%c3%b8%c3%b8' ); self::assertNotNull( $params ); self::assertTrue( $params->has( 'a' ) ); self::assertTrue( $params->has( 'a b' ) ); self::assertTrue( $params->has( ' ' ) ); self::assertFalse( $params->has( 'c' ) ); self::assertTrue( $params->has( ' c' ) ); self::assertTrue( $params->has( 'møø' ) ); $params = new URLSearchParams( 'id=0&value=%' ); self::assertNotNull( $params ); self::assertTrue( $params->has( 'id' ) ); self::assertTrue( $params->has( 'value' ) ); self::assertSame( '0', $params->get( 'id' ) ); self::assertSame( '%', $params->get( 'value' ) ); $params = new URLSearchParams( 'b=%2sf%2a' ); self::assertNotNull( $params ); self::assertTrue( $params->has( 'b' ) ); self::assertSame( '%2sf*', $params->get( 'b' ) ); $params = new URLSearchParams( 'b=%2%2af%2a' ); self::assertNotNull( $params ); self::assertTrue( $params->has( 'b' ) ); self::assertSame( '%2*f*', $params->get( 'b' ) ); $params = new URLSearchParams( 'b=%%2a' ); self::assertNotNull( $params ); self::assertTrue( $params->has( 'b' ) ); self::assertSame( '%*', $params->get( 'b' ) ); } public function testConstructorObject(): void { $seed = new URLSearchParams( 'a=b&c=d' ); $params = new URLSearchParams( $seed ); self::assertNotNull( $params, 'message' ); self::assertSame( 'b', $params->get( 'a' ) ); self::assertSame( 'd', $params->get( 'c' ) ); self::assertFalse( $params->has( 'd' ), 'message' ); // The name-value pairs are copied when created; later, updates should // not be observable. $seed->append( 'e', 'f' ); self::assertFalse( $params->has( 'e' ) ); $params->append( 'g', 'h' ); self::assertFalse( $seed->has( 'g' ) ); } public function testParsePlusSign(): void { $params = new URLSearchParams( 'a=b+c' ); self::assertSame( 'b c', $params->get( 'a' ) ); $params = new URLSearchParams( 'a+b=c' ); self::assertSame( 'c', $params->get( 'a b' ) ); } public function testParsePlusSignPercentEncoded(): void { $testValue = '+15555555555'; $params = new URLSearchParams(); $params->set( 'query', $testValue ); $newParams = new URLSearchParams( $params->toString() ); self::assertSame( 'query=%2B15555555555', $params->toString() ); self::assertSame( $testValue, $params->get( 'query' ) ); self::assertSame( $testValue, $newParams->get( 'query' ) ); } public function testParseSpace(): void { $params = new URLSearchParams( 'a=b c' ); self::assertSame( 'b c', $params->get( 'a' ) ); $params = new URLSearchParams( 'a b=c' ); self::assertSame( 'c', $params->get( 'a b' ) ); } public function testParseSpacePercentEncoded(): void { $params = new URLSearchParams( 'a=b%20c' ); self::assertSame( 'b c', $params->get( 'a' ) ); $params = new URLSearchParams( 'a%20b=c' ); self::assertSame( 'c', $params->get( 'a b' ) ); } public function testParseNullByte(): void { $params = new URLSearchParams( "a=b\0c" ); self::assertSame( "b\0c", $params->get( 'a' ) ); $params = new URLSearchParams( "a\0b=c" ); self::assertSame( 'c', $params->get( "a\0b" ) ); } public function testParseNullBytePercentEncoded(): void { $params = new URLSearchParams( 'a=b%00c' ); self::assertSame( "b\0c", $params->get( 'a' ) ); $params = new URLSearchParams( 'a%00b=c' ); self::assertSame( 'c', $params->get( "a\0b" ) ); } public function testParseUnicodeCompositionSymbol(): void { $params = new URLSearchParams( "a=b\u{2384}" ); self::assertSame( "b\u{2384}", $params->get( 'a' ) ); $params = new URLSearchParams( "a\u{2384}=c" ); self::assertSame( 'c', $params->get( "a\u{2384}" ) ); } public function testParseUnicodeCompositionSymbolPercentEncoded(): void { $params = new URLSearchParams( 'a=b%E2%8E%84' ); self::assertSame( "b\u{2384}", $params->get( 'a' ) ); $params = new URLSearchParams( 'a%E2%8E%84=c' ); self::assertSame( 'c', $params->get( "a\u{2384}" ) ); } public function testParseUnicodePileOfPoo(): void { $params = new URLSearchParams( "a=b\u{1F4A9}c" ); self::assertSame( "b\u{1F4A9}c", $params->get( 'a' ) ); $params = new URLSearchParams( "a\u{1F4A9}b=c" ); self::assertSame( 'c', $params->get( "a\u{1F4A9}b" ) ); } public function testParseUnicodePileOfPooPercentEncoded(): void { $params = new URLSearchParams( 'a=b%f0%9f%92%a9c' ); self::assertSame( "b\u{1F4A9}c", $params->get( 'a' ) ); $params = new URLSearchParams( 'a%f0%9f%92%a9b=c' ); self::assertSame( 'c', $params->get( "a\u{1F4A9}b" ) ); } public function testSequenceOfSequences(): void { $params = new URLSearchParams( [] ); self::assertNotNull( $params ); $params = new URLSearchParams( [ [ 'a', 'b' ], [ 'c', 'd' ] ] ); self::assertSame( 'b', $params->get( 'a' ) ); self::assertSame( 'd', $params->get( 'c' ) ); try { new URLSearchParams( [ [ 1 ] ] ); self::assertTrue( false ); } catch ( TypeError $exception ) { self::assertTrue( true ); } try { new URLSearchParams( [ [ 1, 2, 3 ] ] ); self::assertTrue( false ); } catch ( TypeError $exception ) { self::assertTrue( true ); } } public static function getTestData(): array { $obj = new stdClass(); $obj->{'+'} = '%C2'; $obj2 = new stdClass(); $obj2->c = 'x'; $obj2->a = '?'; $obj3 = new stdClass(); $obj3->{"a\0b"} = '42'; $obj3->{"c\u{D83D}"} = '23'; $obj3->{"d\u{1234}"} = 'foo'; // Mimic error handling of JavaScript Object keys $json = json_encode( $obj3, JSON_INVALID_UTF8_SUBSTITUTE ); if ( json_last_error() !== JSON_ERROR_NONE ) { throw new Exception( json_last_error_msg() ); } $obj3 = json_decode( $json, false, 512, 0 ); if ( json_last_error() !== JSON_ERROR_NONE ) { throw new Exception( json_last_error_msg() ); } return [ [ 'input' => $obj, 'output' => [ [ '+', '%C2' ] ] ], [ 'input' => $obj2, 'output' => [ [ 'c', 'x' ], [ 'a', '?' ], ], ], [ 'input' => [ [ 'c', 'x' ], [ 'a', '?' ], ], 'output' => [ [ 'c', 'x' ], [ 'a', '?' ], ], ], [ 'input' => $obj3, 'output' => [ [ "a\0b", '42' ], [ "c\u{FFFD}", '23' ], [ "d\u{1234}", 'foo' ], ], ], ]; } public function test( $input, array $output ): void { $params = new URLSearchParams( $input ); $i = 0; foreach ( $params as $param ) { self::assertSame( $output[ $i ++ ], $param ); } } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/ToASCIIWindowTest.php 0000777 00000002516 15251156632 0025201 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\Attributes\DataProvider; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\URL; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/toascii.window.js */ class ToASCIIWindowTest extends WhatwgTestCase { public static function toAsciiTestProvider(): iterable { foreach ( self::loadTestData( 'toascii.json' ) as $inputs ) { yield [ $inputs ]; } } public function testURLContructor( array $hostTest ): void { if ( $hostTest['output'] !== null ) { $url = new URL( 'https://' . $hostTest['input'] . '/x' ); self::assertSame( $hostTest['output'], $url->host ); self::assertSame( $hostTest['output'], $url->hostname ); self::assertSame( '/x', $url->pathname ); self::assertSame( 'https://' . $hostTest['output'] . '/x', $url->href ); return; } $this->expectException( TypeError::class ); new URL( $hostTest['input'] ); } public function testHostSetter( array $hostTest ): void { $url = new URL( 'https://x/x' ); $url->host = $hostTest['input']; self::assertSame( $hostTest['output'] ?? 'x', $url->host ); } public function testHostnameSetter( array $hostTest ): void { $url = new URL( 'https://x/x' ); $url->hostname = $hostTest['input']; self::assertSame( $hostTest['output'] ?? 'x', $url->hostname ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/PercentEncodingTest.php 0000777 00000005620 15251156632 0025724 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\Attributes\DataProvider; use Rowbot\URL\BasicURLParser; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; use Rowbot\URL\String\Utf8String; class PercentEncodingTest extends WhatwgTestCase { public function testPercentEncoding( string $input, array $output ): void { $parser = new BasicURLParser(); $in = new Utf8String( "https://doesnotmatter.invalid/?{$input}#{$input}" ); foreach ( $output as $encoding => $expected ) { $url = $parser->parse( $in, null, $encoding ); self::assertNotFalse( $url ); self::assertSame( $expected, $url->query, $encoding ); self::assertSame( $output['utf-8'], $url->fragment ); } } public static function percentEncodedDataProvider(): iterable { foreach ( self::loadTestData( 'percent-encoding.json' ) as $data ) { // Skip tests for encodings where mbstring produces a result that is different from what is expected foreach ( [ 'iso-2022-jp', 'gb18030' ] as $encoding ) { if ( isset( $data['output'][ $encoding ] ) ) { unset( $data['output'][ $encoding ] ); } } yield $data; } } /** * @see https://url.spec.whatwg.org/#example-percent-encode-operations * * @param mixed $encodeSet */ public function testPercentEncodingExamples( string $encoding, string $input, string $output, $encodeSet, bool $spaceAsPlus ): void { $percentEncoder = new PercentEncoder(); $result = $percentEncoder->percentEncodeAfterEncoding( $encoding, $input, $encodeSet, $spaceAsPlus ); self::assertSame( $output, $result ); } public static function exampleDataProvider(): array { return [ [ 'encoding' => 'Shift_JIS', 'input' => ' ', 'output' => '%20', 'encodeSet' => EncodeSet::USERINFO, 'spaceAsPlus' => false ], [ 'encoding' => 'Shift_JIS', 'input' => '≡', 'output' => '%81%DF', 'encodeSet' => EncodeSet::USERINFO, 'spaceAsPlus' => false ], [ 'encoding' => 'Shift_JIS', 'input' => '‽', 'output' => '%26%238253%3B', 'encodeSet' => EncodeSet::USERINFO, 'spaceAsPlus' => false, ], // ['encoding' => 'ISO-2022-JP', 'input' => '¥', 'output' => '%1B(J\%1B(B', 'encodeSet' => EncodeSet::USERINFO, 'spaceAsPlus' => false], [ 'encoding' => 'Shift_JIS', 'input' => '1+1 ≡ 2%20‽', 'output' => '1+1+%81%DF+2%20%26%238253%3B', 'encodeSet' => EncodeSet::USERINFO, 'spaceAsPlus' => true, ], [ 'encoding' => 'UTF-8', 'input' => '≡', 'output' => '%E2%89%A1', 'encodeSet' => EncodeSet::USERINFO, 'spaceAsPlus' => false ], [ 'encoding' => 'UTF-8', 'input' => '‽', 'output' => '%E2%80%BD', 'encodeSet' => EncodeSet::USERINFO, 'spaceAsPlus' => false ], [ 'encoding' => 'UTF-8', 'input' => 'Say what‽', 'output' => 'Say%20what%E2%80%BD', 'encodeSet' => EncodeSet::USERINFO, 'spaceAsPlus' => false, ], ]; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsSetTest.php 0000777 00000002563 15251156632 0026270 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-set.any.js */ class URLSearchParamsSetTest extends TestCase { public function testSetBasics(): void { $params = new URLSearchParams( 'a=b&c=d' ); $params->set( 'a', 'B' ); self::assertSame( 'a=B&c=d', $params . '' ); $params = new URLSearchParams( 'a=b&c=d&a=e' ); $params->set( 'a', 'B' ); self::assertSame( 'a=B&c=d', $params . '' ); $params->set( 'e', 'f' ); self::assertSame( 'a=B&c=d&e=f', $params . '' ); } public function testURLSearchParamsSet(): void { $params = new URLSearchParams( 'a=1&a=2&a=3' ); self::assertTrue( $params->has( 'a' ), 'Search params object has name "a"' ); self::assertSame( '1', $params->get( 'a' ), 'Search params object has name "a" with a value of "1"' ); $params->set( 'first', 4 ); self::assertTrue( $params->has( 'a' ), 'Search params object has name "a"' ); self::assertSame( '1', $params->get( 'a' ), 'Search params object has name "a" with value "1"' ); $params->set( 'a', 4 ); self::assertTrue( $params->has( 'a' ), 'Search params object has name "a"' ); self::assertSame( '4', $params->get( 'a' ), 'Search params object has name "a" with value "4"' ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/FailureTest.php 0000777 00000002053 15251156632 0024241 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\Attributes\DataProvider; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\URL; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/failure.html */ class FailureTest extends WhatwgTestCase { public static function urlTestDataFailureProvider(): iterable { foreach ( self::loadTestData( 'urltestdata.json' ) as $inputs ) { if ( isset( $inputs['failure'] ) && $inputs['base'] === null ) { yield [ $inputs ]; } } } /** * URL's constructor's first argument is tested by url-constructorTest. If a * URL fails to parse with any valid base, it must also fail to parse with * no base, i.e. when used as a base URL itself. */ public function testURLContructor( array $test ): void { $this->expectException( TypeError::class ); new URL( 'about:blank', $test['input'] ); } public function testUrlHrefSetterThrows( array $test ): void { $this->expectException( TypeError::class ); $url = new URL( 'about:blank' ); $url->href = $test['input']; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsGetAllTest.php 0000777 00000002533 15251156632 0026702 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\TestCase; use Rowbot\URL\URLSearchParams; use function count; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-getall.any.js */ class URLSearchParamsGetAllTest extends TestCase { public function testGetAllBasics(): void { $params = new URLSearchParams( 'a=b&c=d' ); self::assertSame( [ 'b' ], $params->getAll( 'a' ) ); self::assertSame( [ 'd' ], $params->getAll( 'c' ) ); self::assertSame( [], $params->getAll( 'e' ) ); $params = new URLSearchParams( 'a=b&c=d&a=e' ); self::assertSame( [ 'b', 'e' ], $params->getAll( 'a' ) ); $params = new URLSearchParams( '=b&c=d' ); self::assertSame( [ 'b' ], $params->getAll( '' ) ); $params = new URLSearchParams( 'a=&c=d&a=e' ); self::assertSame( [ '', 'e' ], $params->getAll( 'a' ) ); } public function testGetAllMultiple(): void { $params = new URLSearchParams( 'a=1&a=2&a=3&a' ); self::assertTrue( $params->has( 'a' ) ); $matches = $params->getAll( 'a' ); self::assertTrue( $matches && count( $matches ) === 4 ); self::assertSame( [ '1', '2', '3', '' ], $matches ); $params->set( 'a', 'one' ); self::assertSame( 'one', $params->get( 'a' ) ); $matches = $params->getAll( 'a' ); self::assertTrue( $matches && count( $matches ) === 1 ); self::assertSame( [ 'one' ], $matches ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLSearchParamsSizeTest.php 0000777 00000003245 15251156632 0026445 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\TestCase; use Rowbot\URL\URL; use Rowbot\URL\URLSearchParams; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-size.any.js */ class URLSearchParamsSizeTest extends TestCase { public function testSizeAndDeletion(): void { $params = new URLSearchParams( 'a=1&b=2&a=3' ); self::assertSame( 3, $params->size ); self::assertCount( 3, $params ); $params->delete( 'a' ); self::assertSame( 1, $params->size ); self::assertCount( 1, $params ); } public function testSizeAndAddition(): void { $params = new URLSearchParams( 'a=1&b=2&a=3' ); self::assertSame( 3, $params->size ); self::assertCount( 3, $params ); $params->append( 'b', '4' ); self::assertSame( 4, $params->size ); self::assertCount( 4, $params ); } public function testSizeWhenObtainedFromAURL(): void { $url = new URL( "http://localhost/query?a=1&b=2&a=3" ); self::assertSame( 3, $url->searchParams->size ); self::assertCount( 3, $url->searchParams ); $url->searchParams->delete( 'a' ); self::assertSame( 1, $url->searchParams->size ); self::assertCount( 1, $url->searchParams ); $url->searchParams->append( 'b', '4' ); self::assertSame( 2, $url->searchParams->size ); self::assertCount( 2, $url->searchParams ); } public function testSizeWhenObtainedFromAURLAndUsingSearch(): void { $url = new URL( "http://localhost/query?a=1&b=2&a=3" ); self::assertSame( 3, $url->searchParams->size ); self::assertCount( 3, $url->searchParams ); $url->search = '?'; self::assertSame( 0, $url->searchParams->size ); self::assertCount( 0, $url->searchParams ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/WhatWg/URLConstructorTest.php 0000777 00000004037 15251156632 0025566 0 ustar 00 <?php namespace Rowbot\URL\Tests\WhatWg; use PHPUnit\Framework\Attributes\DataProvider; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\URL; use function array_key_exists; /** * @see https://github.com/web-platform-tests/wpt/blob/master/url/url-constructor.html */ class URLConstructorTest extends WhatwgTestCase { public static function urlTestDataSuccessProvider(): iterable { foreach ( self::loadTestData( 'urltestdata.json' ) as $inputs ) { if ( isset( $inputs['base'] ) && ! isset( $inputs['failure'] ) ) { yield [ $inputs ]; } } } public function testUrlConstructorSucceeded( array $expected ): void { $base = $expected['base'] ? $expected['base'] : 'about:blank'; $url = new URL( $expected['input'], $base ); self::assertSame( $expected['href'], $url->href, 'href' ); self::assertSame( $expected['protocol'], $url->protocol, 'protocol' ); self::assertSame( $expected['username'], $url->username, 'username' ); self::assertSame( $expected['password'], $url->password, 'password' ); self::assertSame( $expected['host'], $url->host, 'host' ); self::assertSame( $expected['hostname'], $url->hostname, 'hostname' ); self::assertSame( $expected['port'], $url->port, 'port' ); self::assertSame( $expected['pathname'], $url->pathname, 'pathname' ); self::assertSame( $expected['search'], $url->search, 'search' ); if ( array_key_exists( 'searchParams', $expected ) ) { self::assertTrue( (bool) $url->searchParams ); self::assertSame( $expected['searchParams'], $url->searchParams->toString(), 'searchParams' ); } self::assertSame( $expected['hash'], $url->hash, 'hash' ); } public static function urlTestDataFailureProvider(): iterable { foreach ( self::loadTestData( 'urltestdata.json' ) as $inputs ) { if ( isset( $inputs['failure'] ) ) { yield [ $inputs ]; } } } public function testUrlConstructorFailed( array $expected ): void { $this->expectException( TypeError::class ); isset( $expected['base'] ) ? new URL( $expected['input'], $expected['base'] ) : new URL( $expected['input'] ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/NullHostTest.php 0000777 00000001306 15251156632 0023221 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\TestCase; use Rowbot\URL\Component\Host\NullHost; use Rowbot\URL\Component\Host\StringHost; class NullHostTest extends TestCase { public function testNullHostSerializesToEmptyString(): void { $host = new NullHost(); $serializer = $host->getSerializer(); self::assertEmpty( $serializer->toFormattedString() ); self::assertEmpty( $serializer->toString() ); } public function testNullHostIsEqualOnlyToItself(): void { $host = new NullHost(); self::assertTrue( $host->equals( $host ) ); self::assertTrue( $host->equals( new NullHost() ) ); self::assertFalse( $host->equals( new StringHost() ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/HostParserTest.php 0000777 00000004001 15251156632 0023536 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rowbot\URL\Component\Host\HostInterface; use Rowbot\URL\Component\Host\HostParser; use Rowbot\URL\ParserContext; use Rowbot\URL\String\StringBuffer; use Rowbot\URL\String\Utf8String; use Rowbot\URL\URLRecord; class HostParserTest extends TestCase { /** * @see https://url.spec.whatwg.org/#example-host-parsing */ public function testHostParser( string $input, array $output ): void { $parser = new HostParser(); $in = new Utf8String( $input ); $context = new ParserContext( $in, $in->getIterator(), new StringBuffer(), new URLRecord(), null, null, null, null ); foreach ( $output as $i => $expected ) { $isOpaque = $i % 2 === 1; $host = $parser->parse( $context, $in, $isOpaque ); if ( $expected === false ) { self::assertFalse( $host ); } else { self::assertInstanceOf( HostInterface::class, $host ); self::assertSame( $expected, $host->getSerializer()->toFormattedString() ); } } } public static function exampleDataProvider(): array { return [ [ 'input' => 'EXAMPLE.COM', 'output' => [ 'example.com', 'EXAMPLE.COM' ] ], [ 'input' => 'example%2Ecom', 'output' => [ 'example.com', 'example%2Ecom' ] ], [ 'input' => 'faß.example', 'output' => [ 'xn--fa-hia.example', 'fa%C3%9F.example' ] ], [ 'input' => '0', 'output' => [ '0.0.0.0', '0' ] ], [ 'input' => '%30', 'output' => [ '0.0.0.0', '%30' ] ], [ 'input' => '0x', 'output' => [ '0.0.0.0', '0x' ] ], [ 'input' => '0xffffffff', 'output' => [ '255.255.255.255', '0xffffffff' ] ], [ 'input' => '[0:0::1]', 'output' => [ '[::1]', '[::1]' ] ], [ 'input' => '[0:0::1%5D', 'output' => [ false, false ] ], [ 'input' => '[0:0::%31]', 'output' => [ false, false ] ], [ 'input' => '09', 'output' => [ false, '09' ] ], [ 'input' => 'example.255', 'output' => [ false, 'example.255' ] ], [ 'input' => 'example^example', 'output' => [ false, false ] ], ]; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/EncodingHelperTest.php 0000777 00000000660 15251156632 0024341 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Rowbot\URL\Support\EncodingHelper; class EncodingHelperTest extends TestCase { public function testReplacementAndUtf16EncodingsGetForcedToUtf8( string $encoding, string $outputEncoding ): void { self::assertSame( $outputEncoding, EncodingHelper::getOutputEncoding( $encoding ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/ValidationErrorLogger.php 0000777 00000000674 15251156632 0025064 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests; use Psr\Log\LoggerInterface; use Psr\Log\LoggerTrait; use Stringable; final class ValidationErrorLogger implements LoggerInterface { use LoggerTrait; /** * @var mixed[] */ private $messages; /** * @param string|Stringable $message */ public function log( $level, $message, array $context = [] ): void { $this->messages[] = [ $level, $message, $context ]; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/Math/BrickMathAdapterTest.php 0000777 00000002033 15251156632 0025505 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests\Math; use Rowbot\URL\Component\Host\Math\BrickMathAdapter; use Rowbot\URL\Component\Host\Math\Exception\MathException; use Rowbot\URL\Component\Host\Math\NativeIntAdapter; use Rowbot\URL\Component\Host\Math\NumberInterface; class BrickMathAdapterTest extends MathTestCase { /** * @param int|string $number */ public function createNumber( $number, int $base = 10 ): NumberInterface { return new BrickMathAdapter( $number, $base ); } public function testIsEqaulToError(): void { $this->expectException( MathException::class ); ( new BrickMathAdapter( 42 ) )->isEqualTo( new NativeIntAdapter( 42 ) ); } public function testIsGreaterThanOrEqualToError(): void { $this->expectException( MathException::class ); ( new BrickMathAdapter( 42 ) )->isGreaterThanOrEqualTo( new NativeIntAdapter( 42 ) ); } public function testPlusError(): void { $this->expectException( MathException::class ); ( new BrickMathAdapter( 42 ) )->plus( new NativeIntAdapter( 42 ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/Math/MathTestCase.php 0000777 00000005756 15251156632 0024044 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests\Math; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Rowbot\URL\Component\Host\Math\NumberInterface; use const PHP_INT_MAX; abstract class MathTestCase extends TestCase { /** * @param int|string $number */ abstract public function createNumber( $number, int $base = 10 ): NumberInterface; public function testIntDiv( int $divisor, string $quoient ): void { $dividend = $this->createNumber( 42 ); $computedQuotient = $dividend->intdiv( $divisor ); self::assertTrue( $computedQuotient->isEqualTo( $this->createNumber( $quoient ) ) ); self::assertSame( $quoient, (string) $computedQuotient ); } public static function equalityNumberProvider(): array { return [ [ PHP_INT_MAX, 10, (string) PHP_INT_MAX ], [ '01234567', 8, '342391' ], [ 'DF', 16, '223' ], [ '-24', 10, '-24' ], ]; } /** * @param int|string $number */ public function testIsEqualTo( $number, int $base, string $expected ): void { self::assertTrue( $this->createNumber( $number, $base )->isEqualTo( $this->createNumber( $expected ) ) ); } public function testIsGreaterThan( int $number1, int $number2, bool $result ): void { self::assertSame( $result, $this->createNumber( $number1 )->isGreaterThan( $number2 ) ); } public function testIsGreaterThanOrEqualTo( int $number1, int $number2, bool $result ): void { self::assertSame( $result, $this->createNumber( $number1 )->isGreaterThanOrEqualTo( $this->createNumber( $number2 ) ) ); } public function testMod( int $dividend, int $divisor, int $remainder ): void { $computedRemainder = $this->createNumber( $dividend )->mod( $divisor ); self::assertTrue( $computedRemainder->isEqualTo( $this->createNumber( $remainder ) ) ); self::assertSame( (string) $remainder, (string) $computedRemainder ); } public function testMultipliedBy( int $multiplicand, int $multiplier, int $product ): void { $computedProduct = $this->createNumber( $multiplicand )->multipliedBy( $multiplier ); self::assertTrue( $computedProduct->isEqualTo( $this->createNumber( $product ) ) ); self::assertSame( (string) $product, (string) $computedProduct ); } public function testPlus( int $addend1, int $addend2, string $sum ): void { $computedSum = $this->createNumber( $addend1 )->plus( $this->createNumber( $addend2 ) ); self::assertTrue( $computedSum->isEqualTo( $this->createNumber( $sum ) ) ); self::assertSame( $sum, (string) $computedSum ); } public function testPow( int $base, int $exponent, string $power ): void { $computedPower = $this->createNumber( $base )->pow( $exponent ); self::assertTrue( $computedPower->isEqualTo( $this->createNumber( $power ) ) ); self::assertSame( $power, (string) $computedPower ); } /** * @param int|string $number */ public function testToString( $number, int $base, string $result ): void { self::assertSame( $result, (string) $this->createNumber( $number, $base ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/tests/Math/NativeIntAdapterTest.php 0000777 00000002033 15251156632 0025542 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Tests\Math; use Rowbot\URL\Component\Host\Math\BrickMathAdapter; use Rowbot\URL\Component\Host\Math\Exception\MathException; use Rowbot\URL\Component\Host\Math\NativeIntAdapter; use Rowbot\URL\Component\Host\Math\NumberInterface; class NativeIntAdapterTest extends MathTestCase { /** * @param int|string $number */ public function createNumber( $number, int $base = 10 ): NumberInterface { return new NativeIntAdapter( $number, $base ); } public function testIsEqaulToError(): void { $this->expectException( MathException::class ); ( new NativeIntAdapter( 42 ) )->isEqualTo( new BrickMathAdapter( 42 ) ); } public function testIsGreaterThanOrEqualToError(): void { $this->expectException( MathException::class ); ( new NativeIntAdapter( 42 ) )->isGreaterThanOrEqualTo( new BrickMathAdapter( 42 ) ); } public function testPlusError(): void { $this->expectException( MathException::class ); ( new NativeIntAdapter( 42 ) )->plus( new BrickMathAdapter( 42 ) ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/LICENSE 0000777 00000002061 15251156632 0017742 0 ustar 00 MIT License Copyright (c) 2018 Trevor Rowbotham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/rowbot/url/composer.json 0000777 00000002263 15251156632 0021463 0 ustar 00 { "name": "rowbot/url", "description": "A WHATWG URL spec compliant URL parser for working with URLs and their query strings.", "keywords": [ "url", "url-parser", "uri", "querystring", "query-string", "url-parsing", "whatwg" ], "license": "MIT", "authors": [ { "name": "Trevor Rowbotham", "homepage": "https://trowbotham.com", "role": "Developer" } ], "require": { "php": ">=8.1", "ext-mbstring": "*", "brick/math": "^0.8.13 || ^0.9", "psr/log": "^3.0", "rowbot/idna": "^0.1.5" }, "require-dev": { "guzzlehttp/guzzle": "^7.0", "phpstan/phpstan": "^1.9", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^10.0 || ^11.0", "slevomat/coding-standard": "^8.0", "squizlabs/php_codesniffer": "^3.7.1", "symfony/cache": "^5.0 || ^6.0" }, "autoload": { "psr-4": { "Rowbot\\URL\\": "src/" } }, "autoload-dev": { "psr-4": { "Rowbot\\URL\\Tests\\": "tests/" } }, "scripts": { "phpunit": "phpunit --coverage-text", "test": [ "@phpunit" ] }, "config": { "sort-packages": true, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/CHANGELOG.md 0000777 00000033204 15251156632 0020551 0 ustar 00 # Changelog ## [Unreleased] ## [4.0.0] - 2024-06-20 ### Added - Performance improvements - Validation error logging - `\Rowbot\URL\URL` and `\Rowbot\URL\URLSearchParams` now implement `\Stringable` - `\Rowbot\URL\URLSearchParams` constructor now has a native typehint of `array|object|string` - `\Rowbot\URL\URLSearchParams` now has a `size` getter per [whatwg/url#734](https://github.com/whatwg/url/pull/734) - `\Rowbot\URL\URLSearchParams` now also implements `\Countable` - Added `\Rowbot\URL\URL::canParse()`, which returns a boolean if parsing was successful, per [whatwg/url#713](https://github.com/whatwg/url/issues/713) and [whatwg/url#763](https://github.com/whatwg/url/pull/763) - Add value parameter to `\Rowbot\URL\URLSearchParams::has()` and `\Rowbot\URL\URLSearchParams::delete()` per [whatwg/url#335](https://github.com/whatwg/url/issues/335) and [whatwg/url#735](https://github.com/whatwg/url/pull/735) - Added `\Rowbot\URL\URL::parse()`, which will return the parsed URL or null on failure, avoiding needing a `try` statement per [whatwg/url#372](https://github.com/whatwg/url/issues/372) and [whatwg/url#825](https://github.com/whatwg/url/pull/825) ### Changed - Bump minimum PHP version to 8.1 - Lone surrogate code points are no longer treated differently from other invalid code points - `\Rowbot\URL\String\Exception\UConverterException` has been renamed to `\Rowbot\URL\String\Exception\EncodingException` - Moved 32-bit tests to GitHub Actions from Appveyor - `\Rowbot\URL\URL`'s $url and $base parameters now also accept `\Stringable` - `\Rowbot\URL\URLSearchParams::current()` now returns `null` when the iterator is invalid instead of `['', '']`, which better matches the expected behavior - Ensure opaque paths can round trip from the API [whatwg/url#651](https://github.com/whatwg/url/issues/651) [whatwg/url#728](https://github.com/whatwg/url/pull/728) - Blob URL's with an inner non-http(s) URL now returns an opaque origin per [whatwg/url#770](https://github.com/whatwg/url/issues/770) and [whatwg/url#771](https://github.com/whatwg/url/pull/771) ### Removed - Removed `\Rowbot\URL\Exception\JsonException` in favor of `\JsonException` ### Internals - Removed class `\Rowbot\URL\String\IDLString` - Added method `\Rowbot\URL\String\Utf8String::scrub()` - Added method `\Rowbot\URL\String\Utf8String::fromUnsafe()` - Moved method `\Rowbot\URL\String\AbstractUSVString::transcode()` to `\Rowbot\URL\String\Utf8String` - Removed method `\Rowbot\URL\String\Exception\RegexException::getNameFromLastCode()` - All objects with a `__toString()` method now implement `\Stringable` - Added native union typehints where possible - `\Rowbot\URL\Origin` is now an interface - Added class `\Rowbot\URL\Component\TupleOrigin` which implements `\Rowbot\URL\Origin` - Added class `\Rowbot\URL\Component\OpaqueOrigin` which implements `\Rowbot\URL\Origin` - `\Rowbot\URL\Component\PathListInterface` renamed to `\Rowbot\URL\PathInterface` - Added class `\Rowbot\URL\Component\OpaquePath` which implements `\Rowbot\URL\PathInterface` - `\Rowbot\URL\Component\Path` was renamed to `\Rowbot\URL\Component\PathSegment` - `\Rowbot\URL\State\CannotBeABaseUrlPathState` was renamed to `\Rowbot\URL\State\OpaquePathState` - Removed property `\Rowbot\URL\URLRecord::$cannotBeABaseUrl` - Adopted the specs new percent encoding model - Removed const `\Rowbot\URL\String\CodePoint::C0_CONTROL_PERCENT_ENCODE_SET` - Removed const `\Rowbot\URL\String\CodePoint::FRAGMENT_PERCENT_ENCODE_SET` - Removed const `\Rowbot\URL\String\CodePoint::PATH_PERCENT_ENCODE_SET` - Removed const `\Rowbot\URL\String\CodePoint::USERINFO_PERCENT_ENCODE_SET` - Removed method `\Rowbot\URL\String\CodePoint::utf8PercentEncode()` - Added enum `\Rowbot\URL\String\EncodeSet` - Added class `\Rowbot\URL\String\PercentEncoder` - `\Rowbot\URL\Component\Host\HostParser` methods are no longer static - `\Rowbot\URL\String\StringListInterface` extends `\IteratorAggregate` instead of `\Iterator` - `\Rowbot\URL\Component\QueryList` now implements `\Countable` - Added enum `\Rowbot\URL\State\StatusCode` - `\Rowbot\URL\State\State` now has a return type of `\Rowbot\URL\State\StatusCode` instead of `int` - Removed const `\Rowbot\URL\State\State::RETURN_OK` - Removed const `\Rowbot\URL\State\State::RETURN_CONTINUE` - Removed const `\Rowbot\URL\State\State::RETURN_BREAK` - Removed const `\Rowbot\URL\State\State::RETURN_FAILURE` ## [3.1.7] - 2022-08-26 ### Fixed - Detection of windows drive letters ## [3.1.6] - 2022-08-16 ### Changed - Forbid C0 control code points and U+007F DEL code point in non-opaque domain names per [whatwg/url#685](https://github.com/whatwg/url/pull/685) ## Fixed - Fix parsing IPv4-mapped IPv6 addresses due to wrong logical condition ## [3.1.5] - 2022-01-10 ### Changed - Upgraded to PHPStan 1.0 ### Fixed - Initialization of `URLSearchParams` using an object now behaves the same as when using and iterator or string ## [3.1.4] - 2021-07-27 ### Added - Support for PHP 8.1 ### Changed - Make the `URL::$hostname` setter do nothing if the host name contains a ":" per [whatwg/url#601](https://github.com/whatwg/url/issues/601) [whatwg/url#604](https://github.com/whatwg/url/pull/604) - Reject non-IPv4 hostnames that end in numbers per [whatwg/url#560](https://github.com/whatwg/url/issues/560) [whatwg/url#619](https://github.com/whatwg/url/pull/619) ### Fixed - Prevent the pathname setter from erasing the path of path-only URLs [whatwg/url#581](https://github.com/whatwg/url/issues/581) [whatwg/url#582](https://github.com/whatwg/url/pull/582) ## [3.1.3] - 2021-05-11 ### Fixed - File URL reparse bug with percent encoded windows drive letter [whatwg/url#589](https://github.com/whatwg/url/pull/589) ### Changed - Switched to GitHub Actions for CI - Minimum PHPUnit version is now 7.5 ## [3.1.2] - 2021-02-11 ### Added - Performance improvements - Test on PHP 8 release in CI ## [3.1.1] - 2020-10-17 ### Added - Support for `brick/math` ^0.9 ### Changed - Serializing non-special URLs is now idempotent per [whatwg/url#505](https://github.com/whatwg/url/pull/505) - Changes to file URL path normalization per [whatwg/url#544](https://github.com/whatwg/url/pull/544). ## [3.1.0] - 2020-07-15 ### Added - Added test coverage on Windows 32-bit. - Dependency on `rowbot/idna` to support international domain names without `ext-intl`. ### Changed - `URLSearchParams::sort()` now correctly sorts by code units instead of trying to fake it using code points. - Null bytes are now percent encoded in fragments instead of being ignored per [whatwg/url#440](https://github.com/whatwg/url/issues/440). - Domain names of URLs with special schemes can no longer be an empty string per [whatwg/url#492](https://github.com/whatwg/url/pull/492) and [whatwg/url#497](https://github.com/whatwg/url/pull/497). - Host names now also forbid the use of the `<`, `>`, and `^` code points per [whatwg/url#458](https://github.com/whatwg/url/issues/458). ### Fixed - Incorrect output encoding used when encoding was overriden to be "replacement", "utf-16", "utf-16le", or "utf-16be". - `ext-json` is now correctly listed as a dependency. ### Removed - Dependency on `ext-intl` and `lib-ICU`. ## [3.0.1] - 2020-02-29 ### Added - Improved portability and 32-bit PHP support. - Dependency on `brick/math` to support 32-bit versions of PHP. ### Removed - Removed `ext-gmp` as a dependency. ## [3.0.0] - 2020-02-11 The majority of this library was rewritten in this update, but the public API has remain unchanged. This should make for a relatively painless upgrade. ### Added - Installation requirement of ICU >= 4.6 to assist with [#6](https://github.com/TRowbotham/URL-Parser/issues/6) - Support for PHPUnit ^8.0 and ^9.0. - More test coverage. - Support for symfony/cache ^5.0. ### Changed - "gopher" was removed from the list of special schemes per [whatwg/url#453](https://github.com/whatwg/url/pull/453) and [whatwg/url#454](https://github.com/whatwg/url/pull/454). - Coding style has been updated for PSR-12. - Non-iterable objects passed to the `URLSearchParams` constructor now have their properties iterated over using `\ReflectionObject::getProperties()` rather than directly. There should not be any change in behavior. - Removed artificial limitation when passing a sequence of sequences to the `URLSearchParams` constructor that required the non-array sub-sequences to implement the `\ArrayAccess` interface. - Note that sub-sequences must still be countable and only contain exactly 2 items. - This broadens the type of sequences that can be supplied from `iterable<mixed, array{0: string, 1: string}>` to `iterable<mixed, iterable<mixed, stringable>>`, where `stringable` is a scalar value or an object with the `__toString()` method. - `URLSearchParams` now throws a `\Rowbot\URL\Exception\TypeError` instead of a `\InvalidArgument` exception to match browsers when passed an iterable that does not solely contain other iterables that are countable. - JSON encoding the `URL` object will now throw a `\Rowbot\URL\Exception\JsonException` when JSON encoding fails. ### Fixed - Documentaion errors. - Restores expected string conversion behavior on systems using an ICU version >= 60.1 ([#7](https://github.com/TRowbotham/URL-Parser/issues/6)). - `Origin::getEffectiveDomain()` was incorrectly returning the string `"null"` instead of the actual value `null` when the origin was an opaque origin. ### Removed - `phpstan/phpstan` and associated packages are no longer a dev dependency and is now only run in CI. - No longer depends on `ext-ctype`, which was an unlisted dependency. - `php-coveralls/php-coveralls` is no longer a dev dependency. The project has moved to Codecov instead. - `squizlabs/php_codesniffer` is no longer a dev dependency and is only run in CI now. ## [2.0.3] - 2019-08-13 ### Fixed - "%2e%2E" and "%2E%2e" were not properly detected as percent encoded double dot path segments. ## [2.0.2] - 2019-04-28 ### Added - PHP 7.4 compatibility - Sped up IPv6 address serialization by removing some unncessary type casting. ## [2.0.1] - 2019-04-15 ### Fixed - Loading cached test data failed when using newer versions of the symfony/cache component. ## [2.0.0] - 2018-12-08 ### Added - Tests now automatically pull the latest data directly from the Web Platform Tests repository - thanks [@nyamsprod](https://github.com/nyamsprod) ### Changed - The minimum required PHP version is now 7.1. - Testing environment updated for PHP 7.1 - thanks [@nyamsprod](https://github.com/nyamsprod) - Native typehints are now used. This which means that an `\TypeError` is now thrown instead of an `\InvalidArgumentException` when a value with an incorrect type is passed. - `\Rowbot\URL\Exception\TypeError` and `\Rowbot\URL\Exception\InvalidParserState` now inherit from `\Rowbot\URL\Exception\URLException` ## [1.1.1] - 2018-08-15 ### Added - Sped up `URLSearchParams::has()` when the string does not exist in the list. - TravisCI automation for tests and code coverage. ### Fixed - The query string sorting algorithm now correctly sorts by code units instead of code points. ## [1.1.0] - 2018-06-21 ### Added - Updated documentation - Updated tests ### Changed - Only scalar values (bool, float, int, string) and objects with a `__toString()` method are considered as valid input now for methods and properties that only accept strings. This matches what PHP's string type hint would allow, allowing for an easier upgrade path when adding native type hints in the future. - A `null` value is no longer considered valid input and will cause an `\InvalidArgumentException` to be thrown. Previously, this was converted to the string `"null"`. - A `resource` value such as that returned by a call to `fopen()` is no longer considered valid input and will cause an `\InvalidArgumentException` to be thrown. This was previously casted to a string resulting in something like `"Resource id #1"`. - Previously, the values `true` and `false` were converted to the strings `"true"` and `"false"`. This is no longer the case. They now are now simply cast to a string resulting in the values `"1"` and `""` respectively. - Passing an `iterable` that does not solely contain other `iterables` to the `URLSearchParams` constructor now causes it to throw an `\InvalidArgumentException`. - Trying to access an invalid property on the `URL` object will now throw an `\InvalidArgumentException` to help catch typos. ## [1.0.3] - 2018-06-19 ### Added - Sped up serialization of IPv6 addresses - Slightly better handling of non-UTF-8 encoded text when parsing query strings. - A bunch of missing `use` import statements - Updated tests and test data. ### Changed - URLs with [special schemes](https://url.spec.whatwg.org/#special-scheme) now percent encode `'` characters in the query string. - The application/x-www-form-urlencoded parser now only handles UTF-8 encoded text. ## [1.0.2] - 2018-02-28 ### Changed - Trying to set `URL::searchParams` directly will now throw a `\Rowbot\URL\Exception\TypeError` - Passing invalid input will throw an `\InvalidArgumentException` - Malformed byte sequences will now get fixed up with `\u{FFFD}` replacement characters. - `URLSearchParams` now implements `\Iterator` instead of `\IteratorAggregate` to match test expectations. ### Fixed - The last few failing tests are now passing with the exception of 3 errors as a result of PHP bug [72506](https://bugs.php.net/bug.php?id=72506) ## [1.0.1] - 2018-02-22 ### Added - MIT License ## [1.0.0] - 2018-02-22 - Initial Release! php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/AbstractUSVString.php 0000777 00000006306 15251156632 0025041 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use Rowbot\URL\String\Exception\RegexException; use function explode; use function intval; use function mb_strlen; use function mb_substr; use function preg_last_error_msg; use function preg_match; use function preg_replace; use function sprintf; use function strspn; use const PHP_INT_MAX; abstract class AbstractUSVString implements USVStringInterface { /** * @var string */ protected $string; public function __construct( string $string = '' ) { $this->string = $string; } public function append( string $string ): USVStringInterface { $copy = clone $this; $copy->string .= $string; return $copy; } public function endsWith( string $string ): bool { return substr_compare( $this->string, $string, - strlen( $string ) ) === 0; } public function getIterator(): StringIteratorInterface { return new Utf8StringIterator( $this->string ); } public function isEmpty(): bool { return $this->string === ''; } public function length(): int { return mb_strlen( $this->string, 'utf-8' ); } public function matches( string $pattern, ?array &$matches = null, int $flags = 0, int $offset = 0 ): bool { $result = preg_match( $pattern, $this->string, $matches, $flags, $offset ); if ( $result === false ) { throw new RegexException( sprintf( 'preg_match encountered an error with message "%s".', preg_last_error_msg() ) ); } return $result === 1; } public function replaceRegex( string $pattern, string $replacement, int $limit = - 1, int &$count = 0 ): USVStringInterface { $result = preg_replace( $pattern, $replacement, $this->string, $limit, $count ); if ( $result === null ) { throw new RegexException( sprintf( 'preg_replace encountered an error with message "%s".', preg_last_error_msg() ) ); } $copy = clone $this; $copy->string = $result; return $copy; } public function split( string $delimiter, ?int $limit = null ): StringListInterface { if ( $delimiter === '' ) { return new StringList(); } /** @var non-empty-list<string> $list */ $list = explode( $delimiter, $this->string, $limit ?? PHP_INT_MAX ); $temp = []; foreach ( $list as $string ) { $copy = clone $this; $copy->string = $string; $temp[] = $copy; } return new StringList( $temp ); } public function startsWith( string $string ): bool { return strncmp( $this->string, $string, strlen( $string ) ) === 0; } public function startsWithTwoAsciiHexDigits(): bool { if ( ! isset( $this->string[1] ) ) { return false; } return strspn( $this->string, CodePoint::HEX_DIGIT_MASK, 0, 2 ) === 2; } /** * @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter */ public function startsWithWindowsDriveLetter(): bool { return preg_match( '/^[A-Za-z][:|](?:$|[\/\\\?#])/u', $this->string ) === 1; } public function substr( int $start, ?int $length = null ): USVStringInterface { $copy = clone $this; $copy->string = mb_substr( $this->string, $start, $length, 'utf-8' ); return $copy; } public function toInt( int $base = 10 ): int { return intval( $this->string, $base ); } public function __toString(): string { return $this->string; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/CodePoint.php 0000777 00000003645 15251156632 0023400 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use function in_array; use function strpbrk; /** * A helper class for working with UTF-8 code points. * * @see https://infra.spec.whatwg.org/#code-points */ final class CodePoint { public const ASCII_ALPHA_MASK = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; public const ASCII_ALNUM_MASK = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; public const ASCII_DIGIT_MASK = '0123456789'; public const OCTAL_DIGIT_MASK = '01234567'; public const HEX_DIGIT_MASK = 'ABCDEFabcdef0123456789'; public const EOF = ''; /** * @codeCoverageIgnore */ private function __construct() { } /** * @see https://url.spec.whatwg.org/#url-code-points */ public static function isUrlCodePoint( string $codePoint ): bool { return ( strpbrk( $codePoint, self::ASCII_ALNUM_MASK ) === $codePoint || in_array( $codePoint, [ '!', '$', ':', ';', '=', '?', '@', '_', '~' ], true ) || ( $codePoint >= '&' && $codePoint <= '/' ) || ( $codePoint >= "\xA0" && $codePoint <= "\u{10FFFD}" ) ) // Not a surrogate && ( $codePoint < "\u{D800}" || $codePoint > "\u{DFFF}" ) // Not a non-character && ( $codePoint < "\u{FDD0}" || $codePoint > "\u{FDEF}" ) && ! in_array( $codePoint, [ "\u{FFFE}", "\u{FFFF}", "\u{1FFFE}", "\u{1FFFF}", "\u{2FFFE}", "\u{2FFFF}", "\u{3FFFE}", "\u{3FFFF}", "\u{4FFFE}", "\u{4FFFF}", "\u{5FFFE}", "\u{5FFFF}", "\u{6FFFE}", "\u{6FFFF}", "\u{7FFFE}", "\u{7FFFF}", "\u{8FFFE}", "\u{8FFFF}", "\u{9FFFE}", "\u{9FFFF}", "\u{AFFFE}", "\u{AFFFF}", "\u{BFFFE}", "\u{BFFFF}", "\u{CFFFE}", "\u{CFFFF}", "\u{DFFFE}", "\u{DFFFF}", "\u{EFFFE}", "\u{EFFFF}", "\u{FFFFE}", "\u{FFFFF}", "\u{10FFFE}", "\u{10FFFF}", ], true ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/StringIteratorInterface.php 0000777 00000001114 15251156632 0026302 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use SeekableIterator; /** * @extends SeekableIterator<int, string> */ interface StringIteratorInterface extends SeekableIterator { public function current(): string; public function key(): int; public function next(): void; public function peek( int $count = 1 ): string; public function prev(): void; /** * @param int $position The position to seek to relative to the current position. */ public function seek( $position ): void; public function rewind(): void; public function valid(): bool; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/StringBuffer.php 0000777 00000001206 15251156632 0024103 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use Rowbot\URL\Component\PathSegment; use Rowbot\URL\Component\Scheme; use function intval; use function preg_match; class StringBuffer extends AbstractStringBuffer implements StringBufferInterface { public function isWindowsDriveLetter(): bool { return preg_match( '/^[A-Za-z][:|]$/u', $this->string ) === 1; } public function toInt( int $base = 10 ): int { return intval( $this->string, $base ); } public function toPath(): PathSegment { return new PathSegment( $this->string ); } public function toScheme(): Scheme { return new Scheme( $this->string ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/PercentEncoder.php 0000777 00000016515 15251156632 0024414 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use Rowbot\URL\String\Exception\RegexException; use Rowbot\URL\Support\EncodingHelper; use function bin2hex; use function hexdec; use function mb_convert_encoding; use function mb_substitute_character; use function ord; use function preg_last_error_msg; use function preg_replace; use function preg_split; use function random_bytes; use function rawurlencode; use function sprintf; use function strcasecmp; use function strlen; use const PREG_SPLIT_DELIM_CAPTURE; final class PercentEncoder { /** * @var string|null */ private $randomBytes; public function __construct() { $this->randomBytes = null; } /** * While letting mbstring do the text encoding works in the majority of cases, it isn't perfect due to differences * in the encoders for mbstring and those defined by the WHATWG encoding standard. This is the best we can do * without actually implementing custom encoders from the WHATWG standard. Mbstring's lack of granular error * handling also makes this more complex than it would otherwise need to be. * * @see https://url.spec.whatwg.org/#string-percent-encode-after-encoding * * @param string $encoding Output encoding * @param string $input UTF-8 encoded string * @param EncodeSet::* $percentEncodeSet */ public function percentEncodeAfterEncoding( string $encoding, string $input, $percentEncodeSet, bool $spaceAsPlus = false ): string { // 1. Let encoder be the result of getting an encoder from encoding. $encoder = EncodingHelper::getOutputEncoding( $encoding ) ?? 'utf-8'; // 3. Let output be the empty string. $output = ''; // Avoid the costly encoding conversion and error handling in the common case of UTF-8 as it is only relevant // for non-UTF-8 strings. if ( strcasecmp( $encoder, 'utf-8' ) !== 0 ) { // Generate a random string to be used as a placeholder, only changing it if the given input contains the // same sequence of bytes. while ( $this->randomBytes === null || strpos( $input, $this->randomBytes ) !== false ) { $this->randomBytes = bin2hex( random_bytes( 16 ) ); } // Replace any existing numeric entities, that are in the hexadecimal format, so that we can distinguish // encoding errors below. These will be reinserted later. $replacedEntities = 0; $input = preg_replace( '/&#x([[:xdigit:]]{2,6};?)/', '__' . $this->randomBytes . '_${1}__', $input, - 1, $replacedEntities ); if ( $input === null ) { throw new RegexException( sprintf( 'preg_replace encountered an error with message "%s".', preg_last_error_msg() ) ); } // 5.1. Let encodeOutput be an empty I/O queue. // 5.2. Set potentialError to the result of running encode or fail with inputQueue, encoder, and // encodeOutput. $substituteChar = mb_substitute_character(); mb_substitute_character( 'entity' ); $encodeOutput = mb_convert_encoding( $input, $encoder, 'utf-8' ); mb_substitute_character( $substituteChar ); $chunks = preg_split( '/&#x([[:xdigit:]]{2,6});/', $encodeOutput, - 1, PREG_SPLIT_DELIM_CAPTURE ); if ( $chunks === false ) { throw new RegexException( sprintf( 'preg_split encountered an error with message "%s".', preg_last_error_msg() ) ); } // Replace the inserted placeholders of original numeric entities with the original text, so they get // percent encoded. if ( $replacedEntities > 0 ) { $chunks = preg_replace( "/__{$this->randomBytes}_([[:xdigit:]]+;?)__/", '&#x${1}', $chunks ); if ( $chunks === null ) { throw new RegexException( sprintf( 'preg_replace encountered an error with message "%s".', preg_last_error_msg() ) ); } } } $chunks = $chunks ?? [ $input ]; foreach ( $chunks as $key => $bytes ) { // 5.4. If potentialError is non-null, then append "%26%23", followed by the shortest sequence of ASCII // digits representing potentialError in base ten, followed by "%3B", to output. // // NOTE: This can happen when encoding is not UTF-8. // // Because we are splitting using the PREG_SPLIT_DELIM_CAPTURE_FLAG, odd keys contain the hex number of the // numeric entity inserted during encoding conversion should an invalid character be encountered. if ( $key % 2 === 1 ) { $output .= '%26%23' . hexdec( $bytes ) . '%3B'; continue; } // 5.3. For each byte of encodeOutput converted to a byte sequence: for ( $i = 0, $length = strlen( $bytes ); $i < $length; ++ $i ) { // 5.3.1. If spaceAsPlus is true and byte is 0x20 (SP), then append U+002B (+) to output and continue. if ( $spaceAsPlus && $bytes[ $i ] === "\x20" ) { $output .= '+'; continue; } // 5.3.2. Let isomorph be a code point whose value is byte’s value. $isomorph = ord( $bytes[ $i ] ); // 5.3.4. If isomorph is not in percentEncodeSet, then append isomorph to output. if ( ! $this->inEncodeSet( $isomorph, $percentEncodeSet ) ) { $output .= $bytes[ $i ]; continue; } // 5.3.5. Otherwise, percent-encode byte and append the result to output. $output .= rawurlencode( $bytes[ $i ] ); } } // 6. Return output. return $output; } /** * @see https://url.spec.whatwg.org/#c0-control-percent-encode-set * @see https://url.spec.whatwg.org/#fragment-percent-encode-set * @see https://url.spec.whatwg.org/#query-percent-encode-set * @see https://url.spec.whatwg.org/#special-query-percent-encode-set * @see https://url.spec.whatwg.org/#path-percent-encode-set * @see https://url.spec.whatwg.org/#userinfo-percent-encode-set * @see https://url.spec.whatwg.org/#component-percent-encode-set * * @param EncodeSet::* $percentEncodeSet */ private function inEncodeSet( int $codePoint, $percentEncodeSet ): bool { switch ( $percentEncodeSet ) { case EncodeSet::X_WWW_URLENCODED: switch ( $codePoint ) { case ord( '!' ): case ord( '\'' ): case ord( '(' ): case ord( ')' ): case ord( '~' ): return true; } // no break case EncodeSet::COMPONENT: switch ( $codePoint ) { case ord( '$' ): case ord( '%' ): case ord( '&' ): case ord( '+' ): case ord( ',' ): return true; } // no break case EncodeSet::USERINFO: switch ( $codePoint ) { case ord( '/' ): case ord( ':' ): case ord( ';' ): case ord( '=' ): case ord( '@' ): case ord( '[' ): case ord( '\\' ): case ord( ']' ): case ord( '^' ): case ord( '|' ): return true; } // no break case EncodeSet::PATH: switch ( $codePoint ) { case ord( '?' ): case ord( '`' ): case ord( '{' ): case ord( '}' ): return true; default: $percentEncodeSet = EncodeSet::QUERY; } } switch ( $percentEncodeSet ) { case EncodeSet::SPECIAL_QUERY: if ( $codePoint === ord( '\'' ) ) { return true; } // no break case EncodeSet::QUERY: switch ( $codePoint ) { case ord( "\x20" ): case ord( '"' ): case ord( '#' ): case ord( '<' ): case ord( '>' ): return true; } break; case EncodeSet::FRAGMENT: switch ( $codePoint ) { case ord( "\x20" ): case ord( '"' ): case ord( '<' ): case ord( '>' ): case ord( '`' ): return true; } } // C0_CONTROL return $codePoint < 0x20 || $codePoint > 0x7E; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/StringListInterface.php 0000777 00000001065 15251156632 0025431 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use Countable; use IteratorAggregate; /** * @extends IteratorAggregate<int, USVStringInterface> */ interface StringListInterface extends Countable, IteratorAggregate { /** * @return USVStringInterface */ public function first(); public function isEmpty(): bool; /** * @return USVStringInterface */ public function last(); /** * @return USVStringInterface|null */ public function pop(); /** * @param USVStringInterface $item */ public function push( $item ): void; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/USVStringInterface.php 0000777 00000002330 15251156632 0025167 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use IteratorAggregate; /** * @extends IteratorAggregate<int, string> */ interface USVStringInterface extends IteratorAggregate { public function append( string $string ): self; public function endsWith( string $string ): bool; public function getIterator(): StringIteratorInterface; public function isEmpty(): bool; public function length(): int; /** * @param array<int, string> $matches * @param int-mask<0, 256, 512> $flags */ public function matches( string $pattern, ?array &$matches = null, int $flags = 0, int $offset = 0 ): bool; public function replaceRegex( string $pattern, string $replacement, int $limit = - 1, int &$count = 0 ): self; public function split( string $delimiter, ?int $limit = null ): StringListInterface; public function startsWith( string $string ): bool; public function startsWithTwoAsciiHexDigits(): bool; /** * @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter */ public function startsWithWindowsDriveLetter(): bool; public function substr( int $start, ?int $length = null ): self; public function toInt( int $base = 10 ): int; public function __toString(): string; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/StringBufferInterface.php 0000777 00000001565 15251156632 0025734 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use IteratorAggregate; use Rowbot\URL\Component\PathSegment; use Rowbot\URL\Component\Scheme; /** * @extends IteratorAggregate<int, string> */ interface StringBufferInterface extends IteratorAggregate { public function append( string $string ): void; public function clear(): void; public function isEmpty(): bool; /** * @see https://url.spec.whatwg.org/#windows-drive-letter */ public function isWindowsDriveLetter(): bool; public function length(): int; public function prepend( string $string ): void; public function setCodePointAt( int $index, string $codePoint ): void; public function toInt( int $base = 10 ): int; public function toPath(): PathSegment; public function toScheme(): Scheme; public function toUtf8String(): USVStringInterface; public function __toString(): string; } DataLiberation/vendor-patched/rowbot/url/src/String/Exception/UndefinedIndexException.php 0000777 00000000252 15251156632 0030132 0 ustar 00 php-toolkit <?php declare( strict_types=1 ); namespace Rowbot\URL\String\Exception; use Rowbot\URL\Exception\URLException; class UndefinedIndexException extends URLException { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/Exception/EncodingException.php 0000777 00000000244 15251156632 0027047 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String\Exception; use Rowbot\URL\Exception\URLException; class EncodingException extends URLException { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/Exception/RegexException.php 0000777 00000000241 15251156632 0026370 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String\Exception; use Rowbot\URL\Exception\URLException; class RegexException extends URLException { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/StringList.php 0000777 00000000313 15251156632 0023603 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; /** * @extends AbstractStringList<USVStringInterface> */ class StringList extends AbstractStringList implements StringListInterface { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/AbstractStringBuffer.php 0000777 00000002462 15251156632 0025574 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use IteratorAggregate; use function mb_strlen; use function mb_substr; /** * @implements IteratorAggregate<int, string> */ abstract class AbstractStringBuffer implements IteratorAggregate { /** * @var string */ protected $string; public function __construct( string $string = '' ) { $this->string = $string; } public function append( string $string ): void { $this->string .= $string; } public function clear(): void { $this->string = ''; } public function getIterator(): StringIteratorInterface { return new Utf8StringIterator( $this->string ); } public function isEmpty(): bool { return $this->string === ''; } public function length(): int { return mb_strlen( $this->string, 'utf-8' ); } public function prepend( string $string ): void { $this->string = $string . $this->string; } public function setCodePointAt( int $index, string $codePoint ): void { $prefix = mb_substr( $this->string, 0, $index, 'utf-8' ); $suffix = mb_substr( $this->string, $index + 1, null, 'utf-8' ); $this->string = $prefix . $codePoint . $suffix; } public function toUtf8String(): USVStringInterface { return new Utf8String( $this->string ); } public function __toString(): string { return $this->string; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/Utf8StringIterator.php 0000777 00000002377 15251156632 0025244 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use function mb_str_split; class Utf8StringIterator implements StringIteratorInterface { /** * @var list<string> */ private $codePoints; /** * @var int */ private $cursor; public function __construct( string $string ) { $this->codePoints = mb_str_split( $string, 1, 'utf-8' ); $this->cursor = 0; } public function current(): string { return $this->codePoints[ $this->cursor ] ?? ''; } public function key(): int { return $this->cursor; } public function next(): void { ++ $this->cursor; } public function peek( int $count = 1 ): string { if ( $count === 1 ) { return $this->codePoints[ $this->cursor + 1 ] ?? ''; } $output = ''; $cursor = $this->cursor + 1; for ( $i = 0; $i < $count; ++ $i ) { if ( ! isset( $this->codePoints[ $cursor ] ) ) { break; } $output .= $this->codePoints[ $cursor ]; ++ $cursor; } return $output; } public function prev(): void { -- $this->cursor; } public function rewind(): void { $this->cursor = 0; } public function seek( $position ): void { $this->cursor += $position; } public function valid(): bool { return $this->cursor > - 1 && isset( $this->codePoints[ $this->cursor ] ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/AbstractStringList.php 0000777 00000002571 15251156632 0025277 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use Generator; use Rowbot\URL\String\Exception\UndefinedIndexException; use function array_pop; use function count; /** * @template T of object */ abstract class AbstractStringList { /** * @var array<int, T> */ protected $list; /** * @param array<int, T> $list */ public function __construct( array $list = [] ) { $this->list = $list; } public function count(): int { return count( $this->list ); } /** * @return T */ public function first() { if ( ! isset( $this->list[0] ) ) { throw new UndefinedIndexException(); } return $this->list[0]; } public function isEmpty(): bool { return $this->list === []; } /** * @return T */ public function last() { $last = count( $this->list ) - 1; if ( $last < 0 ) { throw new UndefinedIndexException(); } return $this->list[ $last ]; } /** * @return T|null */ public function pop() { return array_pop( $this->list ); } /** * @param T $string */ public function push( $string ): void { $this->list[] = $string; } /** * @return Generator<int, T> */ public function getIterator(): Generator { foreach ( $this->list as $string ) { yield $string; } } public function __clone() { $temp = []; foreach ( $this->list as $string ) { $temp[] = clone $string; } $this->list = $temp; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/Utf8String.php 0000777 00000002152 15251156632 0023521 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; use Rowbot\URL\String\Exception\EncodingException; use function mb_convert_encoding; use function mb_scrub; use function mb_substitute_character; use function sprintf; class Utf8String extends AbstractUSVString { public static function fromUnsafe( string $input ): self { return new self( self::scrub( $input ) ); } public static function scrub( string $input ): string { $substituteChar = mb_substitute_character(); mb_substitute_character( 0xFFFD ); $input = mb_scrub( $input, 'utf-8' ); mb_substitute_character( $substituteChar ); return $input; } public static function transcode( string $string, string $toEncoding, string $fromEncoding ): string { $substituteChar = mb_substitute_character(); mb_substitute_character( 0xFFFD ); $result = mb_convert_encoding( $string, $toEncoding, $fromEncoding ); mb_substitute_character( $substituteChar ); if ( $result === false ) { throw new EncodingException( sprintf( 'Transcoding from "%s" to "%s" failed.', $fromEncoding, $toEncoding ) ); } return $result; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/String/EncodeSet.php 0000777 00000000617 15251156632 0023361 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\String; class EncodeSet { public const C0_CONTROL = 'c0_control'; public const FRAGMENT = 'fragment'; public const QUERY = 'query'; public const SPECIAL_QUERY = 'special_query'; public const PATH = 'path'; public const USERINFO = 'userinfo'; public const COMPONENT = 'component'; public const X_WWW_URLENCODED = 'x_www_urlencoded'; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/APIParserResult.php 0000777 00000000613 15251156632 0023223 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; final class APIParserResult { /** * @readonly * @var URLRecord|null */ public $url; /** * @readonly * @var APIParserErrorType */ public $error; /** * @param APIParserErrorType::* $error */ public function __construct( ?URLRecord $urlRecord, $error ) { $this->url = $urlRecord; $this->error = $error; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/APIParserErrorType.php 0000777 00000000254 15251156632 0023701 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; class APIParserErrorType { public const NONE = 'none'; public const URL = 'url'; public const BASE = 'base'; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/URLRecord.php 0000777 00000013644 15251156632 0022047 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; use Rowbot\URL\Component\Host\HostInterface; use Rowbot\URL\Component\Host\NullHost; use Rowbot\URL\Component\OpaqueOrigin; use Rowbot\URL\Component\PathInterface; use Rowbot\URL\Component\PathList; use Rowbot\URL\Component\Scheme; use Rowbot\URL\Component\TupleOrigin; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; use Rowbot\URL\String\USVStringInterface; class URLRecord { /** * An ASCII string that identifies the type of URL. * @var Scheme */ public $scheme; /** * An ASCII string identifying a username. * @var string */ public $username; /** * An ASCII string identifying a password. * @var string */ public $password; /** * @var HostInterface */ public $host; /** * A 16-bit unsigned integer that identifies a networking port. * @var int|null */ public $port; /** * An ASCII string or a list of zero or more ASCII strings; Initially an empty list. * @var PathInterface */ public $path; /** * An ASCII string holding data. * @var string|null */ public $query; /** * An ASCII string holding data. * @var string|null */ public $fragment; public function __construct() { $this->scheme = new Scheme(); $this->username = ''; $this->password = ''; $this->host = new NullHost(); $this->port = null; $this->path = new PathList(); $this->query = null; $this->fragment = null; } /** * Whether or not a URL can have a username, password, or port set. * * @see https://url.spec.whatwg.org/#cannot-have-a-username-password-port */ public function cannotHaveUsernamePasswordPort(): bool { return $this->host->isNull() || $this->host->isEmpty() || $this->scheme->isFile(); } /** * Whether or not the URL has a username or password. * * @see https://url.spec.whatwg.org/#include-credentials */ public function includesCredentials(): bool { return $this->username !== '' || $this->password !== ''; } /** * Computes a URL's origin. * * @see https://url.spec.whatwg.org/#origin */ public function getOrigin(): Origin { if ( $this->scheme->isBlob() ) { $parser = new BasicURLParser(); $url = $parser->parse( $this->path->first()->toUtf8String() ); if ( $url === false ) { // Return a new opaque origin return new OpaqueOrigin(); } switch ( (string) $url->scheme ) { case 'https': case 'http': case 'file': return $url->getOrigin(); default: return new OpaqueOrigin(); } } if ( $this->scheme->isFile() ) { // Unfortunate as it is, this is left as an exercise to the // reader. When in doubt, return a new opaque origin. return new OpaqueOrigin(); } if ( $this->scheme->isSpecial() ) { // Return a tuple consiting of URL's scheme, host, port, and null return new TupleOrigin( (string) $this->scheme, $this->host, $this->port ); } // Return a new opaque origin. return new OpaqueOrigin(); } /** * Determines whether two URLs are equal to eachother. * * @see https://url.spec.whatwg.org/#concept-url-equals * * @param bool $excludeFragment (optional) determines whether a URL's fragment should be factored into equality. */ public function isEqual( self $otherUrl, bool $excludeFragment = false ): bool { return $this->serializeURL( $excludeFragment ) === $otherUrl->serializeURL( $excludeFragment ); } /** * Serializes a URL object. * * @see https://url.spec.whatwg.org/#concept-url-serializer * * @param bool $excludeFragment (optional) When specified it will exclude the URL's fragment from being serialized. */ public function serializeURL( bool $excludeFragment = false ): string { $output = $this->scheme . ':'; $isNullHost = $this->host->isNull(); if ( ! $isNullHost ) { $output .= '//'; if ( $this->username !== '' || $this->password !== '' ) { $output .= $this->username; if ( $this->password !== '' ) { $output .= ':' . $this->password; } $output .= '@'; } $output .= $this->host->getSerializer()->toFormattedString(); if ( $this->port !== null ) { $output .= ':' . $this->port; } } // 3. If url’s host is null, url does not have an opaque path, url’s path’s size is greater than 1, and url’s // path[0] is the empty string, then append U+002F (/) followed by U+002E (.) to output. if ( $isNullHost && ! $this->path->isOpaque() && $this->path->count() > 1 && $this->path->first()->isEmpty() ) { // NOTE: This prevents web+demo:/.//not-a-host/ or web+demo:/path/..//not-a-host/, when parsed and then // serialized, from ending up as web+demo://not-a-host/ (they end up as web+demo:/.//not-a-host/) $output .= '/.'; } // 4. Append the result of URL path serializing url to output. $output .= $this->path; // 5. If url’s query is non-null, append U+003F (?), followed by url’s query, to output. if ( $this->query !== null ) { $output .= '?' . $this->query; } // 6. If exclude fragment is false and url’s fragment is non-null, then append U+0023 (#), followed by url’s // fragment, to output. if ( ! $excludeFragment && $this->fragment !== null ) { $output .= '#' . $this->fragment; } // 7. Return output. return $output; } /** * @see https://url.spec.whatwg.org/#set-the-password */ public function setPassword( USVStringInterface $input ): void { $percentEncoder = new PercentEncoder(); $this->password = $percentEncoder->percentEncodeAfterEncoding( 'utf-8', (string) $input, EncodeSet::USERINFO ); } /** * @see https://url.spec.whatwg.org/#set-the-username */ public function setUsername( USVStringInterface $input ): void { $percentEncoder = new PercentEncoder(); $this->username = $percentEncoder->percentEncodeAfterEncoding( 'utf-8', (string) $input, EncodeSet::USERINFO ); } public function __clone() { $this->scheme = clone $this->scheme; $this->host = clone $this->host; $this->path = clone $this->path; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/URL.php 0000777 00000024722 15251156632 0020707 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; use InvalidArgumentException; use JsonSerializable; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; use Rowbot\URL\Component\PathList; use Rowbot\URL\Component\QueryList; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\String\Utf8String; use Stringable; use Throwable; use function assert; use function json_encode; use function sprintf; use const JSON_UNESCAPED_SLASHES; /** * Represents a URL that can be manipulated. * * @see https://url.spec.whatwg.org/#api * @see https://developer.mozilla.org/en-US/docs/Web/API/URL * * @property string $href * @property string $origin * @property string $protocol * @property string $username * @property string $password * @property string $host * @property string $hostname * @property string $port * @property string $pathname * @property string $search * @property URLSearchParams $searchParams * @property string $hash */ class URL implements JsonSerializable, LoggerAwareInterface { use LoggerAwareTrait; /** * @var URLSearchParams */ private $queryObject; /** * @var URLRecord */ private $url; /** * @param array{logger?: LoggerInterface|null} $options * * @param string|Stringable $url * @param string|Stringable|null $base * * @throws TypeError */ public function __construct( $url, $base = null, array $options = [] ) { $this->logger = null; if ( isset( $options['logger'] ) ) { if ( ! $options['logger'] instanceof LoggerInterface ) { throw new TypeError( sprintf( 'The passed logger must be null or an instance of %s', LoggerInterface::class ) ); } $this->logger = $options['logger']; } // 1. Let parsedURL be the result of running the API URL parser on url with base, if given. $parsedURL = self::parseURL( $url, $base, $this->logger ); if ( $parsedURL->error === APIParserErrorType::NONE ) { // 3. Initialize this with parsedURL. assert( $parsedURL->url !== null ); self::initializeURL( $this, $parsedURL->url ); return; } switch ( $parsedURL->error ) { case APIParserErrorType::BASE: $message = 'Invalid base URL'; break; case APIParserErrorType::URL: $message = 'Invalid URL'; break; } throw new TypeError( $message ); } /** * @see https://url.spec.whatwg.org/#dom-url-parse * * @param string|Stringable $url * @param string|Stringable|null $base */ public static function parse( $url, $base = null ): ?self { try { return new self( $url, $base ); } catch ( Throwable $exception ) { return null; } } /** * @see https://url.spec.whatwg.org/#dom-url-canparse * * @param string|Stringable $url * @param string|Stringable|null $base */ public static function canParse( $url, $base = null ): bool { $parsedURL = self::parseURL( $url, $base ); return $parsedURL->error === APIParserErrorType::NONE; } public function toString(): string { return $this->url->serializeURL(); } /** * Returns a JSON encoded string without escaping forward slashes. If you * need forward slashes to be escaped, pass the URL object to json_encode() * instead of calling this method. * * @see https://url.spec.whatwg.org/#dom-url-tojson */ public function toJSON(): string { // Use JSON_UNESCAPED_SLASHES here since JavaScript's JSON.stringify() // method does not escape forward slashes by default. return json_encode( $this->url->serializeURL(), JSON_UNESCAPED_SLASHES ); } /** * Returns the serialized URL for consumption by json_encode(). To match * JavaScript's behavior, you should pass the JSON_UNESCAPED_SLASHES option * to json_encode(). */ public function jsonSerialize(): string { return $this->url->serializeURL(); } /** * @see https://url.spec.whatwg.org/#api-url-parser * * @param string|Stringable $url * @param string|Stringable|null $base */ private static function parseURL( $url, $base = null, ?LoggerInterface $logger = null ): APIParserResult { // 1. Let parsedBase be null. $parsedBase = null; $parser = new BasicURLParser( $logger ); // 2. If base is non-null: if ( $base !== null ) { // 2.1. Set parsedBase to the result of running the basic URL parser on base. $stringBase = (string) $base; $parsedBase = $parser->parse( Utf8String::fromUnsafe( $stringBase ) ); // 2.2. If parsedBase is failure, then return failure. if ( $parsedBase === false ) { return new APIParserResult( null, APIParserErrorType::BASE ); } } // 3. Return the result of running the basic URL parser on url with parsedBase. $stringURL = (string) $url; $parsedURL = $parser->parse( Utf8String::fromUnsafe( $stringURL ), $parsedBase ); if ( $parsedURL === false ) { return new APIParserResult( null, APIParserErrorType::URL ); } return new APIParserResult( $parsedURL, APIParserErrorType::NONE ); } /** * @see https://url.spec.whatwg.org/#url-initialize */ private static function initializeURL( self $url, URLRecord $urlRecord ): void { // 1. Let query be urlRecord’s query, if that is non-null; otherwise the empty string. $query = $urlRecord->query ?? ''; // 2. Set url’s URL to urlRecord. $url->url = $urlRecord; // 3. Set url’s query object to a new URLSearchParams object. $url->queryObject = new URLSearchParams(); // 4. Initialize url’s query object with query. $url->queryObject->setList( QueryList::fromString( $query ) ); // 5. Set url’s query object’s URL object to url. $url->queryObject->setUrl( $urlRecord ); } public function __clone() { $this->url = clone $this->url; $this->queryObject = clone $this->queryObject; $this->queryObject->setUrl( $this->url ); } /** * @return string|URLSearchParams * @throws InvalidArgumentException When an invalid $name value is passed. */ public function __get( string $name ) { if ( $name === 'hash' ) { if ( $this->url->fragment === null || $this->url->fragment === '' ) { return ''; } return '#' . $this->url->fragment; } if ( $name === 'host' ) { if ( $this->url->host->isNull() ) { return ''; } $serializer = $this->url->host->getSerializer(); if ( $this->url->port === null ) { return $serializer->toFormattedString(); } return $serializer->toFormattedString() . ':' . $this->url->port; } if ( $name === 'hostname' ) { if ( $this->url->host->isNull() ) { return ''; } return $this->url->host->getSerializer()->toFormattedString(); } if ( $name === 'href' ) { return $this->url->serializeURL(); } if ( $name === 'origin' ) { return (string) $this->url->getOrigin(); } if ( $name === 'password' ) { return $this->url->password; } if ( $name === 'pathname' ) { return (string) $this->url->path; } if ( $name === 'port' ) { if ( $this->url->port === null ) { return ''; } return (string) $this->url->port; } if ( $name === 'protocol' ) { return $this->url->scheme . ':'; } if ( $name === 'search' ) { if ( $this->url->query === null || $this->url->query === '' ) { return ''; } return '?' . $this->url->query; } if ( $name === 'searchParams' ) { return $this->queryObject; } if ( $name === 'username' ) { return $this->url->username; } throw new InvalidArgumentException( sprintf( '"%s" is not a valid property.', $name ) ); } /** * @throws InvalidArgumentException When an invalid $name or $value value is passed. * @throws TypeError Only when trying to set URL::$searchParams */ public function __set( string $name, string $value ): void { if ( $name === 'searchParams' ) { throw new TypeError( 'Cannot redefine the searchParams property.' ); } $input = Utf8String::fromUnsafe( $value ); $parser = new BasicURLParser( $this->logger ); if ( $name === 'hash' ) { if ( $input->isEmpty() ) { $this->url->fragment = null; $this->url->path->potentiallyStripTrailingSpaces( $this->url ); // Terminate these steps return; } if ( $input->startsWith( '#' ) ) { $input = $input->substr( 1 ); } $this->url->fragment = ''; $parser->parse( $input, null, null, $this->url, ParserState::FRAGMENT ); } elseif ( $name === 'host' ) { if ( $this->url->path->isOpaque() ) { // Terminate these steps return; } $parser->parse( $input, null, null, $this->url, ParserState::HOST ); } elseif ( $name === 'hostname' ) { if ( $this->url->path->isOpaque() ) { // Terminate these steps return; } $parser->parse( $input, null, null, $this->url, ParserState::HOSTNAME ); } elseif ( $name === 'href' ) { $parsedURL = $parser->parse( $input ); if ( $parsedURL === false ) { throw new TypeError( sprintf( '"%s" is not a valid URL.', $value ) ); } $this->url = $parsedURL; $this->queryObject->setUrl( $this->url ); if ( $this->url->query === null ) { return; } $this->queryObject->setList( QueryList::fromString( $this->url->query ) ); } elseif ( $name === 'password' ) { if ( $this->url->cannotHaveUsernamePasswordPort() ) { return; } $this->url->setPassword( $input ); } elseif ( $name === 'pathname' ) { if ( $this->url->path->isOpaque() ) { // Terminate these steps return; } $this->url->path = new PathList(); $parser->parse( $input, null, null, $this->url, ParserState::PATH_START ); } elseif ( $name === 'port' ) { if ( $this->url->cannotHaveUsernamePasswordPort() ) { return; } if ( $value === '' ) { $this->url->port = null; return; } $parser->parse( $input, null, null, $this->url, ParserState::PORT ); } elseif ( $name === 'protocol' ) { $parser->parse( $input->append( ':' ), null, null, $this->url, ParserState::SCHEME_START ); } elseif ( $name === 'search' ) { if ( $value === '' ) { $this->url->query = null; $this->queryObject->setList( new QueryList() ); $this->url->path->potentiallyStripTrailingSpaces( $this->url ); return; } if ( $input->startsWith( '?' ) ) { $input = $input->substr( 1 ); } $this->url->query = ''; $parser->parse( $input, null, null, $this->url, ParserState::QUERY ); $this->queryObject->setList( QueryList::fromString( (string) $input ) ); } elseif ( $name === 'username' ) { if ( $this->url->cannotHaveUsernamePasswordPort() ) { return; } $this->url->setUsername( $input ); } else { throw new InvalidArgumentException( sprintf( '"%s" is not a valid property.', $name ) ); } } public function __toString(): string { return $this->url->serializeURL(); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Origin.php 0000777 00000001354 15251156632 0021470 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; /** * @see https://html.spec.whatwg.org/multipage/browsers.html#origin */ interface Origin { /** * @see https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-effective-domain */ public function getEffectiveDomain(): ?string; public function isOpaque(): bool; /** * Checks if two origins are the same. * * @see https://html.spec.whatwg.org/multipage/browsers.html#same-origin */ public function isSameOrigin( self $other ): bool; /** * Checks if the origin is both the same origin and the same domain. * * @see https://html.spec.whatwg.org/multipage/browsers.html#same-origin-domain */ public function isSameOriginDomain( self $other ): bool; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/URLSearchParams.php 0000777 00000023647 15251156632 0023206 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; use Countable; use Iterator; use ReflectionObject; use ReflectionProperty; use Rowbot\URL\Component\QueryList; use Rowbot\URL\Exception\TypeError; use Rowbot\URL\String\Utf8String; use Stringable; use Traversable; use function array_column; use function count; use function func_num_args; use function get_debug_type; use function is_iterable; use function is_object; use function is_scalar; use function sprintf; use function substr; /** * An object containing a list of all URL query parameters. This allows you to manipulate a URL's * query string in a granular manner. * * @see https://url.spec.whatwg.org/#urlsearchparams * @see https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams * * @implements Iterator<int, array{0: string, 1: string}|null> * * @property-read int<0, max> $size */ class URLSearchParams implements Countable, Iterator { /** * @var 0|positive-int */ private $cursor; /** * @var QueryList */ private $list; /** * @var URLRecord|null */ private $url; /** * @see https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams * * @param iterable<int|string, array<int|string, scalar|Stringable>|(Traversable<int|string, scalar|Stringable>&Countable)>|object|string|Stringable $init */ public function __construct( $init = '' ) { $this->list = new QueryList(); $this->url = null; $this->cursor = 0; if ( func_num_args() < 1 ) { return; } if ( $this->isStringable( $init ) ) { $init = Utf8String::scrub( (string) $init ); if ( $init !== '' && $init[0] === '?' ) { $init = substr( $init, 1 ); } $this->list = QueryList::fromString( $init ); return; } if ( is_iterable( $init ) ) { $this->initIterator( $init ); return; } if ( is_object( $init ) ) { $this->initObject( $init ); return; } } /** * Appends a new name-value pair to the end of the query string. * * @see https://url.spec.whatwg.org/#dom-urlsearchparams-append * * @param string $name The name of the key in the pair. * @param string $value The value assigned to the key. */ public function append( string $name, string $value ): void { $this->list->append( Utf8String::scrub( $name ), Utf8String::scrub( $value ) ); $this->update(); } public function count(): int { return $this->list->count(); } /** * @return array{0: string, 1: string}|null */ public function current(): ?array { $tuple = $this->list->getTupleAt( $this->cursor ); if ( $tuple === null ) { return null; } return [ $tuple['name'], $tuple['value'] ]; } /** * Deletes all occurances of pairs with the specified key name. * * @see https://url.spec.whatwg.org/#dom-urlsearchparams-delete * * @param string $name The name of the key to delete. */ public function delete( string $name, ?string $value = null ): void { if ( $value !== null ) { $value = Utf8String::scrub( $value ); } $this->list->remove( Utf8String::scrub( $name ), $value ); $this->update(); } /** * Get the value of the first name-value pair with the specified key name. * * @see https://url.spec.whatwg.org/#dom-urlsearchparams-get * * @param string $name The name of the key whose value you want to retrive. * * @return string|null The value of the specified key. */ public function get( string $name ): ?string { return $this->list->first( Utf8String::scrub( $name ) ); } /** * Gets all name-value pairs that has the specified key name. * * @see https://url.spec.whatwg.org/#dom-urlsearchparams-getall * * @param string $name The name of the key whose values you want to retrieve. * * @return array<int, string> An array containing all the values of the specified key. */ public function getAll( string $name ): array { $name = Utf8String::scrub( $name ); return array_column( $this->list->filter( static function ( array $pair ) use ( $name ): bool { return $pair['name'] === $name; } ), 'value' ); } /** * Indicates whether or not a query string contains any keys with the specified key name. * * @see https://url.spec.whatwg.org/#dom-urlsearchparams-has * * @param string $name The key name you want to test if it exists. * * @return bool Returns true if the key exits, otherwise false. */ public function has( string $name, ?string $value = null ): bool { if ( $value !== null ) { $value = Utf8String::scrub( $value ); } return $this->list->contains( Utf8String::scrub( $name ), $value ); } public function key(): int { return $this->cursor; } public function next(): void { ++ $this->cursor; } public function rewind(): void { $this->cursor = 0; } /** * Sets the value of the specified key name. If multiple pairs exist with the same key name it * will set the value for the first occurance of the key in the query string and all other * occurances will be removed from the query string. If the key does not already exist in the * query string, it will be added to the end of the query string. * * @see https://url.spec.whatwg.org/#dom-urlsearchparams-set * * @param string $name The name of the key you want to modify the value of. * @param string $value The value you want to associate with the key name. */ public function set( string $name, string $value ): void { $name = Utf8String::scrub( $name ); $value = Utf8String::scrub( $value ); if ( $this->list->contains( $name ) ) { $this->list->set( $name, $value ); } else { $this->list->append( $name, $value ); } $this->update(); } /** * @internal */ public function setList( QueryList $list ): void { $this->list = $list; } /** * Sets the associated url record. * * @internal */ public function setUrl( URLRecord $url ): void { $this->url = $url; } /** * Sorts the list of search params by their names by comparing their code unit values, * preserving the relative order between pairs with the same name. * * @see https://url.spec.whatwg.org/#dom-urlsearchparams-sort */ public function sort(): void { $this->list->sort(); $this->update(); } public function toString(): string { return $this->list->toUrlencodedString(); } /** * @phpstan-assert-if-true array{0: string, 1: string} $this->current() */ public function valid(): bool { return $this->list->getTupleAt( $this->cursor ) !== null; } /** * Set's the associated URL object's query to the serialization of URLSearchParams. * * @see https://url.spec.whatwg.org/#concept-urlsearchparams-update * * @internal */ protected function update(): void { if ( $this->url === null ) { return; } $query = $this->list->toUrlencodedString(); if ( $query === '' ) { $query = null; } $this->url->query = $query; if ( $query === null ) { $this->url->path->potentiallyStripTrailingSpaces( $this->url ); } } /** * @param iterable<int|string, iterable<int|string, scalar|Stringable>&Countable> $input * * @throws TypeError */ private function initIterator( iterable $input ): void { foreach ( $input as $key => $pair ) { // Try to catch cases where $pair isn't countable or $pair is // countable, but isn't a valid sequence, such as: // // class CountableClass implements \Countable // { // public function count() // { // return 2; // } // } // // $s = new \Rowbot\URL\URLSearchParams([new CountableClass()]); // // or: // // $a = new \ArrayObject(['x', 'y']); // $s = new \Rowbot\URL\URLSearchParams($a); // // while still allowing things like: // // $a = new \ArrayObject(new \ArrayObject(['x', 'y'])); // $s = new \Rowbot\URL\URLSearchParams($a);' if ( ! ( is_array( $pair ) || $pair instanceof Countable ) || ! is_iterable( $pair ) ) { throw new TypeError( sprintf( 'Expected a valid sequence such as an Array or iterable Object that implements ' . 'the \\Countable interface. %s found instead.', get_debug_type( $pair ) ) ); } if ( count( $pair ) !== 2 ) { throw new TypeError( sprintf( 'Expected sequence with exactly 2 items. Sequence contained %d items.', count( $pair ) ) ); } [ $name, $value ] = $pair; if ( ! $this->isStringable( $name ) ) { throw new TypeError( sprintf( 'The name of the name-value pair at index "%s" must be a scalar value or stringable.', $key ) ); } if ( ! $this->isStringable( $value ) ) { throw new TypeError( sprintf( 'The value of the name-value pair at index "%s" must be a scalar value or stringable.', $key ) ); } $this->list->append( Utf8String::scrub( (string) $name ), Utf8String::scrub( (string) $value ) ); } } private function initObject( object $input ): void { $reflection = new ReflectionObject( $input ); foreach ( $reflection->getProperties( ReflectionProperty::IS_PUBLIC ) as $property ) { $value = $property->getValue( $input ); if ( ! $this->isStringable( $value ) ) { throw new TypeError( sprintf( 'The value of property "%s" must be a scalar value or \\Stringable.', $reflection->getName() ) ); } $this->list->append( Utf8String::scrub( $property->getName() ), Utf8String::scrub( (string) $value ) ); } } /** * @phpstan-assert-if-true scalar|Stringable $value * * @param mixed $value */ private function isStringable( $value ): bool { return is_object( $value ) && method_exists( $value, '__toString' ) || is_scalar( $value ); } public function __clone() { $this->list = clone $this->list; // Null out the url in-case someone tries cloning the object returned by // the URL::searchParams attribute. $this->url = null; } /** * @return mixed */ public function __get( string $name ) { switch ( $name ) { case 'size': return $this->list->count(); default: return null; } } /** * Returns all name-value pairs stringified in the correct order. */ public function __toString(): string { return $this->list->toUrlencodedString(); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/ParserState.php 0000777 00000006230 15251156632 0022474 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; use Rowbot\URL\State\AuthorityState; use Rowbot\URL\State\FileHostState; use Rowbot\URL\State\FileSlashState; use Rowbot\URL\State\FileState; use Rowbot\URL\State\FragmentState; use Rowbot\URL\State\HostnameState; use Rowbot\URL\State\HostState; use Rowbot\URL\State\NoSchemeState; use Rowbot\URL\State\OpaquePathState; use Rowbot\URL\State\PathOrAuthorityState; use Rowbot\URL\State\PathStartState; use Rowbot\URL\State\PathState; use Rowbot\URL\State\PortState; use Rowbot\URL\State\QueryState; use Rowbot\URL\State\RelativeSlashState; use Rowbot\URL\State\RelativeState; use Rowbot\URL\State\SchemeStartState; use Rowbot\URL\State\SchemeState; use Rowbot\URL\State\SpecialAuthorityIgnoreSlashesState; use Rowbot\URL\State\SpecialAuthoritySlashesState; use Rowbot\URL\State\SpecialRelativeOrAuthorityState; use Rowbot\URL\State\State; class ParserState { public const SCHEME_START = 'scheme_start'; public const SCHEME = 'scheme'; public const NO_SCHEME = 'no_scheme'; public const SPECIAL_RELATIVE_OR_AUTHORITY = 'special_relative_or_authority'; public const PATH_OR_AUTHORITY = 'path_or_authority'; public const RELATIVE = 'relative'; public const RELATIVE_SLASH = 'relative_slash'; public const SPECIAL_AUTHORITY_SLASHES = 'special_authority_slashes'; public const SPECIAL_AUTHORITY_IGNORE_SLASHES = 'special_authority_ignore_slashes'; public const AUTHORITY = 'authority'; public const HOST = 'host'; public const HOSTNAME = 'hostname'; public const PORT = 'port'; public const FILE = 'file'; public const FILE_SLASH = 'file_slash'; public const FILE_HOST = 'file_host'; public const PATH_START = 'path_start'; public const PATH = 'path'; public const OPAQUE_PATH = 'opaque_path'; public const QUERY = 'query'; public const FRAGMENT = 'fragment'; public static function createHandlerFor( $state ): State { switch ( $state ) { case self::SCHEME_START: return new SchemeStartState(); case self::SCHEME: return new SchemeState(); case self::NO_SCHEME: return new NoSchemeState(); case self::SPECIAL_RELATIVE_OR_AUTHORITY: return new SpecialRelativeOrAuthorityState(); case self::PATH_OR_AUTHORITY: return new PathOrAuthorityState(); case self::RELATIVE: return new RelativeState(); case self::RELATIVE_SLASH: return new RelativeSlashState(); case self::SPECIAL_AUTHORITY_SLASHES: return new SpecialAuthoritySlashesState(); case self::SPECIAL_AUTHORITY_IGNORE_SLASHES: return new SpecialAuthorityIgnoreSlashesState(); case self::AUTHORITY: return new AuthorityState(); case self::HOST: return new HostState(); case self::HOSTNAME: return new HostnameState(); case self::PORT: return new PortState(); case self::FILE: return new FileState(); case self::FILE_SLASH: return new FileSlashState(); case self::FILE_HOST: return new FileHostState(); case self::PATH_START: return new PathStartState(); case self::PATH: return new PathState(); case self::OPAQUE_PATH: return new OpaquePathState(); case self::QUERY: return new QueryState(); case self::FRAGMENT: return new FragmentState(); } } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Exception/UnsupportedOperationException.php0000777 00000000201 15251156632 0030255 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Exception; class UnsupportedOperationException extends URLException { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Exception/URLException.php 0000777 00000000175 15251156632 0024520 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Exception; use Exception; class URLException extends Exception { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Exception/TypeError.php 0000777 00000000155 15251156632 0024130 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Exception; class TypeError extends URLException { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Support/EncodingHelper.php 0000777 00000001621 15251156632 0024600 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Support; use function strtolower; class EncodingHelper { /** * @see https://encoding.spec.whatwg.org/#get-an-output-encoding */ public static function getOutputEncoding( ?string $encoding ): ?string { if ( $encoding === null ) { return null; } $encoding = strtolower( $encoding ); if ( $encoding === 'replacement' || $encoding === 'utf-16' || $encoding === 'utf-16le' || $encoding === 'utf-16be' ) { return 'utf-8'; } // We could validate the encoding against the list of valid encodings found at // {@link https://encoding.spec.whatwg.org/#concept-encoding-get} and the list of supported // encodings on the PHP installation. However, I'm not convinced it would be worth the // effort since overriding the encoding is a feature designed only for use in the HTML // specification. return $encoding; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/ParserContext.php 0000777 00000004653 15251156632 0023047 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; use Psr\Log\LoggerInterface; use Rowbot\URL\String\StringBufferInterface; use Rowbot\URL\String\StringIteratorInterface; use Rowbot\URL\String\USVStringInterface; use Rowbot\URL\Support\EncodingHelper; final class ParserContext { /** * @readonly * @var URLRecord|null */ public $base; /** * @readonly * @var StringBufferInterface */ public $buffer; /** * @readonly * @var USVStringInterface */ public $input; /** * @readonly * @var StringIteratorInterface */ public $iter; /** * @var ParserState */ public $state; /** * @readonly * @var URLRecord */ public $url; /** * @var LoggerInterface|null */ public $logger; /** * @var string */ private $encoding; /** * @var ParserState|null */ private $stateOverride; /** * @param ?ParserState::* $stateOverride */ public function __construct( USVStringInterface $input, StringIteratorInterface $iter, StringBufferInterface $buffer, URLRecord $url, ?URLRecord $base, ?string $stateOverride, ?string $encodingOverride, ?LoggerInterface $logger ) { $this->input = $input; $this->iter = $iter; $this->buffer = $buffer; $this->url = $url; $this->base = $base; $this->encoding = EncodingHelper::getOutputEncoding( $encodingOverride ) ?? 'utf-8'; $this->state = $stateOverride ?? ParserState::SCHEME_START; $this->stateOverride = $stateOverride; $this->logger = $logger; } /** * Returns the output encoding of the URL string. */ public function getOutputEncoding(): string { return $this->encoding; } /** * Returns whether the parser's starting state was overriden. This occurs when using one of the * URL object's setters. */ public function isStateOverridden(): bool { return $this->stateOverride !== null; } /** * Returns whether the parser's starting state was the Hostname state. */ public function isOverrideStateHostname(): bool { return $this->stateOverride === ParserState::HOSTNAME; } /** * Changes the encoding of the resulting URL string. This only affects the query string portion * and it is only for use in the HTML specification. This should never be changed from the * default UTF-8 encoding. */ public function setOutputEncoding( string $encoding ): void { $this->encoding = EncodingHelper::getOutputEncoding( $encoding ) ?? 'utf-8'; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/BasicURLParser.php 0000777 00000012012 15251156632 0023013 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; use Rowbot\URL\State\StatusCode; use Rowbot\URL\String\StringBuffer; use Rowbot\URL\String\USVStringInterface; use function mb_strlen; use function strlen; use function substr; use const PREG_OFFSET_CAPTURE; class BasicURLParser implements LoggerAwareInterface { use LoggerAwareTrait; public function __construct( ?LoggerInterface $logger = null ) { $this->logger = $logger; } /** * The parser can parse both absolute and relative URLs. If a relative URL is given, a base URL must also be given * so that an absolute URL can be resolved. It can also parse individual parts of a URL when the default starting * state is overridden, however, a previously parsed URL record object must be provided in this case. * * @see https://url.spec.whatwg.org/#concept-basic-url-parser * * @param USVStringInterface $input A UTF-8 encoded string consisting of only scalar * values, excluding surrogates. * @param URLRecord|null $base (optional) This represents the base URL, which in * most cases, is the document's URL, it may also be * a node's base URI or whatever base URL you wish to * resolve relative URLs against. Default is null. * @param string|null $encodingOverride (optional) Overrides the default ouput encoding, * which is UTF-8. This option exists solely for the * use of the HTML specification and should never be * changed. * @param URLRecord|null $url (optional) This represents an existing URL record * object that should be modified based on the input * URL and optional base URL. Default is null. * @param ParserState|null $stateOverride (optional) An object implementing the * \Rowbot\URL\ParserState interface that overrides * the default start state, which is the Scheme Start * State. Default is null. * * @return URLRecord|false * * @param ?ParserState::* $stateOverride */ public function parse( USVStringInterface $input, ?URLRecord $base = null, ?string $encodingOverride = null, ?URLRecord $url = null, ?string $stateOverride = null ) { $count = 0; if ( $url === null ) { $url = new URLRecord(); $originalInput = $input; $input = $input->replaceRegex( '/^[\x00-\x20]+|[\x00-\x20]+$/u', '', - 1, $count ); if ( $count !== 0 ) { // Validation error. ( $nullsafeVariable1 = $this->logger ) ? $nullsafeVariable1->notice( 'invalid-URL-unit', [ 'input' => (string) $originalInput, 'column_range' => ( static function () use ( $originalInput ): array { $originalInput->matches( '/^[\x00-\x20]+|[\x00-\x20]+$/u', $matches, PREG_OFFSET_CAPTURE ); if ( $matches[0][1] === 0 ) { return [ 1, strlen( $matches[0][0] ) ]; } return [ $originalInput->length() - strlen( $matches[0][0] ) + 1, $originalInput->length() ]; } )(), ] ) : null; } } $originalInput = $input; $input = $input->replaceRegex( '/[\x09\x0A\x0D]+/u', '', - 1, $count ); if ( $count !== 0 ) { // Validation error. ( $nullsafeVariable2 = $this->logger ) ? $nullsafeVariable2->notice( 'invalid-URL-unit', [ 'input' => (string) $originalInput, 'column_range' => ( static function () use ( $originalInput ): array { $originalInput->matches( '/[\x09\x0A\x0D]+/u', $matches, PREG_OFFSET_CAPTURE ); $start = mb_strlen( substr( (string) $originalInput, 0, $matches[0][1] ), 'utf-8' ); return [ $start + 1, $start + strlen( $matches[0][0] ) ]; } )(), ] ) : null; } $iter = $input->getIterator(); $iter->rewind(); // length + imaginary eof character $length = $input->length() + 1; $buffer = new StringBuffer(); $context = new ParserContext( $input, $iter, $buffer, $url, $base, $stateOverride, $encodingOverride, $this->logger ); do { $handler = ParserState::createHandlerFor( $context->state ); $status = $handler->handle( $context, $iter->current() ); if ( $status === StatusCode::CONTINUE ) { $status = StatusCode::OK; continue; } $iter->next(); } while ( $status === StatusCode::OK && $iter->key() < $length ); switch ( $status ) { case StatusCode::FAILURE: return false; default: return $url; } } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/RelativeSlashState.php 0000777 00000003303 15251156632 0025064 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use function assert; /** * @see https://url.spec.whatwg.org/#relative-slash-state */ class RelativeSlashState implements State { public function handle( ParserContext $context, string $codePoint ) { assert( $context->base !== null ); // 1. If url is special and c is U+002F (/) or U+005C (\), then: if ( $context->url->scheme->isSpecial() && ( $codePoint === '/' || $codePoint === '\\' ) ) { // 1.1. If c is U+005C (\), validation error. if ( $codePoint === '\\' ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-reverse-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } // 1.2. Set state to special authority ignore slashes state. $context->state = ParserState::SPECIAL_AUTHORITY_IGNORE_SLASHES; return StatusCode::OK; } // 2. Otherwise, if c is U+002F (/), then set state to authority state. if ( $codePoint === '/' ) { $context->state = ParserState::AUTHORITY; return StatusCode::OK; } // 3. Otherwise, set url’s username to base’s username, url’s password to base’s password, url’s host to base’s // host, url’s port to base’s port, state to path state, and then, decrease pointer by 1. $context->url->username = $context->base->username; $context->url->password = $context->base->password; $context->url->host = clone $context->base->host; $context->url->port = $context->base->port; $context->state = ParserState::PATH; $context->iter->prev(); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/SpecialRelativeOrAuthorityState.php 0000777 00000002076 15251156632 0027612 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; /** * @see https://url.spec.whatwg.org/#special-relative-or-authority-state */ class SpecialRelativeOrAuthorityState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If c is U+002F (/) and remaining starts with U+002F (/), then set state to special authority ignore // slashes state and increase pointer by 1. if ( $codePoint === '/' && $context->iter->peek() === '/' ) { $context->state = ParserState::SPECIAL_AUTHORITY_IGNORE_SLASHES; $context->iter->next(); return StatusCode::OK; } // 2. Otherwise, validation error, set state to relative state and decrease pointer by 1. $context->state = ParserState::RELATIVE; ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'special-scheme-missing-following-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; $context->iter->prev(); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/PortState.php 0000777 00000005455 15251156632 0023254 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; use function strpbrk; /** * @see https://url.spec.whatwg.org/#port-state */ class PortState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If c is an ASCII digit, append c to buffer. while ( strpbrk( $codePoint, CodePoint::ASCII_DIGIT_MASK ) === $codePoint ) { $context->buffer->append( $codePoint ); $context->iter->next(); $codePoint = $context->iter->current(); } // 2. Otherwise, if one of the following is true: // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) // - url is special and c is U+005C (\) // - state override is given if ( ( $codePoint === CodePoint::EOF || $codePoint === '/' || $codePoint === '?' || $codePoint === '#' ) || ( $context->url->scheme->isSpecial() && $codePoint === '\\' ) || $context->isStateOverridden() ) { // 2.1. If buffer is not the empty string, then: if ( ! $context->buffer->isEmpty() ) { // 2.1.1. Let port be the mathematical integer value that is represented by buffer in radix-10 using // ASCII digits for digits with values 0 through 9. $port = $context->buffer->toInt(); // 2.1.2. If port is greater than 2 ^ 16 − 1, validation error, return failure. if ( $port > 2 ** 16 - 1 ) { // Validation error. Return failure. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->warning( 'port-out-of-range', [ 'input' => (string) $context->input, 'column_range' => [ $context->iter->key() - $context->buffer->length() + 1, $context->iter->key(), ], ] ) : null; return StatusCode::FAILURE; } // 2.1.3. Set url’s port to null, if port is url’s scheme’s default port, and to port otherwise. if ( $context->url->scheme->isSpecial() && $context->url->scheme->isDefaultPort( $port ) ) { $context->url->port = null; } else { $context->url->port = $port; } // 2.1.4. Set buffer to the empty string. $context->buffer->clear(); } // 2.2. If state override is given, then return. if ( $context->isStateOverridden() ) { return StatusCode::BREAK; } // 2.3. Set state to path start state and decrease pointer by 1. $context->state = ParserState::PATH_START; $context->iter->prev(); return StatusCode::OK; } // 3. Otherwise, validation error, return failure. ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->warning( 'port-invalid', [ 'input' => (string) $context->input, 'column_range' => [ $context->iter->key() - $context->buffer->length() + 1, $context->iter->key() + 1 ], ] ) : null; return StatusCode::FAILURE; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/RelativeState.php 0000777 00000005315 15251156632 0024076 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; use function assert; /** * @see https://url.spec.whatwg.org/#relative-state */ class RelativeState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. Assert: base’s scheme is not "file". assert( $context->base !== null && ! $context->base->scheme->isFile() ); // 2. Set url’s scheme to base’s scheme. $context->url->scheme = clone $context->base->scheme; // 3. If c is U+002F (/), then set state to relative slash state. if ( $codePoint === '/' ) { $context->state = ParserState::RELATIVE_SLASH; return StatusCode::OK; } // 4. Otherwise, if url is special and c is U+005C (\), validation error, set state to relative slash state. if ( $context->url->scheme->isSpecial() && $codePoint === '\\' ) { // Validation error ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-reverse-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; $context->state = ParserState::RELATIVE_SLASH; return StatusCode::OK; } // 5. Otherwise: // 5.1. Set url’s username to base’s username, url’s password to base’s password, url’s host to base’s host, // url’s port to base’s port, url’s path to a clone of base’s path, and url’s query to base’s query. $context->url->username = $context->base->username; $context->url->password = $context->base->password; $context->url->host = clone $context->base->host; $context->url->port = $context->base->port; $context->url->path = clone $context->base->path; $context->url->query = $context->base->query; // 5.2. If c is U+003F (?), then set url’s query to the empty string, and state to query state. if ( $codePoint === '?' ) { $context->url->query = ''; $context->state = ParserState::QUERY; return StatusCode::OK; } // 5.3. Otherwise, if c is U+0023 (#), set url’s fragment to the empty string and state to fragment state. if ( $codePoint === '#' ) { $context->url->fragment = ''; $context->state = ParserState::FRAGMENT; return StatusCode::OK; } // 5.4 Otherwise, if c is not the EOF code point: if ( $codePoint === CodePoint::EOF ) { return StatusCode::OK; } // 5.4.1. Set url’s query to null. $context->url->query = null; // 5.4.2. Shorten url’s path. $context->url->path->shorten( $context->url->scheme ); // 5.4.3. Set state to path state and decrease pointer by 1. $context->state = ParserState::PATH; $context->iter->prev(); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/NoSchemeState.php 0000777 00000003431 15251156632 0024021 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; /** * @see https://url.spec.whatwg.org/#no-scheme-state */ class NoSchemeState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If base is null, or base has an opaque path and c is not U+0023 (#), validation error, return failure. if ( $context->base === null || ( $context->base->path->isOpaque() && $codePoint !== '#' ) ) { // Validation error. Return failure. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->warning( 'missing-scheme-non-relative-URL', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; return StatusCode::FAILURE; } // 2. Otherwise, if base has an opaque path and c is U+0023 (#), set url’s scheme to base’s scheme, url’s path // to base’s path, url’s query to base’s query, url’s fragment to the empty string, and set state to fragment // state. if ( $context->base->path->isOpaque() && $codePoint === '#' ) { $context->url->scheme = clone $context->base->scheme; $context->url->path = clone $context->base->path; $context->url->query = $context->base->query; $context->url->fragment = ''; $context->state = ParserState::FRAGMENT; return StatusCode::OK; } // 3. Otherwise, if base’s scheme is not "file", set state to relative state and decrease pointer by 1. if ( ! $context->base->scheme->isFile() ) { $context->state = ParserState::RELATIVE; $context->iter->prev(); return StatusCode::OK; } // 4. Otherwise, set state to file state and decrease pointer by 1. $context->state = ParserState::FILE; $context->iter->prev(); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/HostnameState.php 0000777 00000000162 15251156632 0024074 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; class HostnameState extends AbstractHostState { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/PathStartState.php 0000777 00000004356 15251156632 0024241 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\Component\PathSegment; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; /** * @see https://url.spec.whatwg.org/#path-start-state */ class PathStartState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If url is special, then: if ( $context->url->scheme->isSpecial() ) { // 1.1. If c is U+005C (\), validation error. if ( $codePoint === '\\' ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-reverse-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } // 1.2. Set state to path state. $context->state = ParserState::PATH; // 1.3. If c is neither U+002F (/) nor U+005C (\), then decrease pointer by 1. if ( $codePoint !== '/' && $codePoint !== '\\' ) { $context->iter->prev(); } return StatusCode::OK; } // 2. Otherwise, if state override is not given and c is U+003F (?), set url’s query to the empty string and // state to query state. if ( ! $context->isStateOverridden() && $codePoint === '?' ) { $context->url->query = ''; $context->state = ParserState::QUERY; return StatusCode::OK; } // 3. Otherwise, if state override is not given and c is U+0023 (#), set url’s fragment to the empty string and // state to fragment state. if ( ! $context->isStateOverridden() && $codePoint === '#' ) { $context->url->fragment = ''; $context->state = ParserState::FRAGMENT; return StatusCode::OK; } // 4. Otherwise, if c is not the EOF code point: if ( $codePoint !== CodePoint::EOF ) { // 4.1. Set state to path state. $context->state = ParserState::PATH; // 4.2. If c is not U+002F (/), then decrease pointer by 1. if ( $codePoint !== '/' ) { $context->iter->prev(); } return StatusCode::OK; } // 5. Otherwise, if state override is given and url’s host is null, append the empty string to url’s path. if ( $context->isStateOverridden() && $context->url->host->isNull() ) { $context->url->path->push( new PathSegment() ); } return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/OpaquePathState.php 0000777 00000004564 15251156632 0024377 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; /** * @see https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state */ class OpaquePathState implements State { public function handle( ParserContext $context, string $codePoint ) { $percentEncoder = null; do { // 1. If c is U+003F (?), then set url’s query to the empty string and state to query state. if ( $codePoint === '?' ) { $context->url->query = ''; $context->state = ParserState::QUERY; break; } // 2. Otherwise, if c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state. if ( $codePoint === '#' ) { $context->url->fragment = ''; $context->state = ParserState::FRAGMENT; break; } // 3. Otherwise: // 3.1. If c is not the EOF code point, not a URL code point, and not U+0025 (%), validation error. if ( $codePoint !== CodePoint::EOF && $codePoint !== '%' && ! CodePoint::isUrlCodePoint( $codePoint ) ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-URL-unit', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; // 3.2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, validation error. } elseif ( $codePoint === '%' && ! $context->input->substr( $context->iter->key() + 1 )->startsWithTwoAsciiHexDigits() ) { // Validation error. ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->notice( 'invalid-URL-unit', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } // 3.3. If c is not the EOF code point, UTF-8 percent-encode c using the C0 control percent-encode set and // append the result to url’s path. if ( $codePoint !== CodePoint::EOF ) { $percentEncoder = $percentEncoder ?? new PercentEncoder(); $context->url->path->first()->append( $percentEncoder->percentEncodeAfterEncoding( 'utf-8', $codePoint, EncodeSet::C0_CONTROL ) ); } $context->iter->next(); $codePoint = $context->iter->current(); } while ( $codePoint !== CodePoint::EOF ); return StatusCode::OK; } } DataLiberation/vendor-patched/rowbot/url/src/State/SpecialAuthorityIgnoreSlashesState.php 0000777 00000001574 15251156632 0030227 0 ustar 00 php-toolkit <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; /** * @see https://url.spec.whatwg.org/#special-authority-ignore-slashes-state */ class SpecialAuthorityIgnoreSlashesState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If c is neither U+002F (/) nor U+005C (\), then set state to authority state and decrease pointer by 1. if ( $codePoint !== '/' && $codePoint !== '\\' ) { $context->state = ParserState::AUTHORITY; $context->iter->prev(); return StatusCode::OK; } // 2. Otherwise, validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'special-scheme-missing-following-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/HostState.php 0000777 00000000156 15251156632 0023236 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; class HostState extends AbstractHostState { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/FileSlashState.php 0000777 00000003525 15251156632 0024176 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; /** * @see https://url.spec.whatwg.org/#file-slash-state */ class FileSlashState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If c is U+002F (/) or U+005C (\), then: if ( $codePoint === '/' || $codePoint === '\\' ) { // 1.2. If c is U+005C (\), validation error. if ( $codePoint === '\\' ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-reverse-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } // 1.2. Set state to file host state. $context->state = ParserState::FILE_HOST; return StatusCode::OK; } // 2. Otherwise: // 2.1. If base is non-null and base’s scheme is "file", then: if ( $context->base !== null && $context->base->scheme->isFile() ) { // 2.1.1. Set url’s host to base’s host. $context->url->host = clone $context->base->host; $path = $context->base->path->first(); // 2.1.2. If the substring from pointer in input does not start with a Windows drive letter and base’s // path[0] is a normalized Windows drive letter, then append base’s path[0] to url’s path. if ( ! $context->input->substr( $context->iter->key() )->startsWithWindowsDriveLetter() && $path->isNormalizedWindowsDriveLetter() ) { // This is a (platform-independent) Windows drive letter quirk. Both url’s and // base’s host are null under these conditions and therefore not copied. $context->url->path->push( $path ); } } // 2.2. Set state to path state, and decrease pointer by 1. $context->state = ParserState::PATH; $context->iter->prev(); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/FileHostState.php 0000777 00000006016 15251156632 0024037 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\Component\Host\HostParser; use Rowbot\URL\Component\Host\StringHost; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; /** * @see https://url.spec.whatwg.org/#file-host-state */ class FileHostState implements State { public function handle( ParserContext $context, string $codePoint ) { do { // 1. If c is the EOF code point, U+002F (/), U+005C (\), U+003F (?), or U+0023 (#), then decrease pointer by 1 // and then: if ( $codePoint === CodePoint::EOF || $codePoint === '/' || $codePoint === '\\' || $codePoint === '?' || $codePoint === '#' ) { $context->iter->prev(); // 1.1. If state override is not given and buffer is a Windows drive letter, validation error, set state to // path state. if ( ! $context->isStateOverridden() && $context->buffer->isWindowsDriveLetter() ) { // Validation error ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'file-invalid-Windows-drive-letter-host', [ 'input' => (string) $context->input, 'column_range' => [ $context->iter->key(), $context->iter->key() + $context->buffer->length() ], ] ) : null; $context->state = ParserState::PATH; return StatusCode::OK; } // This is a (platform-independent) Windows drive letter quirk. $context->buffer is not reset // here and instead used in the path state. // // 1.2. Otherwise, if buffer is the empty string, then: if ( $context->buffer->isEmpty() ) { // 1.2.1. Set url’s host to the empty string. $context->url->host = new StringHost(); // 1.2.2. If state override is given, then return. if ( $context->isStateOverridden() ) { return StatusCode::BREAK; } // 1.2.3. Set state to path start state. $context->state = ParserState::PATH_START; return StatusCode::OK; } // 1.3. Otherwise, run these steps: // 1.3.1. Let host be the result of host parsing buffer with url is not special. $parser = new HostParser(); $host = $parser->parse( $context, $context->buffer->toUtf8String(), ! $context->url->scheme->isSpecial() ); // 1.3.2. If host is failure, then return failure. if ( $host === false ) { return StatusCode::FAILURE; } // 1.3.3. If host is "localhost", then set host to the empty string. if ( $host->isLocalHost() ) { $host = new StringHost(); } // 1.3.4. Set url’s host to host. $context->url->host = $host; // 1.3.5. If state override is given, then return. if ( $context->isStateOverridden() ) { return StatusCode::BREAK; } // 1.3.6. Set buffer to the empty string and state to path start state. $context->buffer->clear(); $context->state = ParserState::PATH_START; return StatusCode::OK; } // 2. Otherwise, append c to buffer. $context->buffer->append( $codePoint ); $context->iter->next(); $codePoint = $context->iter->current(); } while ( true ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/SchemeStartState.php 0000777 00000002142 15251156632 0024540 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; use function strpbrk; use function strtolower; /** * @see https://url.spec.whatwg.org/#scheme-start-state */ class SchemeStartState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If c is an ASCII alpha, append c, lowercased, to buffer, and set state to scheme state. if ( strpbrk( $codePoint, CodePoint::ASCII_ALPHA_MASK ) === $codePoint ) { $context->buffer->append( strtolower( $codePoint ) ); $context->state = ParserState::SCHEME; return StatusCode::OK; } // 2. Otherwise, if state override is not given, set state to no scheme state and decrease pointer by 1. if ( ! $context->isStateOverridden() ) { $context->state = ParserState::NO_SCHEME; $context->iter->prev(); return StatusCode::OK; } // 3. Otherwise, return failure. // // Note: This indication of failure is used exclusively by the Location object's protocol // attribute. return StatusCode::FAILURE; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/FileState.php 0000777 00000006507 15251156632 0023206 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\Component\Host\StringHost; use Rowbot\URL\Component\PathList; use Rowbot\URL\Component\Scheme; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; /** * @see https://url.spec.whatwg.org/#file-state */ class FileState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. Set url’s scheme to "file". $context->url->scheme = new Scheme( 'file' ); // 2. Set url’s host to the empty string. $context->url->host = new StringHost(); // 3. If c is U+002F (/) or U+005C (\), then: if ( $codePoint === '/' || $codePoint === '\\' ) { // 3.1. If c is U+005C (\), validation error. if ( $codePoint === '\\' ) { // Validation error ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-reverse-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } // 3.2. Set state to file slash state. $context->state = ParserState::FILE_SLASH; return StatusCode::OK; } // 4. Otherwise, if base is non-null and base’s scheme is "file": if ( $context->base !== null && $context->base->scheme->isFile() ) { // 4.1. Set url’s host to base’s host, url’s path to a clone of base’s path, and url’s query to base’s // query. $context->url->host = clone $context->base->host; $context->url->path = clone $context->base->path; $context->url->query = $context->base->query; // 4.2. If c is U+003F (?), then set url’s query to the empty string and state to query state. if ( $codePoint === '?' ) { $context->url->query = ''; $context->state = ParserState::QUERY; return StatusCode::OK; } // 4.3. Otherwise, if c is U+0023 (#), set url’s fragment to the empty string and state to fragment state. if ( $codePoint === '#' ) { $context->url->fragment = ''; $context->state = ParserState::FRAGMENT; return StatusCode::OK; } // 4.4. Otherwise, if c is not the EOF code point: if ( $codePoint === CodePoint::EOF ) { return StatusCode::OK; } // 4.4.1. Set url’s query to null. $context->url->query = null; // This is a (platform-independent) Windows drive letter quirk. // // 4.4.2. If the substring from pointer in input does not start with a Windows drive letter, then shorten // url’s path. if ( ! $context->input->substr( $context->iter->key() )->startsWithWindowsDriveLetter() ) { $context->url->path->shorten( $context->url->scheme ); // 4.4.3. Otherwise: } else { // 4.4.3.1 Validation error. ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->notice( 'file-invalid-Windows-drive-letter', [ 'input' => (string) $context->input, 'column_range' => [ $context->iter->key() + 1, $context->iter->key() + 3 ], ] ) : null; // 4.4.3.2. Set url’s path to an empty list. $context->url->path = new PathList(); } // 4.4.4 Set state to path state and decrease pointer by 1. $context->state = ParserState::PATH; $context->iter->prev(); return StatusCode::OK; } // 5. Otherwise, set state to path state, and decrease pointer by 1. $context->state = ParserState::PATH; $context->iter->prev(); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/SpecialAuthoritySlashesState.php 0000777 00000002154 15251156632 0027135 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; /** * @see https://url.spec.whatwg.org/#special-authority-slashes-state */ class SpecialAuthoritySlashesState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If c is U+002F (/) and remaining starts with U+002F (/), then set state to special authority ignore // slashes state and increase pointer by 1. if ( $codePoint === '/' && $context->iter->peek() === '/' ) { $context->state = ParserState::SPECIAL_AUTHORITY_IGNORE_SLASHES; $context->iter->next(); return StatusCode::OK; } // 2. Otherwise, validation error, set state to special authority ignore slashes state and decrease pointer // by 1. $context->state = ParserState::SPECIAL_AUTHORITY_IGNORE_SLASHES; ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'special-scheme-missing-following-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; $context->iter->prev(); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/QueryState.php 0000777 00000006164 15251156632 0023433 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; /** * @see https://url.spec.whatwg.org/#query-state */ class QueryState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If encoding is not UTF-8 and one of the following is true: // - url is not special // - url’s scheme is "ws" or "wss" // then set encoding to UTF-8. if ( $context->getOutputEncoding() !== 'utf-8' && ( ! $context->url->scheme->isSpecial() || $context->url->scheme->isWebsocket() ) ) { $context->setOutputEncoding( 'utf-8' ); } do { // 2. If one of the following is true: // - state override is not given and c is U+0023 (#) // - c is the EOF code point // then: if ( ! $context->isStateOverridden() && $codePoint === '#' || $codePoint === CodePoint::EOF ) { // 2.1. Let queryPercentEncodeSet be the special-query percent-encode set if url is special; otherwise the // query percent-encode set. $queryPercentEncodeSet = $context->url->scheme->isSpecial() ? EncodeSet::SPECIAL_QUERY : EncodeSet::QUERY; // 2.2. Percent-encode after encoding, with encoding, buffer, and queryPercentEncodeSet, and append the // result to url’s query. // // NOTE: This operation cannot be invoked code-point-for-code-point due to the stateful ISO-2022-JP encoder. $percentEncoder = new PercentEncoder(); $context->url->query .= $percentEncoder->percentEncodeAfterEncoding( $context->getOutputEncoding(), (string) $context->buffer, $queryPercentEncodeSet ); // 2.3. Set buffer to the empty string. $context->buffer->clear(); // 2.4. If c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state. if ( $codePoint === '#' ) { $context->url->fragment = ''; $context->state = ParserState::FRAGMENT; } return StatusCode::OK; } // 3. Otherwise, if c is not the EOF code point: // 3.1. If c is not a URL code point and not U+0025 (%), validation error. if ( $codePoint !== '%' && ! CodePoint::isUrlCodePoint( $codePoint ) ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-URL-unit', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; // 3.2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, validation error. } elseif ( $codePoint === '%' && ! $context->input->substr( $context->iter->key() + 1 )->startsWithTwoAsciiHexDigits() ) { // Validation error. ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->notice( 'invalid-URL-unit', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } // 3.3. Append c to buffer. $context->buffer->append( $codePoint ); $context->iter->next(); $codePoint = $context->iter->current(); } while ( true ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/AuthorityState.php 0000777 00000007344 15251156632 0024317 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; /** * @see https://url.spec.whatwg.org/#authority-state */ class AuthorityState implements State { /** * @var bool */ private $atTokenSeen; /** * @var bool */ private $passwordTokenSeen; public function __construct() { $this->atTokenSeen = false; $this->passwordTokenSeen = false; } public function handle( ParserContext $context, string $codePoint ) { do { // 1. If c is U+0040 (@), then: if ( $codePoint === '@' ) { // 1.1. Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-credentials', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; // 1.2. If atSignSeen is true, then prepend "%40" to buffer. if ( $this->atTokenSeen ) { $context->buffer->prepend( '%40' ); } // 1.3. Set atSignSeen to true. $this->atTokenSeen = true; $username = ''; $password = ''; // 1.4. For each codePoint in buffer: foreach ( $context->buffer as $bufferCodePoint ) { // 1.4.1. If codePoint is U+003A (:) and passwordTokenSeen is false, then set passwordTokenSeen to true // and continue. if ( $bufferCodePoint === ':' && ! $this->passwordTokenSeen ) { $this->passwordTokenSeen = true; continue; } // 1.4.3. If passwordTokenSeen is true, then append encodedCodePoints to url’s password. // 1.4.4. Otherwise, append encodedCodePoints to url’s username. if ( $this->passwordTokenSeen ) { $password .= $bufferCodePoint; } else { $username .= $bufferCodePoint; } } // 1.4.2. Let encodedCodePoints be the result of running UTF-8 percent-encode codePoint using the // userinfo percent-encode set. $percentEncoder = new PercentEncoder(); $context->url->username .= $percentEncoder->percentEncodeAfterEncoding( 'utf-8', $username, EncodeSet::USERINFO ); $context->url->password .= $percentEncoder->percentEncodeAfterEncoding( 'utf-8', $password, EncodeSet::USERINFO ); // 1.5. Set buffer to the empty string. $context->buffer->clear(); $context->iter->next(); $codePoint = $context->iter->current(); continue; } // 2. Otherwise, if one of the following is true: // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) // - url is special and c is U+005C (\) if ( ( $codePoint === CodePoint::EOF || $codePoint === '/' || $codePoint === '?' || $codePoint === '#' ) || ( $context->url->scheme->isSpecial() && $codePoint === '\\' ) ) { // 2.1. If atSignSeen is true and buffer is the empty string, validation error, return failure. if ( $this->atTokenSeen && $context->buffer->isEmpty() ) { // Validation error. ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->warning( 'host-missing', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; return StatusCode::FAILURE; } // 2.2. Decrease pointer by the number of code points in buffer plus one, set buffer to the empty string, // and set state to host state. $context->iter->seek( - ( $context->buffer->length() + 1 ) ); $context->buffer->clear(); $context->state = ParserState::HOST; return StatusCode::OK; } // 3. Otherwise, append c to buffer. $context->buffer->append( $codePoint ); $context->iter->next(); $codePoint = $context->iter->current(); } while ( true ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/SchemeState.php 0000777 00000012011 15251156632 0023516 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\Component\OpaquePath; use Rowbot\URL\Component\PathSegment; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; use function assert; use function strpbrk; use function strtolower; /** * @see https://url.spec.whatwg.org/#scheme-state */ class SchemeState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If c is an ASCII alphanumeric, U+002B (+), U+002D (-), or U+002E (.), append c, lowercased, to buffer. while ( strpbrk( $codePoint, CodePoint::ASCII_ALNUM_MASK ) === $codePoint || $codePoint === '+' || $codePoint === '-' || $codePoint === '.' ) { $context->buffer->append( strtolower( $codePoint ) ); $context->iter->next(); $codePoint = $context->iter->current(); } // 2. Otherwise, if c is U+003A (:), then: if ( $codePoint === ':' ) { $stateIsOverridden = $context->isStateOverridden(); $bufferIsSpecialScheme = false; $candidateScheme = $context->buffer->toScheme(); // 2.1. If state override is given, then: if ( $stateIsOverridden ) { $bufferIsSpecialScheme = $candidateScheme->isSpecial(); $urlIsSpecial = $context->url->scheme->isSpecial(); // 2.1.1. If url’s scheme is a special scheme and buffer is not a special scheme, then return. // 2.1.2. If url’s scheme is not a special scheme and buffer is a special scheme, then return. if ( $urlIsSpecial xor $bufferIsSpecialScheme ) { return StatusCode::BREAK; } // 2.1.3. If url includes credentials or has a non-null port, and buffer is "file", then return. if ( $context->url->includesCredentials() || ( $context->url->port !== null && $candidateScheme->isFile() ) ) { return StatusCode::BREAK; } // 2.1.4. If url’s scheme is "file" and its host is an empty host, then return. if ( $context->url->scheme->isFile() && $context->url->host->isEmpty() ) { return StatusCode::BREAK; } } // 2.2. Set url’s scheme to buffer. $context->url->scheme = $candidateScheme; // 2.3. If state override is given, then: if ( $stateIsOverridden ) { // 2.3.1. If url’s port is url’s scheme’s default port, then set url’s port to null. if ( $bufferIsSpecialScheme && $context->url->scheme->isDefaultPort( $context->url->port ) ) { $context->url->port = null; } // 2.3.2. Return. return StatusCode::BREAK; } // 2.4. Set buffer to the empty string. $context->buffer->clear(); $urlIsSpecial = $context->url->scheme->isSpecial(); // 2.5. If url’s scheme is "file", then: if ( $context->url->scheme->isFile() ) { // 2.5.1. If remaining does not start with "//", validation error. if ( $context->iter->peek( 2 ) !== '//' ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'special-scheme-missing-following-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 2, ] ) : null; } // 2.5.2. Set state to file state. $context->state = ParserState::FILE; // 2.6. Otherwise, if url is special, base is non-null, and base’s scheme is equal to url’s scheme, set // state to special relative or authority state. } elseif ( $urlIsSpecial && $context->base !== null && $context->base->scheme->equals( $context->url->scheme ) ) { assert( $context->base->scheme->isSpecial(), 'base is special (and therefore does not have an opaque path)' ); $context->state = ParserState::SPECIAL_RELATIVE_OR_AUTHORITY; // 2.7. Otherwise, if url is special, set state to special authority slashes state. } elseif ( $urlIsSpecial ) { $context->state = ParserState::SPECIAL_AUTHORITY_SLASHES; // 2.8. Otherwise, if remaining starts with an U+002F (/), set state to path or authority state and // increase pointer by 1. } elseif ( $context->iter->peek() === '/' ) { $context->state = ParserState::PATH_OR_AUTHORITY; $context->iter->next(); // 2.9. Otherwise, set url’s path to the empty string and set state to opaque path state. } else { $context->url->path = new OpaquePath( new PathSegment() ); $context->state = ParserState::OPAQUE_PATH; } return StatusCode::OK; } // 3. Otherwise, if state override is not given, set buffer to the empty string, state to no scheme state, and // start over (from the first code point in input). if ( ! $context->isStateOverridden() ) { $context->buffer->clear(); $context->state = ParserState::NO_SCHEME; // Reset the pointer to point at the first code point. $context->iter->rewind(); return StatusCode::CONTINUE; } // 4. Otherwise, return failure. // // Note: This indication of failure is used exclusively by the Location object's protocol // attribute. Furthermore, the non-failure termination earlier in this state is an // intentional difference for defining that attribute. return StatusCode::FAILURE; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/FragmentState.php 0000777 00000003315 15251156632 0024064 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\String\CodePoint; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; /** * @see https://url.spec.whatwg.org/#fragment-state */ class FragmentState implements State { public function handle( ParserContext $context, string $codePoint ) { $buffer = ''; // 1. If c is not the EOF code point, then: while ( $codePoint !== CodePoint::EOF ) { // 1.1. If c is not a URL code point and not U+0025 (%), validation error. if ( $codePoint !== '%' && ! CodePoint::isUrlCodePoint( $codePoint ) ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-URL-unit', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; // 1.2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, validation error. } elseif ( $codePoint === '%' && ! $context->input->substr( $context->iter->key() + 1 )->startsWithTwoAsciiHexDigits() ) { // Validation error. ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->notice( 'invalid-URL-unit', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } $buffer .= $codePoint; $context->iter->next(); $codePoint = $context->iter->current(); } // 1.3. UTF-8 percent-encode c using the fragment percent-encode set and append the result to url’s fragment. $percentEncoder = new PercentEncoder(); $context->url->fragment .= $percentEncoder->percentEncodeAfterEncoding( 'utf-8', $buffer, EncodeSet::FRAGMENT ); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/PathState.php 0000777 00000012166 15251156632 0023221 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\Component\PathSegment; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; /** * @see https://url.spec.whatwg.org/#path-state */ class PathState implements State { /** * @see https://url.spec.whatwg.org/#double-dot-path-segment */ private const DOUBLE_DOT_SEGMENT = [ '..' => '', '.%2e' => '', '.%2E' => '', '%2e.' => '', '%2E.' => '', '%2e%2e' => '', '%2E%2E' => '', '%2e%2E' => '', '%2E%2e' => '', ]; /** * @see https://url.spec.whatwg.org/#single-dot-path-segment */ private const SINGLE_DOT_SEGMENT = [ '.' => '', '%2e' => '', '%2E' => '', ]; public function handle( ParserContext $context, string $codePoint ) { $percentEncoder = null; do { // 1. If one of the following is true: // - c is the EOF code point or U+002F (/) // - url is special and c is U+005C (\) // - state override is not given and c is U+003F (?) or U+0023 (#) if ( $codePoint === CodePoint::EOF || $codePoint === '/' || ( $context->url->scheme->isSpecial() && $codePoint === '\\' ) || ( ! $context->isStateOverridden() && ( $codePoint === '?' || $codePoint === '#' ) ) ) { $urlIsSpecial = $context->url->scheme->isSpecial(); // 1.1. If url is special and c is U+005C (\), validation error. if ( $urlIsSpecial && $codePoint === '\\' ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'invalid-reverse-solidus', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } $stringBuffer = (string) $context->buffer; // 1.2. If buffer is a double-dot path segment, then: if ( isset( self::DOUBLE_DOT_SEGMENT[ $stringBuffer ] ) ) { // 1.2.1. Shorten url’s path. $context->url->path->shorten( $context->url->scheme ); // 1.2.2. If neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string // to url’s path. if ( $codePoint !== '/' && ! ( $urlIsSpecial && $codePoint === '\\' ) ) { $context->url->path->push( new PathSegment() ); } // 1.3. Otherwise, if buffer is a single-dot path segment and if neither c is U+002F (/), nor url is special // and c is U+005C (\), append the empty string to url’s path. } elseif ( isset( self::SINGLE_DOT_SEGMENT[ $stringBuffer ] ) && $codePoint !== '/' && ! ( $urlIsSpecial && $codePoint === '\\' ) ) { $context->url->path->push( new PathSegment() ); // 1.4. Otherwise, if buffer is not a single-dot path segment, then: } elseif ( ! isset( self::SINGLE_DOT_SEGMENT[ $stringBuffer ] ) ) { // 1.4.1. If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then // replace the second code point in buffer with U+003A (:). if ( $context->url->scheme->isFile() && $context->url->path->isEmpty() && $context->buffer->isWindowsDriveLetter() ) { // This is a (platform-independent) Windows drive letter quirk. $context->buffer->setCodePointAt( 1, ':' ); } // 1.4.2. Append buffer to url’s path. $context->url->path->push( $context->buffer->toPath() ); } // 1.5. Set buffer to the empty string. $context->buffer->clear(); // 1.6. If c is U+003F (?), then set url’s query to the empty string and state to query state. if ( $codePoint === '?' ) { $context->url->query = ''; $context->state = ParserState::QUERY; // If c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state. } elseif ( $codePoint === '#' ) { $context->url->fragment = ''; $context->state = ParserState::FRAGMENT; } return StatusCode::OK; } // 2. Otherwise, run these steps: // 2.1. If c is not a URL code point and not U+0025 (%), validation error. if ( $codePoint !== '%' && ! CodePoint::isUrlCodePoint( $codePoint ) ) { // Validation error ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->notice( 'invalid-URL-unit', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; // 2.2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, validation error. } elseif ( $codePoint === '%' && ! $context->input->substr( $context->iter->key() + 1 )->startsWithTwoAsciiHexDigits() ) { // Validation error ( $nullsafeVariable3 = $context->logger ) ? $nullsafeVariable3->notice( 'invalid-URL-unit', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; } // 2.3. UTF-8 percent-encode c using the path percent-encode set and append the result to buffer. $percentEncoder = $percentEncoder ?? new PercentEncoder(); $context->buffer->append( $percentEncoder->percentEncodeAfterEncoding( 'utf-8', $codePoint, EncodeSet::PATH ) ); $context->iter->next(); $codePoint = $context->iter->current(); } while ( true ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/AbstractHostState.php 0000777 00000011016 15251156632 0024717 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\Component\Host\HostParser; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; use Rowbot\URL\String\CodePoint; /** * @see https://url.spec.whatwg.org/#host-state */ abstract class AbstractHostState implements State { /** * @var bool */ private $isBracketOpen; public function __construct() { $this->isBracketOpen = false; } public function handle( ParserContext $context, string $codePoint ) { // 1. If state override is given and url’s scheme is "file", then decrease pointer by 1 and set state to file // host state. if ( $context->isStateOverridden() && $context->url->scheme->isFile() ) { $context->iter->prev(); $context->state = ParserState::FILE_HOST; return StatusCode::OK; } do { // 2. Otherwise, if c is U+003A (:) and insideBrackets is false, then: if ( $codePoint === ':' && ! $this->isBracketOpen ) { // 2.1. If buffer is the empty string, validation error, return failure. if ( $context->buffer->isEmpty() ) { // Validation error. Return failure. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->warning( 'host-missing', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 1, ] ) : null; return StatusCode::FAILURE; } // 2.2. If state override is given and state override is hostname state, then return. if ( $context->isOverrideStateHostname() ) { return StatusCode::BREAK; } // 2.3. Let host be the result of host parsing buffer with url is not special. $parser = new HostParser(); $host = $parser->parse( $context, $context->buffer->toUtf8String(), ! $context->url->scheme->isSpecial() ); // 2.4. If host is failure, then return failure. if ( $host === false ) { return StatusCode::FAILURE; } // 5. Set url’s host to host, buffer to the empty string, and state to port state. $context->url->host = $host; $context->buffer->clear(); $context->state = ParserState::PORT; return StatusCode::OK; } // 3. Otherwise, if one of the following is true: // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) // - url is special and c is U+005C (\) if ( ( $codePoint === CodePoint::EOF || $codePoint === '/' || $codePoint === '?' || $codePoint === '#' ) || ( $context->url->scheme->isSpecial() && $codePoint === '\\' ) ) { // then decrease pointer by 1, and then: $context->iter->prev(); // 3.1. If url is special and buffer is the empty string, validation error, return failure. if ( $context->url->scheme->isSpecial() && $context->buffer->isEmpty() ) { // Validation error. Return failure. ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->warning( 'host-missing', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 2, // Add 2 since we called ->prev() above ] ) : null; return StatusCode::FAILURE; } // 3.2. Otherwise, if state override is given, buffer is the empty string, and either url includes // credentials or url’s port is non-null, return. if ( $context->isStateOverridden() && $context->buffer->isEmpty() && ( $context->url->includesCredentials() || $context->url->port !== null ) ) { return StatusCode::BREAK; } // 3.3. Let host be the result of host parsing buffer with url is not special. $parser = new HostParser(); $host = $parser->parse( $context, $context->buffer->toUtf8String(), ! $context->url->scheme->isSpecial() ); // 3.4. If host is failure, then return failure. if ( $host === false ) { return StatusCode::FAILURE; } // 3.5. Set url’s host to host, buffer to the empty string, and state to path start state. $context->url->host = $host; $context->buffer->clear(); $context->state = ParserState::PATH_START; // 3.6. If state override is given, then return. if ( $context->isStateOverridden() ) { return StatusCode::BREAK; } return StatusCode::OK; } // 4. Otherwise: // 4.1. If c is U+005B ([), then set insideBrackets to true. if ( $codePoint === '[' ) { $this->isBracketOpen = true; // 4.2. If c is U+005D (]), then set insideBrackets to false. } elseif ( $codePoint === ']' ) { $this->isBracketOpen = false; } // 4.3. Append c to buffer. $context->buffer->append( $codePoint ); $context->iter->next(); $codePoint = $context->iter->current(); } while ( true ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/StatusCode.php 0000777 00000000325 15251156632 0023374 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; class StatusCode { public const OK = 'ok'; public const CONTINUE = 'continue'; public const BREAK = 'break'; public const FAILURE = 'failure'; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/State.php 0000777 00000000124 15251156632 0022373 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; interface State { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/State/PathOrAuthorityState.php 0000777 00000001206 15251156632 0025424 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\State; use Rowbot\URL\ParserContext; use Rowbot\URL\ParserState; /** * @see https://url.spec.whatwg.org/#path-or-authority-state */ class PathOrAuthorityState implements State { public function handle( ParserContext $context, string $codePoint ) { // 1. If c is U+002F (/), then set state to authority state. if ( $codePoint === '/' ) { $context->state = ParserState::AUTHORITY; return StatusCode::OK; } // 2. Otherwise, set state to path state, and decrease pointer by 1. $context->state = ParserState::PATH; $context->iter->prev(); return StatusCode::OK; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/PathInterface.php 0000777 00000001144 15251156632 0024715 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use Countable; use Rowbot\URL\URLRecord; interface PathInterface extends Countable { public function first(): PathSegment; public function isEmpty(): bool; public function isOpaque(): bool; public function push( PathSegment $path ): void; /** * @see https://url.spec.whatwg.org/#shorten-a-urls-path */ public function shorten( Scheme $scheme ): void; /** * @see https://url.spec.whatwg.org/#potentially-strip-trailing-spaces-from-an-opaque-path */ public function potentiallyStripTrailingSpaces( URLRecord $url ): void; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/OpaquePath.php 0000777 00000001377 15251156632 0024257 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use Rowbot\URL\Exception\UnsupportedOperationException; use function implode; class OpaquePath extends AbstractPath { public function __construct( PathSegment $path ) { parent::__construct( [ $path ] ); } public function isOpaque(): bool { return true; } public function push( PathSegment $path ): void { throw new UnsupportedOperationException( 'An opaque path can only contain a single path' ); } public function shorten( Scheme $scheme ): void { throw new UnsupportedOperationException( 'Cannot shorten an opaque path' ); } /** * @see https://url.spec.whatwg.org/#url-path-serializer */ public function __toString(): string { return implode( '', $this->list ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/PathSegment.php 0000777 00000001344 15251156632 0024421 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use Rowbot\URL\String\AbstractStringBuffer; use Rowbot\URL\String\CodePoint; use function rtrim; use function strlen; use function strpbrk; /** * Represents a component in a URL's path as an ASCII string. */ class PathSegment extends AbstractStringBuffer { /** * @see https://url.spec.whatwg.org/#normalized-windows-drive-letter */ public function isNormalizedWindowsDriveLetter(): bool { return strlen( $this->string ) === 2 && strpbrk( $this->string[0], CodePoint::ASCII_ALPHA_MASK ) === $this->string[0] && $this->string[1] === ':'; } public function stripTrailingSpaces(): void { $this->string = rtrim( $this->string, "\x20" ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/AbstractPath.php 0000777 00000002117 15251156632 0024561 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use Rowbot\URL\String\Exception\UndefinedIndexException; use Rowbot\URL\URLRecord; use function count; abstract class AbstractPath implements PathInterface { /** * @var list<PathSegment> */ protected $list; /** * @param list<PathSegment> $paths */ public function __construct( array $paths = [] ) { $this->list = $paths; } public function count(): int { return count( $this->list ); } public function first(): PathSegment { if ( ! isset( $this->list[0] ) ) { throw new UndefinedIndexException(); } return $this->list[0]; } public function isEmpty(): bool { return $this->list === []; } public function potentiallyStripTrailingSpaces( URLRecord $url ): void { if ( ! $this->isOpaque() ) { return; } if ( $url->fragment !== null ) { return; } if ( $url->query !== null ) { return; } $this->list[0]->stripTrailingSpaces(); } public function __clone() { $list = []; foreach ( $this->list as $path ) { $list[] = clone $path; } $this->list = $list; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/TupleOrigin.php 0000777 00000004173 15251156632 0024446 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use Rowbot\URL\Component\Host\HostInterface; use Rowbot\URL\Origin; /** * @see https://html.spec.whatwg.org/multipage/origin.html#concept-origin-tuple */ final class TupleOrigin implements Origin { /** * @var string|null */ private $domain; /** * @var HostInterface */ private $host; /** * @var int|null */ private $port; /** * @var string */ private $scheme; public function __construct( string $scheme, HostInterface $host, ?int $port, ?string $domain = null ) { $this->domain = $domain; $this->host = $host; $this->port = $port; $this->scheme = $scheme; } public function getEffectiveDomain(): ?string { if ( $this->domain !== null ) { return $this->domain; } return $this->host->getSerializer()->toFormattedString(); } public function isOpaque(): bool { return false; } /** * @param Origin $other */ public function isSameOrigin( $other ): bool { return $other instanceof self && $this->scheme === $other->scheme && $this->host->equals( $other->host ) && $this->port === $other->port; } /** * @param Origin $other */ public function isSameOriginDomain( $other ): bool { // If A and B are both tuple origins... if ( $other instanceof self ) { // If A and B's schemes are identical, and their domains are // identical and non-null, then return true. Otherwise, if A and B // are same origin and their domains are identical and null, then // return true. if ( $this->scheme === $other->scheme && $this->domain !== null && $this->domain === $other->domain ) { return true; } if ( $this->isSameOrigin( $other ) && $this->domain === null && $other->domain === null ) { return true; } } return false; } /** * @see https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin */ public function __toString(): string { $result = $this->scheme; $result .= '://'; $result .= $this->host->getSerializer()->toFormattedString(); if ( $this->port !== null ) { $result .= ':' . $this->port; } return $result; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/IPv6Address.php 0000777 00000001441 15251156632 0025207 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; use Rowbot\URL\Component\Host\Serializer\HostSerializerInterface; use Rowbot\URL\Component\Host\Serializer\IPv6AddressSerializer; /** * @see https://url.spec.whatwg.org/#concept-ipv6 */ class IPv6Address extends AbstractHost implements HostInterface { /** * @var non-empty-list<int> */ private $address; /** * @param non-empty-list<int> $address */ public function __construct( array $address ) { $this->address = $address; } /** * @param HostInterface $other */ public function equals( $other ): bool { return $other instanceof self && $this->address === $other->address; } public function getSerializer(): HostSerializerInterface { return new IPv6AddressSerializer( $this->address ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/NullHost.php 0000777 00000001044 15251156632 0024664 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; use Rowbot\URL\Component\Host\Serializer\HostSerializerInterface; use Rowbot\URL\Component\Host\Serializer\StringHostSerializer; class NullHost extends AbstractHost implements HostInterface { /** * @param HostInterface $other */ public function equals( $other ): bool { return $other instanceof self; } public function getSerializer(): HostSerializerInterface { return new StringHostSerializer( '' ); } public function isNull(): bool { return true; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/IPv4Address.php 0000777 00000001430 15251156632 0025203 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; use Rowbot\URL\Component\Host\Serializer\HostSerializerInterface; use Rowbot\URL\Component\Host\Serializer\IPv4AddressSerializer; /** * @see https://url.spec.whatwg.org/#concept-ipv4 */ class IPv4Address extends AbstractHost implements HostInterface { /** * @var numeric-string */ private $address; /** * @param numeric-string $address */ public function __construct( string $address ) { $this->address = $address; } /** * @param HostInterface $other */ public function equals( $other ): bool { return $other instanceof self && $this->address === $other->address; } public function getSerializer(): HostSerializerInterface { return new IPv4AddressSerializer( $this->address ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/StringHost.php 0000777 00000001546 15251156632 0025227 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; use Rowbot\URL\Component\Host\Serializer\HostSerializerInterface; use Rowbot\URL\Component\Host\Serializer\StringHostSerializer; use Rowbot\URL\String\Utf8String; /** * @see https://url.spec.whatwg.org/#concept-domain * @see https://url.spec.whatwg.org/#opaque-host * @see https://url.spec.whatwg.org/#empty-host */ class StringHost extends Utf8String implements HostInterface { /** * @param HostInterface $other */ public function equals( $other ): bool { return $other instanceof self && $this->string === $other->string; } public function getSerializer(): HostSerializerInterface { return new StringHostSerializer( $this->string ); } public function isLocalHost(): bool { return $this->string === 'localhost'; } public function isNull(): bool { return false; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Math/NativeIntAdapter.php 0000777 00000003511 15251156632 0027210 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Math; use Rowbot\URL\Component\Host\Math\Exception\MathException; use function floor; use function intval; class NativeIntAdapter implements NumberInterface { /** * @var int */ private $number; /** * @param int|string $number */ public function __construct( $number, int $base = 10 ) { $this->number = intval( $number, $base ); } public function intdiv( int $number ): NumberInterface { return new self( (int) floor( $this->number / $number ) ); } /** * @param NumberInterface $number */ public function isEqualTo( $number ): bool { if ( ! $number instanceof self ) { throw new MathException( 'Must be given an instance of itself.' ); } return $this->number === $number->number; } public function isGreaterThan( int $number ): bool { return $this->number > $number; } /** * @param NumberInterface $number */ public function isGreaterThanOrEqualTo( $number ): bool { if ( ! $number instanceof self ) { throw new MathException( 'Must be given an instance of itself.' ); } return $this->number >= $number->number; } public function mod( int $number ): NumberInterface { return new self( $this->number % $number ); } public function multipliedBy( int $number ): NumberInterface { return new self( $this->number * $number ); } /** * @param NumberInterface $number */ public function plus( $number ): NumberInterface { if ( ! $number instanceof self ) { throw new MathException( 'Must be given an instance of itself.' ); } return new self( $this->number + $number->number ); } public function pow( int $number ): NumberInterface { return new self( $this->number ** $number ); } /** * @return numeric-string */ public function __toString(): string { return (string) $this->number; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Math/NumberFactory.php 0000777 00000001565 15251156632 0026575 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Math; use const PHP_INT_SIZE; /** * Creates a number object based on the platform we are operating on. In the case of 32-bit PHP, * we must use a BigInt library since the stored representation of an IPv4 address can overflow * a 32-bit integer as it expects to be stored as an unsigned 32-bit integer, but PHP only * supports signed integers. */ final class NumberFactory { /** * @param int|string $number */ public static function createNumber( $number, int $base ): NumberInterface { // PHP_INT_SIZE returns the number of bytes that can fit in to an integer on the given // platform. If the size is 4, then we know we are operating on a 32-bit platform. if ( PHP_INT_SIZE === 4 ) { return new BrickMathAdapter( $number, $base ); } return new NativeIntAdapter( $number, $base ); } } DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Math/Exception/MathException.php 0000777 00000000255 15251156632 0030437 0 ustar 00 php-toolkit <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Math\Exception; use Rowbot\URL\Exception\URLException; class MathException extends URLException { } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Math/BrickMathAdapter.php 0000777 00000004264 15251156632 0027161 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Math; use Brick\Math\BigInteger; use Brick\Math\RoundingMode; use Rowbot\URL\Component\Host\Math\Exception\MathException; use function assert; use function is_numeric; use function is_string; class BrickMathAdapter implements NumberInterface { /** * @var BigInteger */ private $number; /** * @param int|string|BigInteger $number */ public function __construct( $number, int $base = 10 ) { if ( is_string( $number ) ) { $this->number = BigInteger::fromBase( $number, $base ); return; } $this->number = BigInteger::of( $number ); } public function intdiv( int $number ): NumberInterface { return new self( $this->number->dividedBy( $number, RoundingMode::FLOOR ) ); } /** * @param NumberInterface $number */ public function isEqualTo( $number ): bool { if ( ! $number instanceof self ) { throw new MathException( 'Must be given an instance of itself.' ); } return $this->number->isEqualTo( $number->number ); } public function isGreaterThan( int $number ): bool { return $this->number->isGreaterThan( $number ); } /** * @param NumberInterface $number */ public function isGreaterThanOrEqualTo( $number ): bool { if ( ! $number instanceof self ) { throw new MathException( 'Must be given an instance of itself.' ); } return $this->number->isGreaterThanOrEqualTo( $number->number ); } public function mod( int $number ): NumberInterface { return new self( $this->number->mod( $number ) ); } public function multipliedBy( int $number ): NumberInterface { return new self( $this->number->multipliedBy( $number ) ); } /** * @param NumberInterface $number */ public function plus( $number ): NumberInterface { if ( ! $number instanceof self ) { throw new MathException( 'Must be given an instance of itself.' ); } return new self( $this->number->plus( $number->number ) ); } public function pow( int $number ): NumberInterface { return new self( $this->number->power( $number ) ); } /** * @return numeric-string */ public function __toString(): string { $str = (string) $this->number; assert( is_numeric( $str ) ); return $str; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Math/NumberInterface.php 0000777 00000002210 15251156632 0027052 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Math; use Rowbot\URL\Component\Host\Math\Exception\MathException; interface NumberInterface { /** * Performs integer division, flooring the result. */ public function intdiv( int $number ): self; /** * @throws MathException If anything other than the * instance is given. */ public function isEqualTo( self $number ): bool; public function isGreaterThan( int $number ): bool; /** * @throws MathException If anything other than the * instance is given. */ public function isGreaterThanOrEqualTo( self $number ): bool; public function mod( int $number ): self; public function multipliedBy( int $number ): self; /** * @throws MathException If anything other than the * instance is given. */ public function plus( self $number ): self; public function pow( int $number ): self; /** * @return numeric-string */ public function __toString(): string; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/AbstractHost.php 0000777 00000000420 15251156632 0025512 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; abstract class AbstractHost { public function isEmpty(): bool { return false; } public function isLocalHost(): bool { return false; } public function isNull(): bool { return false; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/HostParser.php 0000777 00000014520 15251156632 0025211 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; use ReflectionClass; use ReflectionClassConstant; use Rowbot\Idna\Idna; use Rowbot\URL\ParserContext; use Rowbot\URL\String\CodePoint; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; use Rowbot\URL\String\USVStringInterface; use function array_filter; use function assert; use function mb_strcut; use function mb_strlen; use function rawurldecode; use const ARRAY_FILTER_USE_KEY; use const PREG_OFFSET_CAPTURE; /** * @see https://url.spec.whatwg.org/#concept-host-parser */ class HostParser { /** * @see https://url.spec.whatwg.org/#forbidden-host-code-point * @see https://url.spec.whatwg.org/#forbidden-domain-code-point */ private const FORBIDDEN_HOST_CODEPOINTS = '\x00\x09\x0A\x0D\x20#\/:<>?@[\\\\\]^|'; private const FORBIDDEN_DOMAIN_CODEPOINTS = self::FORBIDDEN_HOST_CODEPOINTS . '\x01-\x1F%\x7F'; private const UNICODE_IDNA_OPTIONS = [ 'CheckHyphens' => false, 'CheckBidi' => true, 'CheckJoiners' => true, 'UseSTD3ASCIIRules' => false, 'Transitional_Processing' => false, ]; /** * Parses a host string. The string could represent a domain, IPv4 or IPv6 address, or an opaque host. * * @param bool $isOpaque (optional) Whether or not the URL has a special scheme. * * @return HostInterface|false The returned Host can never be a null host. */ public function parse( ParserContext $context, USVStringInterface $input, bool $isOpaque = false ) { if ( $input->startsWith( '[' ) ) { if ( ! $input->endsWith( ']' ) ) { // Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->warning( 'IPv6-unclosed', [ 'input' => (string) $context->input, 'column' => $context->iter->key() + 2, ] ) : null; return false; } return IPv6AddressParser::parse( $context, $input->substr( 1, - 1 ) ); } if ( $isOpaque ) { return $this->parseOpaqueHost( $context, $input ); } assert( ! $input->isEmpty() ); $domain = rawurldecode( (string) $input ); $asciiDomain = $this->domainToAscii( $context, $domain, false ); if ( $asciiDomain === false ) { return false; } if ( $asciiDomain->matches( '/[' . self::FORBIDDEN_DOMAIN_CODEPOINTS . ']/u', $matches, PREG_OFFSET_CAPTURE ) ) { // Validation error. ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->warning( 'domain-invalid-code-point', [ 'input' => (string) $asciiDomain, 'column' => mb_strlen( mb_strcut( (string) $asciiDomain, 0, $matches[0][1], 'utf-8' ), 'utf-8' ) + 1, 'unicode_domain' => Idna::toUnicode( (string) $asciiDomain, self::UNICODE_IDNA_OPTIONS )->getDomain(), ] ) : null; return false; } if ( IPv4AddressParser::endsInIPv4Number( $asciiDomain ) ) { return IPv4AddressParser::parse( $context, $asciiDomain ); } return $asciiDomain; } /** * @see https://url.spec.whatwg.org/#concept-domain-to-ascii * @return StringHost|false */ private function domainToAscii( ParserContext $context, string $domain, bool $beStrict ) { // 1. Let result be the result of running Unicode ToASCII with domain_name set to domain, UseSTD3ASCIIRules set // to beStrict, CheckHyphens set to false, CheckBidi set to true, CheckJoiners set to true, // Transitional_Processing set to false, and VerifyDnsLength set to beStrict. $result = Idna::toAscii( $domain, [ 'CheckHyphens' => false, 'CheckBidi' => true, 'CheckJoiners' => true, 'UseSTD3ASCIIRules' => $beStrict, 'Transitional_Processing' => false, 'VerifyDnsLength' => $beStrict, ] ); $convertedDomain = $result->getDomain(); // 2. If result is a failure value, validation error, return failure. // 3. If result is the empty string, validation error, return failure. if ( $convertedDomain === '' || $result->hasErrors() ) { // Validation error. ( $nullsafeVariable3 = $context->logger ) ? $nullsafeVariable3->warning( 'domain-to-ASCII', [ 'input' => $domain, 'column_range' => [ 1, mb_strlen( $domain, 'utf-8' ) ], 'idn_errors' => $this->enumerateIdnaErrors( $result->getErrors() ), 'unicode_domain' => Idna::toUnicode( $domain, self::UNICODE_IDNA_OPTIONS )->getDomain(), ] ) : null; return false; } // 4. Return result. return new StringHost( $convertedDomain ); } /** * Parses an opaque host. * * @see https://url.spec.whatwg.org/#concept-opaque-host-parser * @return HostInterface|false */ private function parseOpaqueHost( ParserContext $context, USVStringInterface $input ) { if ( $input->matches( '/[' . self::FORBIDDEN_HOST_CODEPOINTS . ']/u', $matches, PREG_OFFSET_CAPTURE ) ) { // Validation error. ( $nullsafeVariable4 = $context->logger ) ? $nullsafeVariable4->warning( 'host-invalid-code-point', [ 'input' => (string) $input, 'column' => mb_strlen( mb_strcut( (string) $input, 0, $matches[0][1], 'utf-8' ), 'utf-8' ) + 1, ] ) : null; return false; } foreach ( $input as $i => $codePoint ) { if ( $codePoint !== '%' && ! CodePoint::isUrlCodePoint( $codePoint ) ) { // Validation error. ( $nullsafeVariable5 = $context->logger ) ? $nullsafeVariable5->notice( 'invalid-URL-unit', [ 'input' => (string) $input, 'column' => $i, ] ) : null; } elseif ( $codePoint === '%' && ! $input->substr( $i + 1 )->startsWithTwoAsciiHexDigits() ) { // Validation error. ( $nullsafeVariable6 = $context->logger ) ? $nullsafeVariable6->notice( 'invalid-URL-unit', [ 'input' => (string) $input, 'column' => $i, ] ) : null; } } $percentEncoder = new PercentEncoder(); $output = $percentEncoder->percentEncodeAfterEncoding( 'utf-8', (string) $input, EncodeSet::C0_CONTROL ); return new StringHost( $output ); } /** * @return list<string> */ private function enumerateIdnaErrors( int $bitmask ): array { $reflection = new ReflectionClass( Idna::class ); $errorConstants = array_filter( $reflection->getConstants( ReflectionClassConstant::IS_PUBLIC ), static function ( string $name ): bool { return strncmp( $name, 'ERROR_', strlen( 'ERROR_' ) ) === 0; }, ARRAY_FILTER_USE_KEY ); $errors = []; foreach ( $errorConstants as $name => $value ) { if ( ( $value & $bitmask ) !== 0 ) { $errors[] = $name; } } return $errors; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/IPv4AddressParser.php 0000777 00000017505 15251156632 0026372 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; use Psr\Log\LogLevel; use Rowbot\URL\Component\Host\Math\NumberFactory; use Rowbot\URL\Component\Host\Math\NumberInterface; use Rowbot\URL\ParserContext; use Rowbot\URL\String\CodePoint; use Rowbot\URL\String\StringListInterface; use Rowbot\URL\String\USVStringInterface; use function array_pop; use function array_reduce; use function array_slice; use function count; use function strlen; use function strspn; /** * @see https://url.spec.whatwg.org/#concept-ipv4-parser */ class IPv4AddressParser { /** * @return IPv4Address|false */ public static function parse( ParserContext $context, USVStringInterface $input ) { // 1. Let parts be the result of strictly splitting input on U+002E (.). $parts = $input->split( '.' ); $count = $parts->count(); // 2. If the last item in parts is the empty string, then: if ( $parts->last()->isEmpty() ) { // 2.1. Validation error. ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->notice( 'IPv4-part-empty', [ 'input' => (string) $input, 'column' => $input->length() + 1, ] ) : null; // 2.2. If parts’s size is greater than 1, then remove the last item from parts. if ( $count > 1 ) { $parts->pop(); -- $count; } } // 3. If parts’s size is greater than 4, validation error, return failure. if ( $count > 4 ) { ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->warning( 'IPv4-too-many-parts', [ 'input' => (string) $input, 'column' => array_reduce( array_slice( array_merge( iterator_to_array( $parts ) ), 0, 4 ), static function ( int $carry, USVStringInterface $part ): int { return $carry + $part->length(); }, 4 ), ] ) : null; return false; } // 4. Let numbers be an empty list. $numbers = []; // 5. For each part of parts: foreach ( $parts as $i => $part ) { // 5.1. Let result be the result of parsing part. $result = self::parseIPv4Number( $part ); // 5.2. If result is failure, validation error, return failure. if ( $result === false ) { ( $nullsafeVariable3 = $context->logger ) ? $nullsafeVariable3->warning( 'IPv4-non-numeric-part', [ 'input' => (string) $input, 'column_range' => self::getColumnRange( $i, $parts ), ] ) : null; return false; } // 5.3. If result[1] is true, then set validationError to true. if ( $result[1] === true ) { // Validation error. ( $nullsafeVariable4 = $context->logger ) ? $nullsafeVariable4->notice( 'IPv4-non-decimal-part', [ 'input' => (string) $input, 'column_range' => self::getColumnRange( $i, $parts ), ] ) : null; } // 5.4. Append result[0] to numbers. $numbers[] = $result[0]; } $size = count( $numbers ); $sizeMinusOne = $size - 1; $failure = false; $errorQueue = []; // 6. If any item in numbers is greater than 255, validation error. foreach ( $numbers as $i => $number ) { if ( $number->isGreaterThan( 255 ) ) { // 7. If any but the last item in numbers is greater than 255, then return failure. if ( $i < $sizeMinusOne ) { $errorQueue[ $i ] = LogLevel::WARNING; $failure = true; break; } $errorQueue[ $i ] = LogLevel::NOTICE; break; } } $limit = NumberFactory::createNumber( 256, 10 )->pow( 5 - $size ); // 8. If the last item in numbers is greater than or equal to 256 ** (5 − numbers’s size), validation error, // return failure. if ( $numbers[ $sizeMinusOne ]->isGreaterThanOrEqualTo( $limit ) ) { $errorQueue[ $sizeMinusOne ] = LogLevel::WARNING; $failure = true; } foreach ( $errorQueue as $partIndex => $level ) { ( $nullsafeVariable5 = $context->logger ) ? $nullsafeVariable5->log( $level, 'IPv4-out-of-range-part', [ 'input' => (string) $input, 'column_range' => self::getColumnRange( $partIndex, $parts ), ] ) : null; } if ( $failure ) { return false; } /** * 9. Let ipv4 be the last item in numbers. * 10. Remove the last item from numbers. * * @var NumberInterface $ipv4 */ $ipv4 = array_pop( $numbers ); // 11. Let counter be 0. $counter = 0; // 12. For each n of numbers: foreach ( $numbers as $number ) { // 12.1. Increment ipv4 by n × 256 ** (3 − counter). $ipv4 = $ipv4->plus( $number->multipliedBy( 256 ** ( 3 - $counter ) ) ); // 12.2. Increment counter by 1. ++ $counter; } // 13. Return ipv4. return new IPv4Address( (string) $ipv4 ); } public static function endsInIPv4Number( USVStringInterface $input ): bool { // 1. Let parts be the result of strictly splitting input on U+002E (.). $parts = $input->split( '.' ); // 2. If the last item in parts is the empty string, then: if ( $parts->last()->isEmpty() ) { // 2.1. If parts’s size is 1, then return false. if ( $parts->count() === 1 ) { return false; } // 3.1. Remove the last item from parts. $parts->pop(); } // 3. Let last be the last item in parts. $last = $parts->last(); // 4. If parsing last as an IPv4 number does not return failure, then return true. if ( self::parseIPv4Number( $last ) !== false ) { return true; } // 5. If last is non-empty and contains only ASCII digits, then return true. // 6. Return false. return ! $last->isEmpty() && strspn( (string) $last, CodePoint::ASCII_DIGIT_MASK ) === $last->length(); } /** * @see https://url.spec.whatwg.org/#ipv4-number-parser * * @return array{0: NumberInterface, 1: bool}|false */ private static function parseIPv4Number( USVStringInterface $input ) { // 1. If input is the empty string, then return failure. if ( $input->isEmpty() ) { return false; } // 2. Let validationError be false. $validationError = false; // 3. Let R be 10. $radix = 10; if ( $input->length() > 1 ) { // 4. If input contains at least two code points and the first two code points are either "0x" or "0X", // then: if ( $input->startsWith( '0x' ) || $input->startsWith( '0X' ) ) { // 4.1. Set validationError to true. $validationError = true; // 4.2. Remove the first two code points from input. $input = $input->substr( 2 ); // 4.3. Set R to 16. $radix = 16; // 5. Otherwise, if input contains at least two code points and the first code point is U+0030 (0), then: } elseif ( $input->startsWith( '0' ) ) { // 5.1. Set validationError to true. $validationError = true; // 5.2. Remove the first code point from input. $input = $input->substr( 1 ); // 5.3. Set R to 8. $radix = 8; } } // 6. If input is the empty string, then return 0. if ( $input->isEmpty() ) { return [ NumberFactory::createNumber( 0, 10 ), $validationError ]; } $s = (string) $input; $length = strlen( $s ); // 7. If input contains a code point that is not a radix-R digit, then return failure. if ( ( $radix === 10 && strspn( $s, CodePoint::ASCII_DIGIT_MASK ) !== $length ) || ( $radix === 16 && strspn( $s, CodePoint::HEX_DIGIT_MASK ) !== $length ) || ( $radix === 8 && strspn( $s, CodePoint::OCTAL_DIGIT_MASK ) !== $length ) ) { return false; } // 8. Let output be the mathematical integer value that is represented by input in radix-R notation, using ASCII // hex digits for digits with values 0 through 15. // 9. Return (output, validationError). return [ NumberFactory::createNumber( $s, $radix ), $validationError ]; } /** * @return array{0: int, 1: int} */ private static function getColumnRange( int $i, StringListInterface $parts ): array { $partsArray = array_merge( iterator_to_array( $parts ) ); $offset = $i; for ( $j = 0; $j < $i; ++ $j ) { $offset += $partsArray[ $j ]->length(); } $targetPartLength = $partsArray[ $i ]->length(); return [ $offset + 1, $offset + $partsArray[ $i ]->length() + ( $targetPartLength > 0 ? 0 : 1 ) ]; } } DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Serializer/StringHostSerializer.php 0000777 00000000637 15251156632 0031313 0 ustar 00 php-toolkit <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Serializer; class StringHostSerializer implements HostSerializerInterface { /** * @var string */ private $string; public function __construct( string $string ) { $this->string = $string; } public function toFormattedString(): string { return $this->string; } public function toString(): string { return $this->string; } } DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Serializer/IPv6AddressSerializer.php 0000777 00000006552 15251156632 0031303 0 ustar 00 php-toolkit <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Serializer; use function dechex; /** * @see https://url.spec.whatwg.org/#concept-ipv6-serializer */ class IPv6AddressSerializer implements HostSerializerInterface { private const MAX_SIZE = 8; /** * @var non-empty-list<int> */ private $address; /** * @param non-empty-list<int> $address */ public function __construct( array $address ) { $this->address = $address; } public function toFormattedString(): string { return '[' . $this->toString() . ']'; } public function toString(): string { // 1. Let output be the empty string. $output = ''; // 2. Let compress be an index to the first IPv6 piece in the first longest sequences of address’s IPv6 pieces // that are 0. // 3. If there is no sequence of address’s IPv6 pieces that are 0 that is longer than 1, then set compress to // null. // 4. Let ignore0 be false. [ $compress, $longestSequence ] = $this->getCompressLocation(); $pieceIndex = 0; // 5. For each pieceIndex in the range 0 to 7, inclusive: do { // 5.3. If compress is pieceIndex, then: if ( $compress === $pieceIndex ) { // 5.3.1. Let separator be "::" if pieceIndex is 0, and U+003A (:) otherwise. // 5.3.2. Append separator to output. $output .= $pieceIndex === 0 ? '::' : ':'; // 5.3.3. Set ignore0 to true and continue. // // Advance the pointer to $compress + $longestSequence // to skip over all 16-bit pieces that are 0 that immediately // follow the piece at $compress. $pieceIndex = $compress + $longestSequence; continue; } // 5.4. Append address[pieceIndex], represented as the shortest possible lowercase hexadecimal number, to // output. // // Is it safe to assume this always returns lowercase letters? $output .= dechex( $this->address[ $pieceIndex ] ); // 5.5. If pieceIndex is not 7, then append U+003A (:) to output. if ( $pieceIndex < self::MAX_SIZE - 1 ) { $output .= ':'; } ++ $pieceIndex; } while ( $pieceIndex < self::MAX_SIZE ); // 6. Return output. return $output; } /** * Finds the longest sequence, with a length greater than 1, of 16-bit pieces that are 0 and * sets $compress to the first 16-bit piece in that sequence, otherwise $compress will remain * null. * * @return array{0: int|null, 1: int} The first item is the compress pointer, which indicates where in the address * it can start compression, or null if the address isn't compressable. The * second item is the length of the longest sequence of zeroes. */ private function getCompressLocation(): array { $longestSequence = 1; $compress = null; $i = 0; do { if ( $this->address[ $i ] !== 0 ) { continue; } $sequenceLength = 0; do { ++ $sequenceLength; ++ $i; } while ( $i < self::MAX_SIZE && $this->address[ $i ] === 0 ); // We are only interested in sequences with a length greater than one. We also only want // to note the first of those sequences since there may be multiple sequences of zero // that have the same length. if ( $sequenceLength > $longestSequence ) { $longestSequence = $sequenceLength; $compress = $i - $sequenceLength; } } while ( ++ $i < self::MAX_SIZE ); return [ $compress, $longestSequence ]; } } DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Serializer/HostSerializerInterface.php 0000777 00000000423 15251156632 0031736 0 ustar 00 php-toolkit <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Serializer; /** * @see https://url.spec.whatwg.org/#concept-host-serializer */ interface HostSerializerInterface { public function toFormattedString(): string; public function toString(): string; } DataLiberation/vendor-patched/rowbot/url/src/Component/Host/Serializer/IPv4AddressSerializer.php 0000777 00000002176 15251156632 0031277 0 ustar 00 php-toolkit <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host\Serializer; use Rowbot\URL\Component\Host\Math\NumberFactory; /** * @see https://url.spec.whatwg.org/#concept-ipv4-serializer */ class IPv4AddressSerializer implements HostSerializerInterface { /** * @var numeric-string */ private $address; /** * @param numeric-string $address */ public function __construct( string $address ) { $this->address = $address; } public function toFormattedString(): string { return $this->toString(); } public function toString(): string { // 1. Let output be the empty string. $output = ''; // 2. Let n be the value of address. $number = NumberFactory::createNumber( $this->address, 10 ); // 3. For each i in the range 1 to 4, inclusive: for ( $i = 0; $i < 4; ++ $i ) { // 3.1. Prepend n % 256, serialized, to output. $output = $number->mod( 256 ) . $output; // 3.2. If i is not 4, then prepend U+002E (.) to output. if ( $i < 3 ) { $output = '.' . $output; } // 3.3. Set n to floor(n / 256). $number = $number->intdiv( 256 ); } // 4. Return output. return $output; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/HostInterface.php 0000777 00000001373 15251156632 0025657 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; use Rowbot\URL\Component\Host\Serializer\HostSerializerInterface; /** * @see https://url.spec.whatwg.org/#concept-host */ interface HostInterface { /** * Checks if two hosts are equal. */ public function equals( self $other ): bool; /** * Returns a serializer for turning the underlying host into human readable text. */ public function getSerializer(): HostSerializerInterface; /** * Checks if the host is an empty string. */ public function isEmpty(): bool; /** * Checks if the host matches the string "localhost". */ public function isLocalHost(): bool; /** * Checks if the implementor is considered a null host. */ public function isNull(): bool; } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Host/IPv6AddressParser.php 0000777 00000025226 15251156632 0026373 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component\Host; use Rowbot\URL\ParserContext; use Rowbot\URL\String\CodePoint; use Rowbot\URL\String\StringIteratorInterface; use Rowbot\URL\String\USVStringInterface; use function array_is_list; use function assert; use function intval; use function strpbrk; use function strpos; use function strrpos; /** * @see https://url.spec.whatwg.org/#concept-ipv6-parser */ class IPv6AddressParser { /** * @return IPv6Address|false */ public static function parse( ParserContext $context, USVStringInterface $input ) { // 1. Let address be a new IPv6 address whose IPv6 pieces are all 0. $address = [ 0, 0, 0, 0, 0, 0, 0, 0 ]; // 2. Let pieceIndex be 0. $pieceIndex = 0; // 3. Let compress be null. $compress = null; // 4. Let pointer be a pointer for input. $iter = $input->getIterator(); $iter->rewind(); // 5. If c is U+003A (:), then: if ( $iter->current() === ':' ) { // 5.1. If remaining does not start with U+003A (:), validation error, return failure. if ( $iter->peek() !== ':' ) { ( $nullsafeVariable1 = $context->logger ) ? $nullsafeVariable1->warning( 'IPv6-invalid-compression', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } // 5.2. Increase pointer by 2. $iter->seek( 2 ); // 5.3. Increase pieceIndex by 1 and then set compress to pieceIndex. $compress = ++ $pieceIndex; } // 6. While c is not the EOF code point: while ( $iter->valid() ) { // 6.1. If pieceIndex is 8, validation error, return failure. if ( $pieceIndex === 8 ) { ( $nullsafeVariable2 = $context->logger ) ? $nullsafeVariable2->warning( 'IPv6-too-many-pieces', [ 'input' => (string) $input, 'column' => $iter->key(), ] ) : null; return false; } // 6.2. If c is U+003A (:), then: if ( $iter->current() === ':' ) { // 6.2.1. If compress is non-null, validation error, return failure. if ( $compress !== null ) { ( $nullsafeVariable3 = $context->logger ) ? $nullsafeVariable3->warning( 'IPv6-multiple-compression', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } // 6.2.2. Increase pointer and pieceIndex by 1, set compress to pieceIndex, and then continue. $iter->next(); $compress = ++ $pieceIndex; continue; } // 6.3. Let value and length be 0. $value = 0; $length = 0; $current = $iter->current(); // 6.4. While length is less than 4 and c is an ASCII hex digit, set value to value × 0x10 + c interpreted // as hexadecimal number, and increase pointer and length by 1. while ( $length < 4 && strpbrk( $current, CodePoint::HEX_DIGIT_MASK ) === $current ) { $value = ( $value * 0x10 ) + intval( $current, 16 ); $iter->next(); ++ $length; $current = $iter->current(); } // 6.5. If c is U+002E (.), then: if ( $iter->current() === '.' ) { // 6.5.1. If length is 0, validation error, return failure. if ( $length === 0 ) { ( $nullsafeVariable4 = $context->logger ) ? $nullsafeVariable4->warning( 'IPv4-in-IPv6-invalid-code-point', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } // 6.5.2. Decrease pointer by length. $iter->seek( - $length ); // 6.5.3. If pieceIndex is greater than 6, validation error, return failure. if ( $pieceIndex > 6 ) { ( $nullsafeVariable5 = $context->logger ) ? $nullsafeVariable5->warning( 'IPv4-in-IPv6-too-many-pieces', [ 'input' => (string) $input, 'column' => $iter->key(), ] ) : null; return false; } $result = self::parseIPv4Address( $context, $input, $iter, $address, $pieceIndex ); if ( $result === false ) { return false; } [ $address, $pieceIndex ] = $result; // 6.5.7. Break. break; } // 6.6. Otherwise, if c is U+003A (:): if ( $iter->current() === ':' ) { // 6.6.1. Increase pointer by 1. $iter->next(); // 6.2.2. If c is the EOF code point, validation error, return failure. if ( ! $iter->valid() ) { ( $nullsafeVariable6 = $context->logger ) ? $nullsafeVariable6->warning( 'IPv6-invalid-code-point', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } // 6.7. Otherwise, if c is not the EOF code point, validation error, return failure. } elseif ( $iter->valid() ) { ( $nullsafeVariable7 = $context->logger ) ? $nullsafeVariable7->warning( 'IPv6-invalid-code-point', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } // 6.8. Set address[pieceIndex] to value. // 6.9. Increase pieceIndex by 1. $address[ $pieceIndex ++ ] = $value; } // 7. If compress is non-null, then: if ( $compress !== null ) { // 7.1. Let swaps be pieceIndex − compress. $swaps = $pieceIndex - $compress; // 7.2. Set pieceIndex to 7. $pieceIndex = 7; // 7.3. While pieceIndex is not 0 and swaps is greater than 0, swap address[pieceIndex] with // address[compress + swaps − 1], and then decrease both pieceIndex and swaps by 1. while ( $pieceIndex !== 0 && $swaps > 0 ) { $temp = $address[ $pieceIndex ]; $address[ $pieceIndex ] = $address[ $compress + $swaps - 1 ]; $address[ $compress + $swaps - 1 ] = $temp; -- $pieceIndex; -- $swaps; } // Otherwise, if compress is null and pieceIndex is not 8, validation error, return failure. } elseif ( $pieceIndex !== 8 ) { ( $nullsafeVariable8 = $context->logger ) ? $nullsafeVariable8->warning( 'IPv6-too-few-pieces', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } $arrayIsListFunction = function ( array $array ): bool { if ( function_exists( 'array_is_list' ) ) { return array_is_list( $array ); } if ( $array === [] ) { return true; } $current_key = 0; foreach ( $array as $key => $noop ) { if ( $key !== $current_key ) { return false; } ++ $current_key; } return true; }; assert( $arrayIsListFunction( $address ) ); // 9. Return address. return new IPv6Address( $address ); } /** * @param non-empty-list<int> $address * * @return array{0: non-empty-list<int>, 1: int}|false */ private static function parseIPv4Address( ParserContext $context, USVStringInterface $input, StringIteratorInterface $iter, array $address, int $pieceIndex ) { // 6.5.4. Let numbersSeen be 0. $numbersSeen = 0; // 6.5.5. While c is not the EOF code point: do { // 6.5.5.1. Let ipv4Piece be null. $ipv4Piece = null; // 6.5.5.2. If numbersSeen is greater than 0, then: if ( $numbersSeen > 0 ) { // 6.5.5.2.2 Otherwise, validation error, return failure. if ( $iter->current() !== '.' || $numbersSeen >= 4 ) { // Validation error. ( $nullsafeVariable9 = $context->logger ) ? $nullsafeVariable9->warning( 'IPv4-in-IPv6-invalid-code-point', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } // 6.5.5.2.1 If c is a U+002E (.) and numbersSeen is less than 4, then increase pointer by 1. $iter->next(); } $current = $iter->current(); // 6.5.5.3. If c is not an ASCII digit, validation error, return failure. if ( strpbrk( $current, CodePoint::ASCII_DIGIT_MASK ) !== $current ) { // Validation error. ( $nullsafeVariable10 = $context->logger ) ? $nullsafeVariable10->warning( 'IPv4-in-IPv6-invalid-code-point', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } // 6.5.5.4. While c is an ASCII digit: do { // 6.5.5.4.1. Let number be c interpreted as decimal number. $number = (int) $current; // 6.5.5.4.2. If ipv4Piece is null, then set ipv4Piece to number. if ( $ipv4Piece === null ) { $ipv4Piece = $number; // Otherwise, if ipv4Piece is 0, validation error, return failure. } elseif ( $ipv4Piece === 0 ) { // Validation error. ( $nullsafeVariable11 = $context->logger ) ? $nullsafeVariable11->warning( 'IPv4-in-IPv6-invalid-code-point', [ 'input' => (string) $input, 'column' => $iter->key(), ] ) : null; return false; // Otherwise, set ipv4Piece to ipv4Piece × 10 + number. } else { $ipv4Piece = ( $ipv4Piece * 10 ) + $number; } // 6.5.5.4.3. If ipv4Piece is greater than 255, validation error, return failure. if ( $ipv4Piece > 255 ) { // Validation error. ( $nullsafeVariable12 = $context->logger ) ? $nullsafeVariable12->warning( 'IPv4-in-IPv6-out-of-range-part', [ 'input' => (string) $input, 'column_range' => ( static function () use ( $input, $iter, $numbersSeen ): array { $str = (string) $input; $delimiter = $numbersSeen === 0 ? ':' : '.'; $startIndex = strrpos( $str, $delimiter, - ( $input->length() - $iter->key() ) ); $endIndex = strpos( $str, '.', $iter->key() ); if ( $startIndex === false ) { $startIndex = $iter->key(); } if ( $endIndex === false ) { $endIndex = $input->length(); } return [ $startIndex + 2, $endIndex ]; } )(), ] ) : null; return false; } // 6.5.5.4.4. Increase pointer by 1. $iter->next(); $current = $iter->current(); } while ( strpbrk( $current, CodePoint::ASCII_DIGIT_MASK ) === $current ); // 6.5.5.5. Set address[pieceIndex] to address[pieceIndex] × 0x100 + ipv4Piece. $piece = $address[ $pieceIndex ]; $address[ $pieceIndex ] = ( $piece * 0x100 ) + $ipv4Piece; // 6.5.5.6. Increase numbersSeen by 1. ++ $numbersSeen; // 6.5.5.7. If numbersSeen is 2 or 4, then increase pieceIndex by 1. if ( $numbersSeen === 2 || $numbersSeen === 4 ) { ++ $pieceIndex; } } while ( $iter->valid() ); // 6.5.6. If numbersSeen is not 4, validation error, return failure. if ( $numbersSeen !== 4 ) { // Validation error. ( $nullsafeVariable13 = $context->logger ) ? $nullsafeVariable13->warning( 'IPv4-in-IPv6-too-few-parts', [ 'input' => (string) $input, 'column' => $iter->key() + 1, ] ) : null; return false; } $arrayIsListFunction = function ( array $array ): bool { if ( function_exists( 'array_is_list' ) ) { return array_is_list( $array ); } if ( $array === [] ) { return true; } $current_key = 0; foreach ( $array as $key => $noop ) { if ( $key !== $current_key ) { return false; } ++ $current_key; } return true; }; assert( $arrayIsListFunction( $address ) ); return [ $address, $pieceIndex ]; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/OpaqueOrigin.php 0000777 00000001362 15251156632 0024604 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use Rowbot\URL\Origin; /** * @see https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-opaque */ final class OpaqueOrigin implements Origin { public function getEffectiveDomain(): ?string { return null; } public function isOpaque(): bool { return true; } /** * @param Origin $other */ public function isSameOrigin( $other ): bool { return $this === $other; } /** * @param Origin $other */ public function isSameOriginDomain( $other ): bool { return $this === $other; } /** * @see https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin */ public function __toString(): string { return 'null'; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/PathList.php 0000777 00000001604 15251156632 0023731 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use function array_pop; use function count; use function implode; class PathList extends AbstractPath { public function isOpaque(): bool { return false; } public function push( PathSegment $path ): void { $this->list[] = $path; } public function shorten( Scheme $scheme ): void { // 3. If url’s scheme is "file", path’s size is 1, and path[0] is a normalized Windows drive letter, then // return. if ( $scheme->isFile() && count( $this->list ) === 1 && $this->list[0]->isNormalizedWindowsDriveLetter() ) { return; } // 4. Remove path’s last item, if any. array_pop( $this->list ); } /** * @see https://url.spec.whatwg.org/#url-path-serializer */ public function __toString(): string { if ( ! isset( $this->list[0] ) ) { return ''; } return '/' . implode( '/', $this->list ); } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/QueryList.php 0000777 00000022122 15251156632 0024140 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use ArrayIterator; use Countable; use IteratorAggregate; use Rowbot\URL\String\EncodeSet; use Rowbot\URL\String\PercentEncoder; use Rowbot\URL\String\Utf8String; use Rowbot\URL\Support\EncodingHelper; use function array_column; use function array_filter; use function array_splice; use function count; use function explode; use function mb_ord; use function mb_str_split; use function rawurldecode; use function str_replace; use function usort; /** * @implements IteratorAggregate<int, array{name: string, value: string}> */ class QueryList implements Countable, IteratorAggregate { private const LEAD_OFFSET = 0xD800 - ( 0x10000 >> 10 ); /** * @var array<string, bool> */ private $cache; /** * @var array<int, array{name: string, value: string}> */ private $list; /** * @param array<int, array{name: string, value: string}> $list */ public function __construct( array $list = [] ) { $this->list = $list; $this->cache = []; } /** * Decodes a application/x-www-form-urlencoded string and returns the decoded pairs as a list. * * Note: A legacy server-oriented implementation might have to support encodings other than * UTF-8 as well as have special logic for tuples of which the name is `_charset_`. Such logic * is not described here as only UTF-8' is conforming. * * @see https://url.spec.whatwg.org/#concept-urlencoded-parser */ public static function fromString( string $input ): self { // Let sequences be the result of splitting input on 0x26 (&). $sequences = explode( '&', $input ); // Let output be an initially empty list of name-value tuples where both name and value // hold a string. $output = new self(); foreach ( $sequences as $bytes ) { if ( $bytes === '' ) { continue; } // If bytes contains a 0x3D (=), then let name be the bytes from the start of bytes up // to but excluding its first 0x3D (=), and let value be the bytes, if any, after the // first 0x3D (=) up to the end of bytes. If 0x3D (=) is the first byte, then name will // be the empty byte sequence. If it is the last, then value will be the empty byte // sequence. Otherwise, let name have the value of bytes and let value be the empty byte // sequence. $name = $bytes; $value = ''; if ( strpos( $bytes, '=' ) !== false ) { [ $name, $value ] = explode( '=', $bytes, 2 ); } // Replace any 0x2B (+) in name and value with 0x20 (SP). [ $name, $value ] = str_replace( '+', "\x20", [ $name, $value ] ); // Let nameString and valueString be the result of running UTF-8 // decode without BOM on the percent decoding of name and value, // respectively. $output->append( Utf8String::transcode( rawurldecode( $name ), 'utf-8', 'utf-8' ), Utf8String::transcode( rawurldecode( $value ), 'utf-8', 'utf-8' ) ); } return $output; } /** * Appends a new name-value pair to the list. */ public function append( string $name, string $value ): void { $this->list[] = [ 'name' => $name, 'value' => $value ]; $this->cache[ $name ] = true; } public function count(): int { return count( $this->list ); } /** * Determines if a name-value pair with name $name exists in the collection. */ public function contains( string $name, ?string $value = null ): bool { $hasTuple = isset( $this->cache[ $name ] ); if ( $value === null || ! $hasTuple ) { return $hasTuple; } foreach ( $this->list as $tuple ) { if ( $name === $tuple['name'] && $value === $tuple['value'] ) { return true; } } return false; } /** * Returns a filtered array based on the given callback. * * @return array<int, array<string, string>> */ public function filter( callable $callback ): array { return array_filter( $this->list, $callback === null ? function ( $value, $key ): bool { return ! empty( $value ); } : $callback, $callback === null ? ARRAY_FILTER_USE_BOTH : 0 ); } /** * Returns the first name-value pair in the list whose name is $name. */ public function first( string $name ): ?string { foreach ( $this->list as $pair ) { if ( $pair['name'] === $name ) { return $pair['value']; } } return null; } /** * @return ArrayIterator<int, array{name: string, value: string}> */ public function getIterator(): ArrayIterator { return new ArrayIterator( $this->list ); } /** * @return array{name: string, value: string}|null */ public function getTupleAt( int $index ): ?array { return $this->list[ $index ] ?? null; } /** * Removes all name-value pairs with name $name from the list. */ public function remove( string $name, ?string $value ): void { $seen = 0; $removed = 0; for ( $i = count( $this->list ) - 1; $i >= 0; -- $i ) { if ( $this->list[ $i ]['name'] === $name ) { ++ $seen; if ( $value !== null && $this->list[ $i ]['value'] !== $value ) { continue; } array_splice( $this->list, $i, 1 ); ++ $removed; } } if ( $seen === $removed ) { unset( $this->cache[ $name ] ); } } /** * Sets the value of the first name-value pair with $name to $value and * removes all other occurances that have name $name. */ public function set( string $name, string $value ): void { $prevIndex = null; for ( $i = count( $this->list ) - 1; $i >= 0; -- $i ) { if ( $this->list[ $i ]['name'] === $name ) { if ( $prevIndex !== null ) { array_splice( $this->list, $prevIndex, 1 ); } $prevIndex = $i; } } if ( $prevIndex === null ) { return; } $this->list[ $prevIndex ]['value'] = $value; } /** * Sorts the collection by code units and preserves the relative positioning * of name-value pairs. */ public function sort(): void { $temp = []; foreach ( $this->list as $pair ) { $codeUnits = $this->convertToCodeUnits( $pair['name'] ); $temp[] = [ 'original' => $pair, 'codeUnits' => $codeUnits, 'length' => count( $codeUnits ) ]; } // Sorting priority overview: // // Each string is compared code unit by code unit against each other. // // 1) If the two strings have different lengths, and the strings are equal up to the end of // the shortest string, then the shorter of the two strings will be moved up in the // array. (e.g. The string "aa" will come before the string "aaa".) // 2) If the value of the code units differ, the character with the lower code unit will be // moved up in the array. (e.g. "🌈" will come before "ffi". Although "🌈" has a code // point value of 127,752 that is greater than the "ffi" code point value of 64,259, "🌈" // is split in to 2 code units and it's first code unit has a value of 55,356, which is // less than the "ffi" single code unit value of 64,259.) // 3) If the two strings are considered equal, then they are sorted by the relative // position in which they appeared in the array. (e.g. The string "b=c&a=c&b=a&a=a" // becomes "a=c&a=a&b=c&b=a".) usort( $temp, static function ( array $a, array $b ): int { $aCodeUnits = $a['codeUnits']; $bCodeUnits = $b['codeUnits']; $lengthComparison = $a['length'] <=> $b['length']; if ( $lengthComparison === 0 ) { return $aCodeUnits <=> $bCodeUnits; } $shortestLength = $lengthComparison < 0 ? $a['length'] : $b['length']; for ( $i = 0; $i < $shortestLength; ++ $i ) { $comparison = $aCodeUnits[ $i ] <=> $bCodeUnits[ $i ]; if ( $comparison !== 0 ) { return $comparison; } } return $lengthComparison; } ); $this->list = array_column( $temp, 'original' ); } /** * Encodes the list of tuples as a valid application/x-www-form-urlencoded string. * * @see https://url.spec.whatwg.org/#concept-urlencoded-serializer */ public function toUrlencodedString( ?string $encodingOverride = null ): string { $encoding = EncodingHelper::getOutputEncoding( $encodingOverride ) ?? 'utf-8'; $output = ''; $percentEncoder = new PercentEncoder(); foreach ( $this->list as $key => $tuple ) { $name = $percentEncoder->percentEncodeAfterEncoding( $encoding, $tuple['name'], EncodeSet::X_WWW_URLENCODED, true ); $value = $percentEncoder->percentEncodeAfterEncoding( $encoding, $tuple['value'], EncodeSet::X_WWW_URLENCODED, true ); if ( $key > 0 ) { $output .= '&'; } $output .= $name . '=' . $value; } return $output; } /** * @see https://www.unicode.org/faq/utf_bom.html?source=post_page---------------------------#utf16-4 * * @return list<non-empty-list<int>> */ private function convertToCodeUnits( string $input ): array { $codeUnits = []; foreach ( mb_str_split( $input, 1, 'utf-8' ) as $strCodePoint ) { $codePoint = mb_ord( $strCodePoint, 'utf-8' ); // Code points less than 0x10000 are part of the Basic Multilingual Plane and are // represented by a single code unit that is equal to its code point. Use 0 as the low // surrogate as the <=> operator compares array size first and values second. $codeUnits[] = $codePoint < 0x10000 ? [ $codePoint, 0 ] : [ self::LEAD_OFFSET + ( $codePoint >> 10 ), 0xDC00 + ( $codePoint & 0x3FF ) ]; } return $codeUnits; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/src/Component/Scheme.php 0000777 00000002416 15251156632 0023407 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\URL\Component; use function array_key_exists; class Scheme { /** * @see https://url.spec.whatwg.org/#special-scheme */ private const SPECIAL_SCHEMES = [ 'ftp' => 21, 'file' => null, 'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443, ]; /** * @var string */ private $scheme; public function __construct( string $scheme = '' ) { $this->scheme = $scheme; } public function isBlob(): bool { return $this->scheme === 'blob'; } public function isFile(): bool { return $this->scheme === 'file'; } public function isWebsocket(): bool { return $this->scheme === 'wss' || $this->scheme === 'ws'; } /** * Returns whether or not the string is a special scheme. * * @see https://url.spec.whatwg.org/#is-special */ public function isSpecial(): bool { return array_key_exists( $this->scheme, self::SPECIAL_SCHEMES ); } public function isDefaultPort( ?int $port ): bool { if ( ! isset( self::SPECIAL_SCHEMES[ $this->scheme ] ) ) { return false; } return self::SPECIAL_SCHEMES[ $this->scheme ] === $port; } public function equals( self $other ): bool { return $this->scheme === $other->scheme; } public function __toString(): string { return $this->scheme; } } php-toolkit/DataLiberation/vendor-patched/rowbot/url/phpunit.xml 0000777 00000001055 15251156632 0021150 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticProperties="false" bootstrap="vendor/autoload.php" cacheDirectory=".phpunit.cache" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"> <source> <include> <directory suffix=".php">./src</directory> </include> </source> <testsuites> <testsuite name="Full Test Suite"> <directory>./tests</directory> </testsuite> </testsuites> </phpunit> php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/LICENSE 0000777 00000002061 15251156632 0020766 0 ustar 00 MIT License Copyright (c) 2020 Trevor Rowbotham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/phpunit.xml 0000777 00000001365 15251156632 0022200 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/5.7/phpunit.xsd" bootstrap="vendor/autoload.php" backupGlobals="false" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTestsThatDoNotTestAnything="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> <filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter> </phpunit> php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/CHANGELOG.md 0000777 00000000745 15251156632 0021601 0 ustar 00 # Changelog ## [Unreleased] ## [1.0.4] - 2024-05-02 ### Fixed - Fix implicit nullable deprecation warning for PHP 8.4 ## [1.0.3] - 2022-01-10 ### Changed - Update PHPstan to 1.0 - Switched to GitHub Actions ## [1.0.2] - 2020-07-15 ### Added - Minor performance improvements ## [1.0.1] - 2020-06-16 ### Fixed - When calling `Punycode::decode()`, the case flags array would fail to populate when given an empty array. ## [1.0.0] - 2020-06-09 - Initial release php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/composer.json 0000777 00000001501 15251156632 0022501 0 ustar 00 { "name": "rowbot/punycode", "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA).", "keywords": [ "punycode", "rfc3492", "rfc-3492" ], "type": "library", "license": "MIT", "authors": [ { "name": "Trevor Rowbotham", "role": "Developer", "homepage": "https://trowbotham.com" } ], "config": { "sort-packages": true }, "autoload": { "psr-4": { "Rowbot\\Punycode\\": "src/" } }, "autoload-dev": { "psr-4": { "Rowbot\\Punycode\\Test\\": "tests/" } }, "require": { "php": ">=7.1" }, "require-dev": { "ext-mbstring": "*", "phpstan/phpstan": "^1.2", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", "squizlabs/php_codesniffer": "^3.5.1" } } php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/src/Punycode.php 0000777 00000026703 15251156632 0023060 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Punycode; use Rowbot\Punycode\Exception\InvalidInputException; use Rowbot\Punycode\Exception\OutputSizeExceededException; use Rowbot\Punycode\Exception\OverflowException; use function array_map; use function array_splice; use function chr; use function func_num_args; use function implode; use function intdiv; use function ord; use function str_split; use function strlen; use function strrpos; /** * @see https://tools.ietf.org/html/rfc3492 * @see https://github.com/bestiejs/punycode.js/blob/master/punycode.js * @see https://github.com/unicode-org/icu/blob/master/icu4c/source/common/punycode.cpp * @see https://github.com/unicode-org/icu/blob/master/icu4j/main/classes/core/src/com/ibm/icu/impl/Punycode.java */ final class Punycode { private const BASE = 36; private const TMIN = 1; private const TMAX = 26; private const SKEW = 38; private const DAMP = 700; private const INITIAL_BIAS = 72; private const INITIAL_N = 128; private const DELIMITER = '-'; private const MAX_INT = 2147483647; /** * Contains the numeric value of a basic code point (for use in representing integers) in the * range 0 to BASE-1, or -1 if b is does not represent a value. * * @var array<int, int> */ private static $basicToDigit = [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 1, - 1, - 1, - 1, - 1, - 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, ]; /** * @codeCoverageIgnore */ private function __construct() { } /** * @see https://tools.ietf.org/html/rfc3492#section-6.1 */ private static function adaptBias( int $delta, int $numPoints, bool $firstTime ): int { // xxx >> 1 is a faster way of doing intdiv(xxx, 2) $delta = $firstTime ? intdiv( $delta, self::DAMP ) : $delta >> 1; $delta += intdiv( $delta, $numPoints ); $k = 0; while ( $delta > ( ( self::BASE - self::TMIN ) * self::TMAX ) >> 1 ) { $delta = intdiv( $delta, self::BASE - self::TMIN ); $k += self::BASE; } return $k + intdiv( ( self::BASE - self::TMIN + 1 ) * $delta, $delta + self::SKEW ); } /** * @see https://tools.ietf.org/html/rfc3492#section-6.2 * * @param array<int, bool> $caseFlags */ public static function decode( string $input, ?int $outLength = null, array &$caseFlags = [] ): string { $n = self::INITIAL_N; $out = 0; $i = 0; $maxOut = $outLength ?? self::MAX_INT; $bias = self::INITIAL_BIAS; $lastDelimIndex = strrpos( $input, self::DELIMITER ); $b = $lastDelimIndex === false ? 0 : $lastDelimIndex; $inputLength = strlen( $input ); $output = []; $wantsCaseFlags = func_num_args() > 2; if ( $b > $maxOut ) { throw new OutputSizeExceededException(); } $bytes = array_map( 'ord', str_split( $input ) ); for ( $j = 0; $j < $b; ++ $j ) { if ( $bytes[ $j ] > 0x7F ) { throw new InvalidInputException(); } if ( $wantsCaseFlags ) { $caseFlags[ $out ] = self::flagged( $bytes[ $j ] ); } $output[ $out ++ ] = $input[ $j ]; } if ( $b > 0 ) { $b += 1; } for ( $in = $b; $in < $inputLength; ++ $out ) { $oldi = $i; $w = 1; for ( $k = self::BASE; ; $k += self::BASE ) { if ( $in >= $inputLength ) { throw new InvalidInputException(); } $digit = self::$basicToDigit[ $bytes[ $in ++ ] & 0xFF ]; if ( $digit < 0 ) { throw new InvalidInputException(); } if ( $digit > intdiv( self::MAX_INT - $i, $w ) ) { throw new OverflowException(); } $i += $digit * $w; if ( $k <= $bias ) { $t = self::TMIN; } elseif ( $k >= $bias + self::TMAX ) { $t = self::TMAX; } else { $t = $k - $bias; } if ( $digit < $t ) { break; } $baseMinusT = self::BASE - $t; if ( $w > intdiv( self::MAX_INT, $baseMinusT ) ) { throw new OverflowException(); } $w *= $baseMinusT; } $outPlusOne = $out + 1; $bias = self::adaptBias( $i - $oldi, $outPlusOne, $oldi === 0 ); if ( intdiv( $i, $outPlusOne ) > self::MAX_INT - $n ) { throw new OverflowException(); } $n += intdiv( $i, $outPlusOne ); $i %= $outPlusOne; if ( $out >= $maxOut ) { throw new OutputSizeExceededException(); } if ( $wantsCaseFlags ) { array_splice( $caseFlags, $i, 0, [ self::flagged( $bytes[ $n - 1 ] ) ] ); } array_splice( $output, $i ++, 0, [ self::encodeCodePoint( $n ) ] ); } return implode( '', $output ); } /** * @see https://tools.ietf.org/html/rfc3492#section-6.3 * * @param array<int, bool> $caseFlags */ public static function encode( string $input, ?int $outLength = null, array $caseFlags = [] ): string { $n = self::INITIAL_N; $delta = 0; $out = 0; $maxOut = $outLength ?? self::MAX_INT; $bias = self::INITIAL_BIAS; $inputLength = 0; $output = ''; $codePoints = self::utf8Decode( $input ); foreach ( $codePoints as $j => $codePoint ) { ++ $inputLength; if ( $codePoint < 0x80 ) { if ( $maxOut - $out < 2 ) { throw new OutputSizeExceededException(); } $output .= isset( $caseFlags[ $j ] ) ? self::encodeBasic( $codePoint, $caseFlags[ $j ] ) : chr( $codePoint ); ++ $out; } } $h = $out; $b = $out; if ( $b > 0 ) { $output .= self::DELIMITER; ++ $out; } while ( $h < $inputLength ) { $m = self::MAX_INT; foreach ( $codePoints as $codePoint ) { if ( $codePoint >= $n && $codePoint < $m ) { $m = $codePoint; } } if ( $m - $n > intdiv( self::MAX_INT - $delta, $h + 1 ) ) { throw new OverflowException(); } $delta += ( $m - $n ) * ( $h + 1 ); $n = $m; foreach ( $codePoints as $j => $codePoint ) { if ( $codePoint < $n && ++ $delta === 0 ) { throw new OverflowException(); } elseif ( $codePoint === $n ) { $q = $delta; for ( $k = self::BASE; ; $k += self::BASE ) { if ( $out >= $maxOut ) { throw new OutputSizeExceededException(); } if ( $k <= $bias ) { $t = self::TMIN; } elseif ( $k >= $bias + self::TMAX ) { $t = self::TMAX; } else { $t = $k - $bias; } if ( $q < $t ) { break; } $qMinusT = $q - $t; $baseMinusT = self::BASE - $t; $output .= self::encodeDigit( $t + ( $qMinusT ) % ( $baseMinusT ), false ); ++ $out; $q = intdiv( $qMinusT, $baseMinusT ); } $output .= self::encodeDigit( $q, $caseFlags[ $j ] ?? false ); ++ $out; $bias = self::adaptBias( $delta, $h + 1, $h === $b ); $delta = 0; ++ $h; } } ++ $delta; ++ $n; } return $output; } private static function encodeBasic( int $codePoint, bool $flag ): string { $codePoint -= ( $codePoint - 97 < 26 ? 1 : 0 ) << 5; return chr( $codePoint + ( ( ! $flag && ( $codePoint - 65 < 26 ) ? 1 : 0 ) << 5 ) ); } /** * Takes a Unicode code point and encodes it. The return behavior is undefined if the given * code point is outside the range 0..10FFFF. * * @see https://encoding.spec.whatwg.org/#utf-8-encoder */ private static function encodeCodePoint( int $codePoint ): string { if ( $codePoint >= 0x00 && $codePoint <= 0x7F ) { return chr( $codePoint ); } $count = 0; $offset = 0; if ( $codePoint >= 0x0080 && $codePoint <= 0x07FF ) { $count = 1; $offset = 0xC0; } elseif ( $codePoint >= 0x0800 && $codePoint <= 0xFFFF ) { $count = 2; $offset = 0xE0; } elseif ( $codePoint >= 0x10000 && $codePoint <= 0x10FFFF ) { $count = 3; $offset = 0xF0; } $bytes = chr( ( $codePoint >> ( 6 * $count ) ) + $offset ); while ( $count > 0 ) { $temp = $codePoint >> ( 6 * ( $count - 1 ) ); $bytes .= chr( 0x80 | ( $temp & 0x3F ) ); -- $count; } return $bytes; } private static function encodeDigit( int $d, bool $flag ): string { return chr( $d + 22 + 75 * ( $d < 26 ? 1 : 0 ) - ( ( $flag ? 1 : 0 ) << 5 ) ); } private static function flagged( int $codePoint ): bool { return $codePoint - 65 < 26; } /** * Takes a UTF-8 encoded string and converts it into a series of integer code points. Any * invalid byte sequences will be replaced by a U+FFFD replacement code point. * * @see https://encoding.spec.whatwg.org/#utf-8-decoder * * @return array<int, int> */ private static function utf8Decode( string $input ): array { $bytesSeen = 0; $bytesNeeded = 0; $lowerBoundary = 0x80; $upperBoundary = 0xBF; $codePoint = 0; $codePoints = []; $length = strlen( $input ); for ( $i = 0; $i < $length; ++ $i ) { $byte = ord( $input[ $i ] ); if ( $bytesNeeded === 0 ) { if ( $byte <= 0x7F ) { $codePoints[] = $byte; continue; } if ( $byte >= 0xC2 && $byte <= 0xDF ) { $bytesNeeded = 1; $codePoint = $byte & 0x1F; } elseif ( $byte >= 0xE0 && $byte <= 0xEF ) { if ( $byte === 0xE0 ) { $lowerBoundary = 0xA0; } elseif ( $byte === 0xED ) { $upperBoundary = 0x9F; } $bytesNeeded = 2; $codePoint = $byte & 0xF; } elseif ( $byte >= 0xF0 && $byte <= 0xF4 ) { if ( $byte === 0xF0 ) { $lowerBoundary = 0x90; } elseif ( $byte === 0xF4 ) { $upperBoundary = 0x8F; } $bytesNeeded = 3; $codePoint = $byte & 0x7; } else { $codePoints[] = 0xFFFD; } continue; } if ( $byte < $lowerBoundary || $byte > $upperBoundary ) { $codePoint = 0; $bytesNeeded = 0; $bytesSeen = 0; $lowerBoundary = 0x80; $upperBoundary = 0xBF; -- $i; $codePoints[] = 0xFFFD; continue; } $lowerBoundary = 0x80; $upperBoundary = 0xBF; $codePoint = ( $codePoint << 6 ) | ( $byte & 0x3F ); if ( ++ $bytesSeen !== $bytesNeeded ) { continue; } $codePoints[] = $codePoint; $codePoint = 0; $bytesNeeded = 0; $bytesSeen = 0; } // String unexpectedly ended, so append a U+FFFD code point. if ( $bytesNeeded !== 0 ) { $codePoints[] = 0xFFFD; } return $codePoints; } } DataLiberation/vendor-patched/rowbot/punycode/src/Exception/OutputSizeExceededException.php 0000777 00000000211 15251156632 0030574 0 ustar 00 php-toolkit <?php declare( strict_types=1 ); namespace Rowbot\Punycode\Exception; class OutputSizeExceededException extends PunycodeException { } php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/src/Exception/OverflowException.php 0000777 00000000205 15251156632 0026677 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Punycode\Exception; final class OverflowException extends PunycodeException { } php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/src/Exception/PunycodeException.php 0000777 00000000207 15251156632 0026664 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Punycode\Exception; use Exception; class PunycodeException extends Exception { } php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/src/Exception/InvalidInputException.php 0000777 00000000203 15251156632 0027500 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Punycode\Exception; class InvalidInputException extends PunycodeException { } php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/README.md 0000777 00000006743 15251156632 0021253 0 ustar 00 # Punycode [](https://github.com/TRowbotham/punycode/blob/master/LICENSE) [](https://travis-ci.com/github/TRowbotham/punycode) [](https://codecov.io/gh/TRowbotham/punycode) [](https://packagist.org/packages/rowbot/punycode) [](https://packagist.org/packages/rowbot/punycode) An implementation of RFC 3492 Punycode in PHP, based on the sample implementation in [Appendix C](https://tools.ietf.org/html/rfc3492#appendix-C). This is NOT a substitue for `idn_to_ascii` and `idn_to_utf8`. ## Requirements - PHP 7.1+ ## Installation ```bash composer require rowbot/punycode ``` ## API ### Punycode::decode(string $input, int $outLength = null, array &$caseFlags = []) The `Punycode::decode()` method takes an ASCII encoded string and decodes it to a UTF-8 encoded string. Optionally, the second parameter can be specified to place a limit on the size of the returned string. #### Parameters - `$input` - An ASCII encoded punycode string to convert to a UTF-8 encoded string. - `$outLength` - A positive integer representing the maximum length, in code points, of the resulting output string. Defaults to 2,147,483,647. - `$caseFlags` - An array, which will have the case flag of each character inserted into it. #### Throws - `\Rowbot\Punycode\Exception\OutputSizeExceededException` - If the size of the output string exceeds the maximum size specified. - `\Rowbot\Punycode\Exception\OverflowException` - If integer overflow occurs. - `\Rowbot\Punycode\Exception\InvalidInputException` - If input contains non-ASCII bytes or mapping a code point to a digit fails. ```php use Rowbot\Punycode\Punycode; try { echo Punycode::decode('Hello-Another-Way--fc4qua05auwb3674vfr0b'); // Hello-Another-Way-それぞれの場所 } catch (\Rowbot\Punycode\Exception\PunycodeException $e) { echo 'An error occured!'; } ``` ### Punycode::encode(string $input, int $outLength = null, array $caseFlags = []) The `Punycode::encode()` method takes a UTF-8 encoded string and converts it into an ASCII encoded punycode string. Optionally, the second parameter can be specified to place a limit on the size of the returned string. #### Parameters - `$input` - A UTF-8 encoded string to convert to punycode. - `$outLength` - A positive integer representing the maximum length, in code points, of the resulting output string. Defaults to 2,147,483,647. - `$caseFlags` - An array of bools where true indicates that the character should be uppercase and false indicates that it should be lowercase. This only affects ASCII characters `[a-zA-Z]`. #### Throws - `\Rowbot\Punycode\Exception\OutputSizeExceededException` - If the size of the output string exceeds the maximum size specified. - `\Rowbot\Punycode\Exception\OverflowException` - If integer overflow occurs. ```php use Rowbot\Punycode\Punycode; try { echo Punycode::encode('Hello-Another-Way-それぞれの場所'); // Hello-Another-Way--fc4qua05auwb3674vfr0b } catch (\Rowbot\Punycode\Exception\PunycodeException $e) { echo 'An error occured!'; } ``` php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/phpstan.neon 0000777 00000000256 15251156632 0022323 0 ustar 00 includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-deprecation-rules/rules.neon parameters: level: max paths: - src php-toolkit/DataLiberation/vendor-patched/rowbot/punycode/tests/PunycodeTest.php 0000777 00000011743 15251156632 0024271 0 ustar 00 <?php declare( strict_types=1 ); namespace Rowbot\Punycode\Test; use PHPUnit\Framework\TestCase; use Rowbot\Punycode\Exception\InvalidInputException; use Rowbot\Punycode\Exception\OutputSizeExceededException; use Rowbot\Punycode\Punycode; use function mb_strlen; use function preg_match; class PunycodeTest extends TestCase { /** * @see https://tools.ietf.org/html/rfc3492#section-7.1 * * @return array<int, array<int, string>> */ public static function punycodeProvider(): array { return [ [ 'ليهمابتكلموشعربي؟', 'egbpdaj6bu4bxfgehfvwxn', 'Arabic (Egyptian)', ], [ '他们为什么不说中文', 'ihqwcrb4cv8a8dqg056pqjye', 'Chinese (simplified)', ], [ '他們爲什麽不說中文', 'ihqwctvzc91f659drss3x8bo0yb', 'Chinese (traditional)', ], [ 'Pročprostěnemluvíčesky', 'Proprostnemluvesky-uyb24dma41a', 'Czech', ], [ 'למההםפשוטלאמדבריםעברית', '4dbcagdahymbxekheh6e0a7fei0b', 'Hebrew', ], [ 'यहलोगहिन्दीक्योंनहींबोलसकतेहैं', 'i1baa7eci9glrd9b2ae1bj0hfcgg6iyaf8o0a1dig0cd', 'Hindi (Devanagari)', ], [ 'なぜみんな日本語を話してくれないのか', 'n8jok5ay5dzabd5bym9f0cm5685rrjetr6pdxa', 'Japanese (kanji and hiragana)', ], [ '세계의모든사람들이한국어를이해한다면얼마나좋을까', '989aomsvi5e83db1d2a355cv1e0vak1dwrv93d5xbh15a0dt30a5jpsd879ccm6fea98c', 'Korean (Hangul syllables)', ], [ 'почемужеонинеговорятпорусски', 'b1abfaaepdrnnbgefbadotcwatmq2g4l', 'Russian (Cyrillic)', ], [ 'PorquénopuedensimplementehablarenEspañol', 'PorqunopuedensimplementehablarenEspaol-fmd56a', 'Spanish', ], [ 'TạisaohọkhôngthểchỉnóitiếngViệt', 'TisaohkhngthchnitingVit-kjcr8268qyxafd2f1b9g', 'Vietnamese', ], [ '3年B組金八先生', '3B-ww4c5e180e575a65lsy2b', '', ], [ '安室奈美恵-with-SUPER-MONKEYS', '-with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n', '', ], [ 'Hello-Another-Way-それぞれの場所', 'Hello-Another-Way--fc4qua05auwb3674vfr0b', '', ], [ 'ひとつ屋根の下2', '2-u9tlzr9756bt3uc0v', '', ], [ 'MajiでKoiする5秒前', 'MajiKoi5-783gue6qz075azm5e', '', ], [ 'パフィーdeルンバ', 'de-jg4avhby1noc0d', '', ], [ 'そのスピードで', 'd9juau41awczczp', '', ], [ '-> $1.00 <-', '-> $1.00 <--', '', ], ]; } /** * @dataProvider punycodeProvider */ public function testEncode( string $decoded, string $encoded, string $comment ): void { self::assertSame( $encoded, Punycode::encode( $decoded ), $comment ); } /** * @dataProvider punycodeProvider */ public function testDecode( string $decoded, string $encoded, string $comment ): void { self::assertSame( $decoded, Punycode::decode( $encoded ), $comment ); } public function encodeMaxLengthDataProvider(): array { return array_filter( $this->punycodeProvider(), static function ( array $data ): bool { return mb_strlen( $data[1], 'utf-8' ) > 10; } ); } /** * @dataProvider encodeMaxLengthDataProvider */ public function testEncodeMaxLength( string $decoded, string $encoded, string $comment ): void { $this->expectException( OutputSizeExceededException::class ); Punycode::encode( $decoded, 10 ); } /** * @return array<int, array<int, string>> */ public function decodeMaxLengthDataProvider(): array { return array_filter( $this->punycodeProvider(), static function ( array $data ): bool { return mb_strlen( $data[0], 'utf-8' ) > 10; } ); } /** * @dataProvider decodeMaxLengthDataProvider */ public function testDecodeMaxLength( string $decoded, string $encoded, string $comment ): void { $this->expectException( OutputSizeExceededException::class ); Punycode::decode( $encoded, 10 ); } /** * @return array<int, array<int, string>> */ public function nonAsciiDataProvider(): array { return array_filter( $this->punycodeProvider(), static function ( array $data ): bool { return preg_match( '/[^\x00-\x7F]/', $data[0] ) === 1; } ) + [ [ "abc\u{FFFD}", '', 'Mixed ASCII and non-ASCII' ], ]; } /** * @dataProvider nonAsciiDataProvider */ public function testDecodeOnlyAcceptsAscii( string $decoded, string $encoded, string $comment ): void { $this->expectException( InvalidInputException::class ); Punycode::decode( $decoded ); } public function testCaseFlags(): void { $c = [ true, true, true ]; $e = Punycode::encode( 'abc', null, $c ); self::assertSame( 'ABC-', $e ); $c = [ false, false, false ]; $d = Punycode::decode( $e, null, $c ); self::assertSame( [ true, true, true ], $c ); self::assertSame( 'ABC', $d ); $c = []; $d = Punycode::decode( $e, null, $c ); self::assertSame( [ true, true, true ], $c ); self::assertSame( 'ABC', $d ); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/README.md 0000777 00000000610 15251156632 0024556 0 ustar 00 # Symfony Polyfill / Intl: Normalizer This component provides a fallback implementation for the [`Normalizer`](https://php.net/Normalizer) class provided by the [Intl](https://php.net/intl) extension. More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). # License This library is released under the [MIT license](LICENSE). php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/bootstrap80.php 0000777 00000001366 15251156632 0026206 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Intl\Normalizer as p; if ( ! function_exists( 'normalizer_is_normalized' ) ) { function normalizer_is_normalized( ?string $string, ?int $form = p\Normalizer::FORM_C ): bool { return p\Normalizer::isNormalized( (string) $string, (int) $form ); } } if ( ! function_exists( 'normalizer_normalize' ) ) { /** * @return string|false */ function normalizer_normalize( ?string $string, ?int $form = p\Normalizer::FORM_C ) { return p\Normalizer::normalize( (string) $string, (int) $form ); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/bootstrap.php 0000777 00000001347 15251156632 0026035 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Intl\Normalizer as p; if ( PHP_VERSION_ID >= 80000 ) { return require __DIR__ . '/bootstrap80.php'; } if ( ! function_exists( 'normalizer_is_normalized' ) ) { function normalizer_is_normalized( $string, $form = p\Normalizer::FORM_C ) { return p\Normalizer::isNormalized( $string, $form ); } } if ( ! function_exists( 'normalizer_normalize' ) ) { function normalizer_normalize( $string, $form = p\Normalizer::FORM_C ) { return p\Normalizer::normalize( $string, $form ); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/composer.json 0000777 00000001556 15251156632 0026033 0 ustar 00 { "name": "symfony/polyfill-intl-normalizer", "type": "library", "description": "Symfony polyfill for intl's Normalizer class and related functions", "keywords": [ "polyfill", "shim", "compatibility", "portable", "intl", "normalizer" ], "homepage": "https://symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "require": { "php": ">=7.2" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "files": [ "bootstrap.php" ], "classmap": [ "Resources/stubs" ] }, "suggest": { "ext-intl": "For best performance" }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/Normalizer.php 0000777 00000017434 15251156632 0026146 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Intl\Normalizer; use ValueError; use function chr; use function defined; use function in_array; use function ini_get; use function ord; use function strlen; use const PHP_VERSION_ID; /** * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension. * * It has been validated with Unicode 6.3 Normalization Conformance Test. * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations. * * @author Nicolas Grekas <p@tchwork.com> * * @internal */ class Normalizer { public const FORM_D = \Normalizer::FORM_D; public const FORM_KD = \Normalizer::FORM_KD; public const FORM_C = \Normalizer::FORM_C; public const FORM_KC = \Normalizer::FORM_KC; public const NFD = \Normalizer::NFD; public const NFKD = \Normalizer::NFKD; public const NFC = \Normalizer::NFC; public const NFKC = \Normalizer::NFKC; private static $C; private static $D; private static $KD; private static $cC; private static $ulenMask = [ "\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4 ]; private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; public static function isNormalized( string $s, int $form = self::FORM_C ) { if ( ! in_array( $form, [ self::NFD, self::NFKD, self::NFC, self::NFKC ] ) ) { return false; } if ( ! isset( $s[ strspn( $s, self::$ASCII ) ] ) ) { return true; } if ( self::NFC == $form && preg_match( '//u', $s ) && ! preg_match( '/[^\x00-\x{2FF}]/u', $s ) ) { return true; } return self::normalize( $s, $form ) === $s; } public static function normalize( string $s, int $form = self::FORM_C ) { if ( ! preg_match( '//u', $s ) ) { return false; } switch ( $form ) { case self::NFC: $C = true; $K = false; break; case self::NFD: $C = false; $K = false; break; case self::NFKC: $C = true; $K = true; break; case self::NFKD: $C = false; $K = true; break; default: if ( defined( 'Normalizer::NONE' ) && \Normalizer::NONE == $form ) { return $s; } if ( 80000 > PHP_VERSION_ID ) { return false; } throw new ValueError( 'normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form' ); } if ( '' === $s ) { return ''; } if ( $K && null === self::$KD ) { self::$KD = self::getData( 'compatibilityDecomposition' ); } if ( null === self::$D ) { self::$D = self::getData( 'canonicalDecomposition' ); self::$cC = self::getData( 'combiningClass' ); } if ( null !== $mbEncoding = ( 2 /* MB_OVERLOAD_STRING */ & (int) ini_get( 'mbstring.func_overload' ) ) ? mb_internal_encoding() : null ) { mb_internal_encoding( '8bit' ); } $r = self::decompose( $s, $K ); if ( $C ) { if ( null === self::$C ) { self::$C = self::getData( 'canonicalComposition' ); } $r = self::recompose( $r ); } if ( null !== $mbEncoding ) { mb_internal_encoding( $mbEncoding ); } return $r; } private static function recompose( $s ) { $ASCII = self::$ASCII; $compMap = self::$C; $combClass = self::$cC; $ulenMask = self::$ulenMask; $result = $tail = ''; $i = $s[0] < "\x80" ? 1 : $ulenMask[ $s[0] & "\xF0" ]; $len = strlen( $s ); $lastUchr = substr( $s, 0, $i ); $lastUcls = isset( $combClass[ $lastUchr ] ) ? 256 : 0; while ( $i < $len ) { if ( $s[ $i ] < "\x80" ) { // ASCII chars if ( $tail ) { $lastUchr .= $tail; $tail = ''; } if ( $j = strspn( $s, $ASCII, $i + 1 ) ) { $lastUchr .= substr( $s, $i, $j ); $i += $j; } $result .= $lastUchr; $lastUchr = $s[ $i ]; $lastUcls = 0; ++ $i; continue; } $ulen = $ulenMask[ $s[ $i ] & "\xF0" ]; $uchr = substr( $s, $i, $ulen ); if ( $lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr || $lastUcls ) { // Table lookup and combining chars composition $ucls = $combClass[ $uchr ] ?? 0; if ( isset( $compMap[ $lastUchr . $uchr ] ) && ( ! $lastUcls || $lastUcls < $ucls ) ) { $lastUchr = $compMap[ $lastUchr . $uchr ]; } elseif ( $lastUcls = $ucls ) { $tail .= $uchr; } else { if ( $tail ) { $lastUchr .= $tail; $tail = ''; } $result .= $lastUchr; $lastUchr = $uchr; } } else { // Hangul chars $L = ord( $lastUchr[2] ) - 0x80; $V = ord( $uchr[2] ) - 0xA1; $T = 0; $uchr = substr( $s, $i + $ulen, 3 ); if ( "\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82" ) { $T = ord( $uchr[2] ) - 0xA7; 0 > $T && $T += 0x40; $ulen += 3; } $L = 0xAC00 + ( $L * 21 + $V ) * 28 + $T; $lastUchr = chr( 0xE0 | $L >> 12 ) . chr( 0x80 | $L >> 6 & 0x3F ) . chr( 0x80 | $L & 0x3F ); } $i += $ulen; } return $result . $lastUchr . $tail; } private static function decompose( $s, $c ) { $result = ''; $ASCII = self::$ASCII; $decompMap = self::$D; $combClass = self::$cC; $ulenMask = self::$ulenMask; if ( $c ) { $compatMap = self::$KD; } $c = []; $i = 0; $len = strlen( $s ); while ( $i < $len ) { if ( $s[ $i ] < "\x80" ) { // ASCII chars if ( $c ) { ksort( $c ); $result .= implode( '', $c ); $c = []; } $j = 1 + strspn( $s, $ASCII, $i + 1 ); $result .= substr( $s, $i, $j ); $i += $j; continue; } $ulen = $ulenMask[ $s[ $i ] & "\xF0" ]; $uchr = substr( $s, $i, $ulen ); $i += $ulen; if ( $uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr ) { // Table lookup if ( $uchr !== $j = $compatMap[ $uchr ] ?? ( $decompMap[ $uchr ] ?? $uchr ) ) { $uchr = $j; $j = strlen( $uchr ); $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[ $uchr[0] & "\xF0" ]; if ( $ulen != $j ) { // Put trailing chars in $s $j -= $ulen; $i -= $j; if ( 0 > $i ) { $s = str_repeat( ' ', - $i ) . $s; $len -= $i; $i = 0; } while ( $j -- ) { $s[ $i + $j ] = $uchr[ $ulen + $j ]; } $uchr = substr( $uchr, 0, $ulen ); } } if ( isset( $combClass[ $uchr ] ) ) { // Combining chars, for sorting if ( ! isset( $c[ $combClass[ $uchr ] ] ) ) { $c[ $combClass[ $uchr ] ] = ''; } $c[ $combClass[ $uchr ] ] .= $uchr; continue; } } else { // Hangul chars $uchr = unpack( 'C*', $uchr ); $j = ( ( $uchr[1] - 224 ) << 12 ) + ( ( $uchr[2] - 128 ) << 6 ) + $uchr[3] - 0xAC80; $uchr = "\xE1\x84" . chr( 0x80 + (int) ( $j / 588 ) ) . "\xE1\x85" . chr( 0xA1 + (int) ( ( $j % 588 ) / 28 ) ); if ( $j %= 28 ) { $uchr .= $j < 25 ? ( "\xE1\x86" . chr( 0xA7 + $j ) ) : ( "\xE1\x87" . chr( 0x67 + $j ) ); } } if ( $c ) { ksort( $c ); $result .= implode( '', $c ); $c = []; } $result .= $uchr; } if ( $c ) { ksort( $c ); $result .= implode( '', $c ); } return $result; } private static function getData( $file ) { if ( file_exists( $file = __DIR__ . '/Resources/unidata/' . $file . '.php' ) ) { return require $file; } return false; } } DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php 0000777 00000000560 15251156632 0031171 0 ustar 00 php-toolkit <?php class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer { /** * @deprecated since ICU 56 and removed in PHP 8 */ public const NONE = 2; public const FORM_D = 4; public const FORM_KD = 8; public const FORM_C = 16; public const FORM_KC = 32; public const NFD = 4; public const NFKD = 8; public const NFC = 16; public const NFKC = 32; } vendor-patched/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php 0000777 00000113164 15251156632 0034025 0 ustar 00 php-toolkit/DataLiberation <?php return array( 'À' => 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '̀' => '̀', '́' => '́', '̓' => '̓', '̈́' => '̈́', 'ʹ' => 'ʹ', ';' => ';', '΅' => '΅', 'Ά' => 'Ά', '·' => '·', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'क़' => 'क़', 'ख़' => 'ख़', 'ग़' => 'ग़', 'ज़' => 'ज़', 'ड़' => 'ड़', 'ढ़' => 'ढ़', 'फ़' => 'फ़', 'य़' => 'य़', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ড়' => 'ড়', 'ঢ়' => 'ঢ়', 'য়' => 'য়', 'ਲ਼' => 'ਲ਼', 'ਸ਼' => 'ਸ਼', 'ਖ਼' => 'ਖ਼', 'ਗ਼' => 'ਗ਼', 'ਜ਼' => 'ਜ਼', 'ਫ਼' => 'ਫ਼', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ଡ଼' => 'ଡ଼', 'ଢ଼' => 'ଢ଼', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'གྷ' => 'གྷ', 'ཌྷ' => 'ཌྷ', 'དྷ' => 'དྷ', 'བྷ' => 'བྷ', 'ཛྷ' => 'ཛྷ', 'ཀྵ' => 'ཀྵ', 'ཱི' => 'ཱི', 'ཱུ' => 'ཱུ', 'ྲྀ' => 'ྲྀ', 'ླྀ' => 'ླྀ', 'ཱྀ' => 'ཱྀ', 'ྒྷ' => 'ྒྷ', 'ྜྷ' => 'ྜྷ', 'ྡྷ' => 'ྡྷ', 'ྦྷ' => 'ྦྷ', 'ྫྷ' => 'ྫྷ', 'ྐྵ' => 'ྐྵ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ά' => 'ά', 'ὲ' => 'ὲ', 'έ' => 'έ', 'ὴ' => 'ὴ', 'ή' => 'ή', 'ὶ' => 'ὶ', 'ί' => 'ί', 'ὸ' => 'ὸ', 'ό' => 'ό', 'ὺ' => 'ὺ', 'ύ' => 'ύ', 'ὼ' => 'ὼ', 'ώ' => 'ώ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'Ά' => 'Ά', 'ᾼ' => 'ᾼ', 'ι' => 'ι', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Έ' => 'Έ', 'Ὴ' => 'Ὴ', 'Ή' => 'Ή', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ΐ' => 'ΐ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', 'Ί' => 'Ί', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ΰ' => 'ΰ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ύ' => 'Ύ', 'Ῥ' => 'Ῥ', '῭' => '῭', '΅' => '΅', '`' => '`', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ό' => 'Ό', 'Ὼ' => 'Ὼ', 'Ώ' => 'Ώ', 'ῼ' => 'ῼ', '´' => '´', ' ' => ' ', ' ' => ' ', 'Ω' => 'Ω', 'K' => 'K', 'Å' => 'Å', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', '〈' => '〈', '〉' => '〉', '⫝̸' => '⫝̸', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '豈' => '豈', '更' => '更', '車' => '車', '賈' => '賈', '滑' => '滑', '串' => '串', '句' => '句', '龜' => '龜', '龜' => '龜', '契' => '契', '金' => '金', '喇' => '喇', '奈' => '奈', '懶' => '懶', '癩' => '癩', '羅' => '羅', '蘿' => '蘿', '螺' => '螺', '裸' => '裸', '邏' => '邏', '樂' => '樂', '洛' => '洛', '烙' => '烙', '珞' => '珞', '落' => '落', '酪' => '酪', '駱' => '駱', '亂' => '亂', '卵' => '卵', '欄' => '欄', '爛' => '爛', '蘭' => '蘭', '鸞' => '鸞', '嵐' => '嵐', '濫' => '濫', '藍' => '藍', '襤' => '襤', '拉' => '拉', '臘' => '臘', '蠟' => '蠟', '廊' => '廊', '朗' => '朗', '浪' => '浪', '狼' => '狼', '郎' => '郎', '來' => '來', '冷' => '冷', '勞' => '勞', '擄' => '擄', '櫓' => '櫓', '爐' => '爐', '盧' => '盧', '老' => '老', '蘆' => '蘆', '虜' => '虜', '路' => '路', '露' => '露', '魯' => '魯', '鷺' => '鷺', '碌' => '碌', '祿' => '祿', '綠' => '綠', '菉' => '菉', '錄' => '錄', '鹿' => '鹿', '論' => '論', '壟' => '壟', '弄' => '弄', '籠' => '籠', '聾' => '聾', '牢' => '牢', '磊' => '磊', '賂' => '賂', '雷' => '雷', '壘' => '壘', '屢' => '屢', '樓' => '樓', '淚' => '淚', '漏' => '漏', '累' => '累', '縷' => '縷', '陋' => '陋', '勒' => '勒', '肋' => '肋', '凜' => '凜', '凌' => '凌', '稜' => '稜', '綾' => '綾', '菱' => '菱', '陵' => '陵', '讀' => '讀', '拏' => '拏', '樂' => '樂', '諾' => '諾', '丹' => '丹', '寧' => '寧', '怒' => '怒', '率' => '率', '異' => '異', '北' => '北', '磻' => '磻', '便' => '便', '復' => '復', '不' => '不', '泌' => '泌', '數' => '數', '索' => '索', '參' => '參', '塞' => '塞', '省' => '省', '葉' => '葉', '說' => '說', '殺' => '殺', '辰' => '辰', '沈' => '沈', '拾' => '拾', '若' => '若', '掠' => '掠', '略' => '略', '亮' => '亮', '兩' => '兩', '凉' => '凉', '梁' => '梁', '糧' => '糧', '良' => '良', '諒' => '諒', '量' => '量', '勵' => '勵', '呂' => '呂', '女' => '女', '廬' => '廬', '旅' => '旅', '濾' => '濾', '礪' => '礪', '閭' => '閭', '驪' => '驪', '麗' => '麗', '黎' => '黎', '力' => '力', '曆' => '曆', '歷' => '歷', '轢' => '轢', '年' => '年', '憐' => '憐', '戀' => '戀', '撚' => '撚', '漣' => '漣', '煉' => '煉', '璉' => '璉', '秊' => '秊', '練' => '練', '聯' => '聯', '輦' => '輦', '蓮' => '蓮', '連' => '連', '鍊' => '鍊', '列' => '列', '劣' => '劣', '咽' => '咽', '烈' => '烈', '裂' => '裂', '說' => '說', '廉' => '廉', '念' => '念', '捻' => '捻', '殮' => '殮', '簾' => '簾', '獵' => '獵', '令' => '令', '囹' => '囹', '寧' => '寧', '嶺' => '嶺', '怜' => '怜', '玲' => '玲', '瑩' => '瑩', '羚' => '羚', '聆' => '聆', '鈴' => '鈴', '零' => '零', '靈' => '靈', '領' => '領', '例' => '例', '禮' => '禮', '醴' => '醴', '隸' => '隸', '惡' => '惡', '了' => '了', '僚' => '僚', '寮' => '寮', '尿' => '尿', '料' => '料', '樂' => '樂', '燎' => '燎', '療' => '療', '蓼' => '蓼', '遼' => '遼', '龍' => '龍', '暈' => '暈', '阮' => '阮', '劉' => '劉', '杻' => '杻', '柳' => '柳', '流' => '流', '溜' => '溜', '琉' => '琉', '留' => '留', '硫' => '硫', '紐' => '紐', '類' => '類', '六' => '六', '戮' => '戮', '陸' => '陸', '倫' => '倫', '崙' => '崙', '淪' => '淪', '輪' => '輪', '律' => '律', '慄' => '慄', '栗' => '栗', '率' => '率', '隆' => '隆', '利' => '利', '吏' => '吏', '履' => '履', '易' => '易', '李' => '李', '梨' => '梨', '泥' => '泥', '理' => '理', '痢' => '痢', '罹' => '罹', '裏' => '裏', '裡' => '裡', '里' => '里', '離' => '離', '匿' => '匿', '溺' => '溺', '吝' => '吝', '燐' => '燐', '璘' => '璘', '藺' => '藺', '隣' => '隣', '鱗' => '鱗', '麟' => '麟', '林' => '林', '淋' => '淋', '臨' => '臨', '立' => '立', '笠' => '笠', '粒' => '粒', '狀' => '狀', '炙' => '炙', '識' => '識', '什' => '什', '茶' => '茶', '刺' => '刺', '切' => '切', '度' => '度', '拓' => '拓', '糖' => '糖', '宅' => '宅', '洞' => '洞', '暴' => '暴', '輻' => '輻', '行' => '行', '降' => '降', '見' => '見', '廓' => '廓', '兀' => '兀', '嗀' => '嗀', '塚' => '塚', '晴' => '晴', '凞' => '凞', '猪' => '猪', '益' => '益', '礼' => '礼', '神' => '神', '祥' => '祥', '福' => '福', '靖' => '靖', '精' => '精', '羽' => '羽', '蘒' => '蘒', '諸' => '諸', '逸' => '逸', '都' => '都', '飯' => '飯', '飼' => '飼', '館' => '館', '鶴' => '鶴', '郞' => '郞', '隷' => '隷', '侮' => '侮', '僧' => '僧', '免' => '免', '勉' => '勉', '勤' => '勤', '卑' => '卑', '喝' => '喝', '嘆' => '嘆', '器' => '器', '塀' => '塀', '墨' => '墨', '層' => '層', '屮' => '屮', '悔' => '悔', '慨' => '慨', '憎' => '憎', '懲' => '懲', '敏' => '敏', '既' => '既', '暑' => '暑', '梅' => '梅', '海' => '海', '渚' => '渚', '漢' => '漢', '煮' => '煮', '爫' => '爫', '琢' => '琢', '碑' => '碑', '社' => '社', '祉' => '祉', '祈' => '祈', '祐' => '祐', '祖' => '祖', '祝' => '祝', '禍' => '禍', '禎' => '禎', '穀' => '穀', '突' => '突', '節' => '節', '練' => '練', '縉' => '縉', '繁' => '繁', '署' => '署', '者' => '者', '臭' => '臭', '艹' => '艹', '艹' => '艹', '著' => '著', '褐' => '褐', '視' => '視', '謁' => '謁', '謹' => '謹', '賓' => '賓', '贈' => '贈', '辶' => '辶', '逸' => '逸', '難' => '難', '響' => '響', '頻' => '頻', '恵' => '恵', '𤋮' => '𤋮', '舘' => '舘', '並' => '並', '况' => '况', '全' => '全', '侀' => '侀', '充' => '充', '冀' => '冀', '勇' => '勇', '勺' => '勺', '喝' => '喝', '啕' => '啕', '喙' => '喙', '嗢' => '嗢', '塚' => '塚', '墳' => '墳', '奄' => '奄', '奔' => '奔', '婢' => '婢', '嬨' => '嬨', '廒' => '廒', '廙' => '廙', '彩' => '彩', '徭' => '徭', '惘' => '惘', '慎' => '慎', '愈' => '愈', '憎' => '憎', '慠' => '慠', '懲' => '懲', '戴' => '戴', '揄' => '揄', '搜' => '搜', '摒' => '摒', '敖' => '敖', '晴' => '晴', '朗' => '朗', '望' => '望', '杖' => '杖', '歹' => '歹', '殺' => '殺', '流' => '流', '滛' => '滛', '滋' => '滋', '漢' => '漢', '瀞' => '瀞', '煮' => '煮', '瞧' => '瞧', '爵' => '爵', '犯' => '犯', '猪' => '猪', '瑱' => '瑱', '甆' => '甆', '画' => '画', '瘝' => '瘝', '瘟' => '瘟', '益' => '益', '盛' => '盛', '直' => '直', '睊' => '睊', '着' => '着', '磌' => '磌', '窱' => '窱', '節' => '節', '类' => '类', '絛' => '絛', '練' => '練', '缾' => '缾', '者' => '者', '荒' => '荒', '華' => '華', '蝹' => '蝹', '襁' => '襁', '覆' => '覆', '視' => '視', '調' => '調', '諸' => '諸', '請' => '請', '謁' => '謁', '諾' => '諾', '諭' => '諭', '謹' => '謹', '變' => '變', '贈' => '贈', '輸' => '輸', '遲' => '遲', '醙' => '醙', '鉶' => '鉶', '陼' => '陼', '難' => '難', '靖' => '靖', '韛' => '韛', '響' => '響', '頋' => '頋', '頻' => '頻', '鬒' => '鬒', '龜' => '龜', '𢡊' => '𢡊', '𢡄' => '𢡄', '𣏕' => '𣏕', '㮝' => '㮝', '䀘' => '䀘', '䀹' => '䀹', '𥉉' => '𥉉', '𥳐' => '𥳐', '𧻓' => '𧻓', '齃' => '齃', '龎' => '龎', 'יִ' => 'יִ', 'ײַ' => 'ײַ', 'שׁ' => 'שׁ', 'שׂ' => 'שׂ', 'שּׁ' => 'שּׁ', 'שּׂ' => 'שּׂ', 'אַ' => 'אַ', 'אָ' => 'אָ', 'אּ' => 'אּ', 'בּ' => 'בּ', 'גּ' => 'גּ', 'דּ' => 'דּ', 'הּ' => 'הּ', 'וּ' => 'וּ', 'זּ' => 'זּ', 'טּ' => 'טּ', 'יּ' => 'יּ', 'ךּ' => 'ךּ', 'כּ' => 'כּ', 'לּ' => 'לּ', 'מּ' => 'מּ', 'נּ' => 'נּ', 'סּ' => 'סּ', 'ףּ' => 'ףּ', 'פּ' => 'פּ', 'צּ' => 'צּ', 'קּ' => 'קּ', 'רּ' => 'רּ', 'שּ' => 'שּ', 'תּ' => 'תּ', 'וֹ' => 'וֹ', 'בֿ' => 'בֿ', 'כֿ' => 'כֿ', 'פֿ' => 'פֿ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸', '𝅗𝅥' => '𝅗𝅥', '𝅘𝅥' => '𝅘𝅥', '𝅘𝅥𝅮' => '𝅘𝅥𝅮', '𝅘𝅥𝅯' => '𝅘𝅥𝅯', '𝅘𝅥𝅰' => '𝅘𝅥𝅰', '𝅘𝅥𝅱' => '𝅘𝅥𝅱', '𝅘𝅥𝅲' => '𝅘𝅥𝅲', '𝆹𝅥' => '𝆹𝅥', '𝆺𝅥' => '𝆺𝅥', '𝆹𝅥𝅮' => '𝆹𝅥𝅮', '𝆺𝅥𝅮' => '𝆺𝅥𝅮', '𝆹𝅥𝅯' => '𝆹𝅥𝅯', '𝆺𝅥𝅯' => '𝆺𝅥𝅯', '丽' => '丽', '丸' => '丸', '乁' => '乁', '𠄢' => '𠄢', '你' => '你', '侮' => '侮', '侻' => '侻', '倂' => '倂', '偺' => '偺', '備' => '備', '僧' => '僧', '像' => '像', '㒞' => '㒞', '𠘺' => '𠘺', '免' => '免', '兔' => '兔', '兤' => '兤', '具' => '具', '𠔜' => '𠔜', '㒹' => '㒹', '內' => '內', '再' => '再', '𠕋' => '𠕋', '冗' => '冗', '冤' => '冤', '仌' => '仌', '冬' => '冬', '况' => '况', '𩇟' => '𩇟', '凵' => '凵', '刃' => '刃', '㓟' => '㓟', '刻' => '刻', '剆' => '剆', '割' => '割', '剷' => '剷', '㔕' => '㔕', '勇' => '勇', '勉' => '勉', '勤' => '勤', '勺' => '勺', '包' => '包', '匆' => '匆', '北' => '北', '卉' => '卉', '卑' => '卑', '博' => '博', '即' => '即', '卽' => '卽', '卿' => '卿', '卿' => '卿', '卿' => '卿', '𠨬' => '𠨬', '灰' => '灰', '及' => '及', '叟' => '叟', '𠭣' => '𠭣', '叫' => '叫', '叱' => '叱', '吆' => '吆', '咞' => '咞', '吸' => '吸', '呈' => '呈', '周' => '周', '咢' => '咢', '哶' => '哶', '唐' => '唐', '啓' => '啓', '啣' => '啣', '善' => '善', '善' => '善', '喙' => '喙', '喫' => '喫', '喳' => '喳', '嗂' => '嗂', '圖' => '圖', '嘆' => '嘆', '圗' => '圗', '噑' => '噑', '噴' => '噴', '切' => '切', '壮' => '壮', '城' => '城', '埴' => '埴', '堍' => '堍', '型' => '型', '堲' => '堲', '報' => '報', '墬' => '墬', '𡓤' => '𡓤', '売' => '売', '壷' => '壷', '夆' => '夆', '多' => '多', '夢' => '夢', '奢' => '奢', '𡚨' => '𡚨', '𡛪' => '𡛪', '姬' => '姬', '娛' => '娛', '娧' => '娧', '姘' => '姘', '婦' => '婦', '㛮' => '㛮', '㛼' => '㛼', '嬈' => '嬈', '嬾' => '嬾', '嬾' => '嬾', '𡧈' => '𡧈', '寃' => '寃', '寘' => '寘', '寧' => '寧', '寳' => '寳', '𡬘' => '𡬘', '寿' => '寿', '将' => '将', '当' => '当', '尢' => '尢', '㞁' => '㞁', '屠' => '屠', '屮' => '屮', '峀' => '峀', '岍' => '岍', '𡷤' => '𡷤', '嵃' => '嵃', '𡷦' => '𡷦', '嵮' => '嵮', '嵫' => '嵫', '嵼' => '嵼', '巡' => '巡', '巢' => '巢', '㠯' => '㠯', '巽' => '巽', '帨' => '帨', '帽' => '帽', '幩' => '幩', '㡢' => '㡢', '𢆃' => '𢆃', '㡼' => '㡼', '庰' => '庰', '庳' => '庳', '庶' => '庶', '廊' => '廊', '𪎒' => '𪎒', '廾' => '廾', '𢌱' => '𢌱', '𢌱' => '𢌱', '舁' => '舁', '弢' => '弢', '弢' => '弢', '㣇' => '㣇', '𣊸' => '𣊸', '𦇚' => '𦇚', '形' => '形', '彫' => '彫', '㣣' => '㣣', '徚' => '徚', '忍' => '忍', '志' => '志', '忹' => '忹', '悁' => '悁', '㤺' => '㤺', '㤜' => '㤜', '悔' => '悔', '𢛔' => '𢛔', '惇' => '惇', '慈' => '慈', '慌' => '慌', '慎' => '慎', '慌' => '慌', '慺' => '慺', '憎' => '憎', '憲' => '憲', '憤' => '憤', '憯' => '憯', '懞' => '懞', '懲' => '懲', '懶' => '懶', '成' => '成', '戛' => '戛', '扝' => '扝', '抱' => '抱', '拔' => '拔', '捐' => '捐', '𢬌' => '𢬌', '挽' => '挽', '拼' => '拼', '捨' => '捨', '掃' => '掃', '揤' => '揤', '𢯱' => '𢯱', '搢' => '搢', '揅' => '揅', '掩' => '掩', '㨮' => '㨮', '摩' => '摩', '摾' => '摾', '撝' => '撝', '摷' => '摷', '㩬' => '㩬', '敏' => '敏', '敬' => '敬', '𣀊' => '𣀊', '旣' => '旣', '書' => '書', '晉' => '晉', '㬙' => '㬙', '暑' => '暑', '㬈' => '㬈', '㫤' => '㫤', '冒' => '冒', '冕' => '冕', '最' => '最', '暜' => '暜', '肭' => '肭', '䏙' => '䏙', '朗' => '朗', '望' => '望', '朡' => '朡', '杞' => '杞', '杓' => '杓', '𣏃' => '𣏃', '㭉' => '㭉', '柺' => '柺', '枅' => '枅', '桒' => '桒', '梅' => '梅', '𣑭' => '𣑭', '梎' => '梎', '栟' => '栟', '椔' => '椔', '㮝' => '㮝', '楂' => '楂', '榣' => '榣', '槪' => '槪', '檨' => '檨', '𣚣' => '𣚣', '櫛' => '櫛', '㰘' => '㰘', '次' => '次', '𣢧' => '𣢧', '歔' => '歔', '㱎' => '㱎', '歲' => '歲', '殟' => '殟', '殺' => '殺', '殻' => '殻', '𣪍' => '𣪍', '𡴋' => '𡴋', '𣫺' => '𣫺', '汎' => '汎', '𣲼' => '𣲼', '沿' => '沿', '泍' => '泍', '汧' => '汧', '洖' => '洖', '派' => '派', '海' => '海', '流' => '流', '浩' => '浩', '浸' => '浸', '涅' => '涅', '𣴞' => '𣴞', '洴' => '洴', '港' => '港', '湮' => '湮', '㴳' => '㴳', '滋' => '滋', '滇' => '滇', '𣻑' => '𣻑', '淹' => '淹', '潮' => '潮', '𣽞' => '𣽞', '𣾎' => '𣾎', '濆' => '濆', '瀹' => '瀹', '瀞' => '瀞', '瀛' => '瀛', '㶖' => '㶖', '灊' => '灊', '災' => '災', '灷' => '灷', '炭' => '炭', '𠔥' => '𠔥', '煅' => '煅', '𤉣' => '𤉣', '熜' => '熜', '𤎫' => '𤎫', '爨' => '爨', '爵' => '爵', '牐' => '牐', '𤘈' => '𤘈', '犀' => '犀', '犕' => '犕', '𤜵' => '𤜵', '𤠔' => '𤠔', '獺' => '獺', '王' => '王', '㺬' => '㺬', '玥' => '玥', '㺸' => '㺸', '㺸' => '㺸', '瑇' => '瑇', '瑜' => '瑜', '瑱' => '瑱', '璅' => '璅', '瓊' => '瓊', '㼛' => '㼛', '甤' => '甤', '𤰶' => '𤰶', '甾' => '甾', '𤲒' => '𤲒', '異' => '異', '𢆟' => '𢆟', '瘐' => '瘐', '𤾡' => '𤾡', '𤾸' => '𤾸', '𥁄' => '𥁄', '㿼' => '㿼', '䀈' => '䀈', '直' => '直', '𥃳' => '𥃳', '𥃲' => '𥃲', '𥄙' => '𥄙', '𥄳' => '𥄳', '眞' => '眞', '真' => '真', '真' => '真', '睊' => '睊', '䀹' => '䀹', '瞋' => '瞋', '䁆' => '䁆', '䂖' => '䂖', '𥐝' => '𥐝', '硎' => '硎', '碌' => '碌', '磌' => '磌', '䃣' => '䃣', '𥘦' => '𥘦', '祖' => '祖', '𥚚' => '𥚚', '𥛅' => '𥛅', '福' => '福', '秫' => '秫', '䄯' => '䄯', '穀' => '穀', '穊' => '穊', '穏' => '穏', '𥥼' => '𥥼', '𥪧' => '𥪧', '𥪧' => '𥪧', '竮' => '竮', '䈂' => '䈂', '𥮫' => '𥮫', '篆' => '篆', '築' => '築', '䈧' => '䈧', '𥲀' => '𥲀', '糒' => '糒', '䊠' => '䊠', '糨' => '糨', '糣' => '糣', '紀' => '紀', '𥾆' => '𥾆', '絣' => '絣', '䌁' => '䌁', '緇' => '緇', '縂' => '縂', '繅' => '繅', '䌴' => '䌴', '𦈨' => '𦈨', '𦉇' => '𦉇', '䍙' => '䍙', '𦋙' => '𦋙', '罺' => '罺', '𦌾' => '𦌾', '羕' => '羕', '翺' => '翺', '者' => '者', '𦓚' => '𦓚', '𦔣' => '𦔣', '聠' => '聠', '𦖨' => '𦖨', '聰' => '聰', '𣍟' => '𣍟', '䏕' => '䏕', '育' => '育', '脃' => '脃', '䐋' => '䐋', '脾' => '脾', '媵' => '媵', '𦞧' => '𦞧', '𦞵' => '𦞵', '𣎓' => '𣎓', '𣎜' => '𣎜', '舁' => '舁', '舄' => '舄', '辞' => '辞', '䑫' => '䑫', '芑' => '芑', '芋' => '芋', '芝' => '芝', '劳' => '劳', '花' => '花', '芳' => '芳', '芽' => '芽', '苦' => '苦', '𦬼' => '𦬼', '若' => '若', '茝' => '茝', '荣' => '荣', '莭' => '莭', '茣' => '茣', '莽' => '莽', '菧' => '菧', '著' => '著', '荓' => '荓', '菊' => '菊', '菌' => '菌', '菜' => '菜', '𦰶' => '𦰶', '𦵫' => '𦵫', '𦳕' => '𦳕', '䔫' => '䔫', '蓱' => '蓱', '蓳' => '蓳', '蔖' => '蔖', '𧏊' => '𧏊', '蕤' => '蕤', '𦼬' => '𦼬', '䕝' => '䕝', '䕡' => '䕡', '𦾱' => '𦾱', '𧃒' => '𧃒', '䕫' => '䕫', '虐' => '虐', '虜' => '虜', '虧' => '虧', '虩' => '虩', '蚩' => '蚩', '蚈' => '蚈', '蜎' => '蜎', '蛢' => '蛢', '蝹' => '蝹', '蜨' => '蜨', '蝫' => '蝫', '螆' => '螆', '䗗' => '䗗', '蟡' => '蟡', '蠁' => '蠁', '䗹' => '䗹', '衠' => '衠', '衣' => '衣', '𧙧' => '𧙧', '裗' => '裗', '裞' => '裞', '䘵' => '䘵', '裺' => '裺', '㒻' => '㒻', '𧢮' => '𧢮', '𧥦' => '𧥦', '䚾' => '䚾', '䛇' => '䛇', '誠' => '誠', '諭' => '諭', '變' => '變', '豕' => '豕', '𧲨' => '𧲨', '貫' => '貫', '賁' => '賁', '贛' => '贛', '起' => '起', '𧼯' => '𧼯', '𠠄' => '𠠄', '跋' => '跋', '趼' => '趼', '跰' => '跰', '𠣞' => '𠣞', '軔' => '軔', '輸' => '輸', '𨗒' => '𨗒', '𨗭' => '𨗭', '邔' => '邔', '郱' => '郱', '鄑' => '鄑', '𨜮' => '𨜮', '鄛' => '鄛', '鈸' => '鈸', '鋗' => '鋗', '鋘' => '鋘', '鉼' => '鉼', '鏹' => '鏹', '鐕' => '鐕', '𨯺' => '𨯺', '開' => '開', '䦕' => '䦕', '閷' => '閷', '𨵷' => '𨵷', '䧦' => '䧦', '雃' => '雃', '嶲' => '嶲', '霣' => '霣', '𩅅' => '𩅅', '𩈚' => '𩈚', '䩮' => '䩮', '䩶' => '䩶', '韠' => '韠', '𩐊' => '𩐊', '䪲' => '䪲', '𩒖' => '𩒖', '頋' => '頋', '頋' => '頋', '頩' => '頩', '𩖶' => '𩖶', '飢' => '飢', '䬳' => '䬳', '餩' => '餩', '馧' => '馧', '駂' => '駂', '駾' => '駾', '䯎' => '䯎', '𩬰' => '𩬰', '鬒' => '鬒', '鱀' => '鱀', '鳽' => '鳽', '䳎' => '䳎', '䳭' => '䳭', '鵧' => '鵧', '𪃎' => '𪃎', '䳸' => '䳸', '𪄅' => '𪄅', '𪈎' => '𪈎', '𪊑' => '𪊑', '麻' => '麻', '䵖' => '䵖', '黹' => '黹', '黾' => '黾', '鼅' => '鼅', '鼏' => '鼏', '鼖' => '鼖', '鼻' => '鼻', '𪘀' => '𪘀', ); vendor-patched/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php 0000777 00000202552 15251156632 0034747 0 ustar 00 php-toolkit/DataLiberation <?php return array( ' ' => ' ', '¨' => ' ̈', 'ª' => 'a', '¯' => ' ̄', '²' => '2', '³' => '3', '´' => ' ́', 'µ' => 'μ', '¸' => ' ̧', '¹' => '1', 'º' => 'o', '¼' => '1⁄4', '½' => '1⁄2', '¾' => '3⁄4', 'IJ' => 'IJ', 'ij' => 'ij', 'Ŀ' => 'L·', 'ŀ' => 'l·', 'ʼn' => 'ʼn', 'ſ' => 's', 'DŽ' => 'DŽ', 'Dž' => 'Dž', 'dž' => 'dž', 'LJ' => 'LJ', 'Lj' => 'Lj', 'lj' => 'lj', 'NJ' => 'NJ', 'Nj' => 'Nj', 'nj' => 'nj', 'DZ' => 'DZ', 'Dz' => 'Dz', 'dz' => 'dz', 'ʰ' => 'h', 'ʱ' => 'ɦ', 'ʲ' => 'j', 'ʳ' => 'r', 'ʴ' => 'ɹ', 'ʵ' => 'ɻ', 'ʶ' => 'ʁ', 'ʷ' => 'w', 'ʸ' => 'y', '˘' => ' ̆', '˙' => ' ̇', '˚' => ' ̊', '˛' => ' ̨', '˜' => ' ̃', '˝' => ' ̋', 'ˠ' => 'ɣ', 'ˡ' => 'l', 'ˢ' => 's', 'ˣ' => 'x', 'ˤ' => 'ʕ', 'ͺ' => ' ͅ', '΄' => ' ́', '΅' => ' ̈́', 'ϐ' => 'β', 'ϑ' => 'θ', 'ϒ' => 'Υ', 'ϓ' => 'Ύ', 'ϔ' => 'Ϋ', 'ϕ' => 'φ', 'ϖ' => 'π', 'ϰ' => 'κ', 'ϱ' => 'ρ', 'ϲ' => 'ς', 'ϴ' => 'Θ', 'ϵ' => 'ε', 'Ϲ' => 'Σ', 'և' => 'եւ', 'ٵ' => 'اٴ', 'ٶ' => 'وٴ', 'ٷ' => 'ۇٴ', 'ٸ' => 'يٴ', 'ำ' => 'ํา', 'ຳ' => 'ໍາ', 'ໜ' => 'ຫນ', 'ໝ' => 'ຫມ', '༌' => '་', 'ཷ' => 'ྲཱྀ', 'ཹ' => 'ླཱྀ', 'ჼ' => 'ნ', 'ᴬ' => 'A', 'ᴭ' => 'Æ', 'ᴮ' => 'B', 'ᴰ' => 'D', 'ᴱ' => 'E', 'ᴲ' => 'Ǝ', 'ᴳ' => 'G', 'ᴴ' => 'H', 'ᴵ' => 'I', 'ᴶ' => 'J', 'ᴷ' => 'K', 'ᴸ' => 'L', 'ᴹ' => 'M', 'ᴺ' => 'N', 'ᴼ' => 'O', 'ᴽ' => 'Ȣ', 'ᴾ' => 'P', 'ᴿ' => 'R', 'ᵀ' => 'T', 'ᵁ' => 'U', 'ᵂ' => 'W', 'ᵃ' => 'a', 'ᵄ' => 'ɐ', 'ᵅ' => 'ɑ', 'ᵆ' => 'ᴂ', 'ᵇ' => 'b', 'ᵈ' => 'd', 'ᵉ' => 'e', 'ᵊ' => 'ə', 'ᵋ' => 'ɛ', 'ᵌ' => 'ɜ', 'ᵍ' => 'g', 'ᵏ' => 'k', 'ᵐ' => 'm', 'ᵑ' => 'ŋ', 'ᵒ' => 'o', 'ᵓ' => 'ɔ', 'ᵔ' => 'ᴖ', 'ᵕ' => 'ᴗ', 'ᵖ' => 'p', 'ᵗ' => 't', 'ᵘ' => 'u', 'ᵙ' => 'ᴝ', 'ᵚ' => 'ɯ', 'ᵛ' => 'v', 'ᵜ' => 'ᴥ', 'ᵝ' => 'β', 'ᵞ' => 'γ', 'ᵟ' => 'δ', 'ᵠ' => 'φ', 'ᵡ' => 'χ', 'ᵢ' => 'i', 'ᵣ' => 'r', 'ᵤ' => 'u', 'ᵥ' => 'v', 'ᵦ' => 'β', 'ᵧ' => 'γ', 'ᵨ' => 'ρ', 'ᵩ' => 'φ', 'ᵪ' => 'χ', 'ᵸ' => 'н', 'ᶛ' => 'ɒ', 'ᶜ' => 'c', 'ᶝ' => 'ɕ', 'ᶞ' => 'ð', 'ᶟ' => 'ɜ', 'ᶠ' => 'f', 'ᶡ' => 'ɟ', 'ᶢ' => 'ɡ', 'ᶣ' => 'ɥ', 'ᶤ' => 'ɨ', 'ᶥ' => 'ɩ', 'ᶦ' => 'ɪ', 'ᶧ' => 'ᵻ', 'ᶨ' => 'ʝ', 'ᶩ' => 'ɭ', 'ᶪ' => 'ᶅ', 'ᶫ' => 'ʟ', 'ᶬ' => 'ɱ', 'ᶭ' => 'ɰ', 'ᶮ' => 'ɲ', 'ᶯ' => 'ɳ', 'ᶰ' => 'ɴ', 'ᶱ' => 'ɵ', 'ᶲ' => 'ɸ', 'ᶳ' => 'ʂ', 'ᶴ' => 'ʃ', 'ᶵ' => 'ƫ', 'ᶶ' => 'ʉ', 'ᶷ' => 'ʊ', 'ᶸ' => 'ᴜ', 'ᶹ' => 'ʋ', 'ᶺ' => 'ʌ', 'ᶻ' => 'z', 'ᶼ' => 'ʐ', 'ᶽ' => 'ʑ', 'ᶾ' => 'ʒ', 'ᶿ' => 'θ', 'ẚ' => 'aʾ', 'ẛ' => 'ṡ', '᾽' => ' ̓', '᾿' => ' ̓', '῀' => ' ͂', '῁' => ' ̈͂', '῍' => ' ̓̀', '῎' => ' ̓́', '῏' => ' ̓͂', '῝' => ' ̔̀', '῞' => ' ̔́', '῟' => ' ̔͂', '῭' => ' ̈̀', '΅' => ' ̈́', '´' => ' ́', '῾' => ' ̔', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', '‑' => '‐', '‗' => ' ̳', '․' => '.', '‥' => '..', '…' => '...', ' ' => ' ', '″' => '′′', '‴' => '′′′', '‶' => '‵‵', '‷' => '‵‵‵', '‼' => '!!', '‾' => ' ̅', '⁇' => '??', '⁈' => '?!', '⁉' => '!?', '⁗' => '′′′′', ' ' => ' ', '⁰' => '0', 'ⁱ' => 'i', '⁴' => '4', '⁵' => '5', '⁶' => '6', '⁷' => '7', '⁸' => '8', '⁹' => '9', '⁺' => '+', '⁻' => '−', '⁼' => '=', '⁽' => '(', '⁾' => ')', 'ⁿ' => 'n', '₀' => '0', '₁' => '1', '₂' => '2', '₃' => '3', '₄' => '4', '₅' => '5', '₆' => '6', '₇' => '7', '₈' => '8', '₉' => '9', '₊' => '+', '₋' => '−', '₌' => '=', '₍' => '(', '₎' => ')', 'ₐ' => 'a', 'ₑ' => 'e', 'ₒ' => 'o', 'ₓ' => 'x', 'ₔ' => 'ə', 'ₕ' => 'h', 'ₖ' => 'k', 'ₗ' => 'l', 'ₘ' => 'm', 'ₙ' => 'n', 'ₚ' => 'p', 'ₛ' => 's', 'ₜ' => 't', '₨' => 'Rs', '℀' => 'a/c', '℁' => 'a/s', 'ℂ' => 'C', '℃' => '°C', '℅' => 'c/o', '℆' => 'c/u', 'ℇ' => 'Ɛ', '℉' => '°F', 'ℊ' => 'g', 'ℋ' => 'H', 'ℌ' => 'H', 'ℍ' => 'H', 'ℎ' => 'h', 'ℏ' => 'ħ', 'ℐ' => 'I', 'ℑ' => 'I', 'ℒ' => 'L', 'ℓ' => 'l', 'ℕ' => 'N', '№' => 'No', 'ℙ' => 'P', 'ℚ' => 'Q', 'ℛ' => 'R', 'ℜ' => 'R', 'ℝ' => 'R', '℠' => 'SM', '℡' => 'TEL', '™' => 'TM', 'ℤ' => 'Z', 'ℨ' => 'Z', 'ℬ' => 'B', 'ℭ' => 'C', 'ℯ' => 'e', 'ℰ' => 'E', 'ℱ' => 'F', 'ℳ' => 'M', 'ℴ' => 'o', 'ℵ' => 'א', 'ℶ' => 'ב', 'ℷ' => 'ג', 'ℸ' => 'ד', 'ℹ' => 'i', '℻' => 'FAX', 'ℼ' => 'π', 'ℽ' => 'γ', 'ℾ' => 'Γ', 'ℿ' => 'Π', '⅀' => '∑', 'ⅅ' => 'D', 'ⅆ' => 'd', 'ⅇ' => 'e', 'ⅈ' => 'i', 'ⅉ' => 'j', '⅐' => '1⁄7', '⅑' => '1⁄9', '⅒' => '1⁄10', '⅓' => '1⁄3', '⅔' => '2⁄3', '⅕' => '1⁄5', '⅖' => '2⁄5', '⅗' => '3⁄5', '⅘' => '4⁄5', '⅙' => '1⁄6', '⅚' => '5⁄6', '⅛' => '1⁄8', '⅜' => '3⁄8', '⅝' => '5⁄8', '⅞' => '7⁄8', '⅟' => '1⁄', 'Ⅰ' => 'I', 'Ⅱ' => 'II', 'Ⅲ' => 'III', 'Ⅳ' => 'IV', 'Ⅴ' => 'V', 'Ⅵ' => 'VI', 'Ⅶ' => 'VII', 'Ⅷ' => 'VIII', 'Ⅸ' => 'IX', 'Ⅹ' => 'X', 'Ⅺ' => 'XI', 'Ⅻ' => 'XII', 'Ⅼ' => 'L', 'Ⅽ' => 'C', 'Ⅾ' => 'D', 'Ⅿ' => 'M', 'ⅰ' => 'i', 'ⅱ' => 'ii', 'ⅲ' => 'iii', 'ⅳ' => 'iv', 'ⅴ' => 'v', 'ⅵ' => 'vi', 'ⅶ' => 'vii', 'ⅷ' => 'viii', 'ⅸ' => 'ix', 'ⅹ' => 'x', 'ⅺ' => 'xi', 'ⅻ' => 'xii', 'ⅼ' => 'l', 'ⅽ' => 'c', 'ⅾ' => 'd', 'ⅿ' => 'm', '↉' => '0⁄3', '∬' => '∫∫', '∭' => '∫∫∫', '∯' => '∮∮', '∰' => '∮∮∮', '①' => '1', '②' => '2', '③' => '3', '④' => '4', '⑤' => '5', '⑥' => '6', '⑦' => '7', '⑧' => '8', '⑨' => '9', '⑩' => '10', '⑪' => '11', '⑫' => '12', '⑬' => '13', '⑭' => '14', '⑮' => '15', '⑯' => '16', '⑰' => '17', '⑱' => '18', '⑲' => '19', '⑳' => '20', '⑴' => '(1)', '⑵' => '(2)', '⑶' => '(3)', '⑷' => '(4)', '⑸' => '(5)', '⑹' => '(6)', '⑺' => '(7)', '⑻' => '(8)', '⑼' => '(9)', '⑽' => '(10)', '⑾' => '(11)', '⑿' => '(12)', '⒀' => '(13)', '⒁' => '(14)', '⒂' => '(15)', '⒃' => '(16)', '⒄' => '(17)', '⒅' => '(18)', '⒆' => '(19)', '⒇' => '(20)', '⒈' => '1.', '⒉' => '2.', '⒊' => '3.', '⒋' => '4.', '⒌' => '5.', '⒍' => '6.', '⒎' => '7.', '⒏' => '8.', '⒐' => '9.', '⒑' => '10.', '⒒' => '11.', '⒓' => '12.', '⒔' => '13.', '⒕' => '14.', '⒖' => '15.', '⒗' => '16.', '⒘' => '17.', '⒙' => '18.', '⒚' => '19.', '⒛' => '20.', '⒜' => '(a)', '⒝' => '(b)', '⒞' => '(c)', '⒟' => '(d)', '⒠' => '(e)', '⒡' => '(f)', '⒢' => '(g)', '⒣' => '(h)', '⒤' => '(i)', '⒥' => '(j)', '⒦' => '(k)', '⒧' => '(l)', '⒨' => '(m)', '⒩' => '(n)', '⒪' => '(o)', '⒫' => '(p)', '⒬' => '(q)', '⒭' => '(r)', '⒮' => '(s)', '⒯' => '(t)', '⒰' => '(u)', '⒱' => '(v)', '⒲' => '(w)', '⒳' => '(x)', '⒴' => '(y)', '⒵' => '(z)', 'Ⓐ' => 'A', 'Ⓑ' => 'B', 'Ⓒ' => 'C', 'Ⓓ' => 'D', 'Ⓔ' => 'E', 'Ⓕ' => 'F', 'Ⓖ' => 'G', 'Ⓗ' => 'H', 'Ⓘ' => 'I', 'Ⓙ' => 'J', 'Ⓚ' => 'K', 'Ⓛ' => 'L', 'Ⓜ' => 'M', 'Ⓝ' => 'N', 'Ⓞ' => 'O', 'Ⓟ' => 'P', 'Ⓠ' => 'Q', 'Ⓡ' => 'R', 'Ⓢ' => 'S', 'Ⓣ' => 'T', 'Ⓤ' => 'U', 'Ⓥ' => 'V', 'Ⓦ' => 'W', 'Ⓧ' => 'X', 'Ⓨ' => 'Y', 'Ⓩ' => 'Z', 'ⓐ' => 'a', 'ⓑ' => 'b', 'ⓒ' => 'c', 'ⓓ' => 'd', 'ⓔ' => 'e', 'ⓕ' => 'f', 'ⓖ' => 'g', 'ⓗ' => 'h', 'ⓘ' => 'i', 'ⓙ' => 'j', 'ⓚ' => 'k', 'ⓛ' => 'l', 'ⓜ' => 'm', 'ⓝ' => 'n', 'ⓞ' => 'o', 'ⓟ' => 'p', 'ⓠ' => 'q', 'ⓡ' => 'r', 'ⓢ' => 's', 'ⓣ' => 't', 'ⓤ' => 'u', 'ⓥ' => 'v', 'ⓦ' => 'w', 'ⓧ' => 'x', 'ⓨ' => 'y', 'ⓩ' => 'z', '⓪' => '0', '⨌' => '∫∫∫∫', '⩴' => '::=', '⩵' => '==', '⩶' => '===', 'ⱼ' => 'j', 'ⱽ' => 'V', 'ⵯ' => 'ⵡ', '⺟' => '母', '⻳' => '龟', '⼀' => '一', '⼁' => '丨', '⼂' => '丶', '⼃' => '丿', '⼄' => '乙', '⼅' => '亅', '⼆' => '二', '⼇' => '亠', '⼈' => '人', '⼉' => '儿', '⼊' => '入', '⼋' => '八', '⼌' => '冂', '⼍' => '冖', '⼎' => '冫', '⼏' => '几', '⼐' => '凵', '⼑' => '刀', '⼒' => '力', '⼓' => '勹', '⼔' => '匕', '⼕' => '匚', '⼖' => '匸', '⼗' => '十', '⼘' => '卜', '⼙' => '卩', '⼚' => '厂', '⼛' => '厶', '⼜' => '又', '⼝' => '口', '⼞' => '囗', '⼟' => '土', '⼠' => '士', '⼡' => '夂', '⼢' => '夊', '⼣' => '夕', '⼤' => '大', '⼥' => '女', '⼦' => '子', '⼧' => '宀', '⼨' => '寸', '⼩' => '小', '⼪' => '尢', '⼫' => '尸', '⼬' => '屮', '⼭' => '山', '⼮' => '巛', '⼯' => '工', '⼰' => '己', '⼱' => '巾', '⼲' => '干', '⼳' => '幺', '⼴' => '广', '⼵' => '廴', '⼶' => '廾', '⼷' => '弋', '⼸' => '弓', '⼹' => '彐', '⼺' => '彡', '⼻' => '彳', '⼼' => '心', '⼽' => '戈', '⼾' => '戶', '⼿' => '手', '⽀' => '支', '⽁' => '攴', '⽂' => '文', '⽃' => '斗', '⽄' => '斤', '⽅' => '方', '⽆' => '无', '⽇' => '日', '⽈' => '曰', '⽉' => '月', '⽊' => '木', '⽋' => '欠', '⽌' => '止', '⽍' => '歹', '⽎' => '殳', '⽏' => '毋', '⽐' => '比', '⽑' => '毛', '⽒' => '氏', '⽓' => '气', '⽔' => '水', '⽕' => '火', '⽖' => '爪', '⽗' => '父', '⽘' => '爻', '⽙' => '爿', '⽚' => '片', '⽛' => '牙', '⽜' => '牛', '⽝' => '犬', '⽞' => '玄', '⽟' => '玉', '⽠' => '瓜', '⽡' => '瓦', '⽢' => '甘', '⽣' => '生', '⽤' => '用', '⽥' => '田', '⽦' => '疋', '⽧' => '疒', '⽨' => '癶', '⽩' => '白', '⽪' => '皮', '⽫' => '皿', '⽬' => '目', '⽭' => '矛', '⽮' => '矢', '⽯' => '石', '⽰' => '示', '⽱' => '禸', '⽲' => '禾', '⽳' => '穴', '⽴' => '立', '⽵' => '竹', '⽶' => '米', '⽷' => '糸', '⽸' => '缶', '⽹' => '网', '⽺' => '羊', '⽻' => '羽', '⽼' => '老', '⽽' => '而', '⽾' => '耒', '⽿' => '耳', '⾀' => '聿', '⾁' => '肉', '⾂' => '臣', '⾃' => '自', '⾄' => '至', '⾅' => '臼', '⾆' => '舌', '⾇' => '舛', '⾈' => '舟', '⾉' => '艮', '⾊' => '色', '⾋' => '艸', '⾌' => '虍', '⾍' => '虫', '⾎' => '血', '⾏' => '行', '⾐' => '衣', '⾑' => '襾', '⾒' => '見', '⾓' => '角', '⾔' => '言', '⾕' => '谷', '⾖' => '豆', '⾗' => '豕', '⾘' => '豸', '⾙' => '貝', '⾚' => '赤', '⾛' => '走', '⾜' => '足', '⾝' => '身', '⾞' => '車', '⾟' => '辛', '⾠' => '辰', '⾡' => '辵', '⾢' => '邑', '⾣' => '酉', '⾤' => '釆', '⾥' => '里', '⾦' => '金', '⾧' => '長', '⾨' => '門', '⾩' => '阜', '⾪' => '隶', '⾫' => '隹', '⾬' => '雨', '⾭' => '靑', '⾮' => '非', '⾯' => '面', '⾰' => '革', '⾱' => '韋', '⾲' => '韭', '⾳' => '音', '⾴' => '頁', '⾵' => '風', '⾶' => '飛', '⾷' => '食', '⾸' => '首', '⾹' => '香', '⾺' => '馬', '⾻' => '骨', '⾼' => '高', '⾽' => '髟', '⾾' => '鬥', '⾿' => '鬯', '⿀' => '鬲', '⿁' => '鬼', '⿂' => '魚', '⿃' => '鳥', '⿄' => '鹵', '⿅' => '鹿', '⿆' => '麥', '⿇' => '麻', '⿈' => '黃', '⿉' => '黍', '⿊' => '黑', '⿋' => '黹', '⿌' => '黽', '⿍' => '鼎', '⿎' => '鼓', '⿏' => '鼠', '⿐' => '鼻', '⿑' => '齊', '⿒' => '齒', '⿓' => '龍', '⿔' => '龜', '⿕' => '龠', ' ' => ' ', '〶' => '〒', '〸' => '十', '〹' => '卄', '〺' => '卅', '゛' => ' ゙', '゜' => ' ゚', 'ゟ' => 'より', 'ヿ' => 'コト', 'ㄱ' => 'ᄀ', 'ㄲ' => 'ᄁ', 'ㄳ' => 'ᆪ', 'ㄴ' => 'ᄂ', 'ㄵ' => 'ᆬ', 'ㄶ' => 'ᆭ', 'ㄷ' => 'ᄃ', 'ㄸ' => 'ᄄ', 'ㄹ' => 'ᄅ', 'ㄺ' => 'ᆰ', 'ㄻ' => 'ᆱ', 'ㄼ' => 'ᆲ', 'ㄽ' => 'ᆳ', 'ㄾ' => 'ᆴ', 'ㄿ' => 'ᆵ', 'ㅀ' => 'ᄚ', 'ㅁ' => 'ᄆ', 'ㅂ' => 'ᄇ', 'ㅃ' => 'ᄈ', 'ㅄ' => 'ᄡ', 'ㅅ' => 'ᄉ', 'ㅆ' => 'ᄊ', 'ㅇ' => 'ᄋ', 'ㅈ' => 'ᄌ', 'ㅉ' => 'ᄍ', 'ㅊ' => 'ᄎ', 'ㅋ' => 'ᄏ', 'ㅌ' => 'ᄐ', 'ㅍ' => 'ᄑ', 'ㅎ' => 'ᄒ', 'ㅏ' => 'ᅡ', 'ㅐ' => 'ᅢ', 'ㅑ' => 'ᅣ', 'ㅒ' => 'ᅤ', 'ㅓ' => 'ᅥ', 'ㅔ' => 'ᅦ', 'ㅕ' => 'ᅧ', 'ㅖ' => 'ᅨ', 'ㅗ' => 'ᅩ', 'ㅘ' => 'ᅪ', 'ㅙ' => 'ᅫ', 'ㅚ' => 'ᅬ', 'ㅛ' => 'ᅭ', 'ㅜ' => 'ᅮ', 'ㅝ' => 'ᅯ', 'ㅞ' => 'ᅰ', 'ㅟ' => 'ᅱ', 'ㅠ' => 'ᅲ', 'ㅡ' => 'ᅳ', 'ㅢ' => 'ᅴ', 'ㅣ' => 'ᅵ', 'ㅤ' => 'ᅠ', 'ㅥ' => 'ᄔ', 'ㅦ' => 'ᄕ', 'ㅧ' => 'ᇇ', 'ㅨ' => 'ᇈ', 'ㅩ' => 'ᇌ', 'ㅪ' => 'ᇎ', 'ㅫ' => 'ᇓ', 'ㅬ' => 'ᇗ', 'ㅭ' => 'ᇙ', 'ㅮ' => 'ᄜ', 'ㅯ' => 'ᇝ', 'ㅰ' => 'ᇟ', 'ㅱ' => 'ᄝ', 'ㅲ' => 'ᄞ', 'ㅳ' => 'ᄠ', 'ㅴ' => 'ᄢ', 'ㅵ' => 'ᄣ', 'ㅶ' => 'ᄧ', 'ㅷ' => 'ᄩ', 'ㅸ' => 'ᄫ', 'ㅹ' => 'ᄬ', 'ㅺ' => 'ᄭ', 'ㅻ' => 'ᄮ', 'ㅼ' => 'ᄯ', 'ㅽ' => 'ᄲ', 'ㅾ' => 'ᄶ', 'ㅿ' => 'ᅀ', 'ㆀ' => 'ᅇ', 'ㆁ' => 'ᅌ', 'ㆂ' => 'ᇱ', 'ㆃ' => 'ᇲ', 'ㆄ' => 'ᅗ', 'ㆅ' => 'ᅘ', 'ㆆ' => 'ᅙ', 'ㆇ' => 'ᆄ', 'ㆈ' => 'ᆅ', 'ㆉ' => 'ᆈ', 'ㆊ' => 'ᆑ', 'ㆋ' => 'ᆒ', 'ㆌ' => 'ᆔ', 'ㆍ' => 'ᆞ', 'ㆎ' => 'ᆡ', '㆒' => '一', '㆓' => '二', '㆔' => '三', '㆕' => '四', '㆖' => '上', '㆗' => '中', '㆘' => '下', '㆙' => '甲', '㆚' => '乙', '㆛' => '丙', '㆜' => '丁', '㆝' => '天', '㆞' => '地', '㆟' => '人', '㈀' => '(ᄀ)', '㈁' => '(ᄂ)', '㈂' => '(ᄃ)', '㈃' => '(ᄅ)', '㈄' => '(ᄆ)', '㈅' => '(ᄇ)', '㈆' => '(ᄉ)', '㈇' => '(ᄋ)', '㈈' => '(ᄌ)', '㈉' => '(ᄎ)', '㈊' => '(ᄏ)', '㈋' => '(ᄐ)', '㈌' => '(ᄑ)', '㈍' => '(ᄒ)', '㈎' => '(가)', '㈏' => '(나)', '㈐' => '(다)', '㈑' => '(라)', '㈒' => '(마)', '㈓' => '(바)', '㈔' => '(사)', '㈕' => '(아)', '㈖' => '(자)', '㈗' => '(차)', '㈘' => '(카)', '㈙' => '(타)', '㈚' => '(파)', '㈛' => '(하)', '㈜' => '(주)', '㈝' => '(오전)', '㈞' => '(오후)', '㈠' => '(一)', '㈡' => '(二)', '㈢' => '(三)', '㈣' => '(四)', '㈤' => '(五)', '㈥' => '(六)', '㈦' => '(七)', '㈧' => '(八)', '㈨' => '(九)', '㈩' => '(十)', '㈪' => '(月)', '㈫' => '(火)', '㈬' => '(水)', '㈭' => '(木)', '㈮' => '(金)', '㈯' => '(土)', '㈰' => '(日)', '㈱' => '(株)', '㈲' => '(有)', '㈳' => '(社)', '㈴' => '(名)', '㈵' => '(特)', '㈶' => '(財)', '㈷' => '(祝)', '㈸' => '(労)', '㈹' => '(代)', '㈺' => '(呼)', '㈻' => '(学)', '㈼' => '(監)', '㈽' => '(企)', '㈾' => '(資)', '㈿' => '(協)', '㉀' => '(祭)', '㉁' => '(休)', '㉂' => '(自)', '㉃' => '(至)', '㉄' => '問', '㉅' => '幼', '㉆' => '文', '㉇' => '箏', '㉐' => 'PTE', '㉑' => '21', '㉒' => '22', '㉓' => '23', '㉔' => '24', '㉕' => '25', '㉖' => '26', '㉗' => '27', '㉘' => '28', '㉙' => '29', '㉚' => '30', '㉛' => '31', '㉜' => '32', '㉝' => '33', '㉞' => '34', '㉟' => '35', '㉠' => 'ᄀ', '㉡' => 'ᄂ', '㉢' => 'ᄃ', '㉣' => 'ᄅ', '㉤' => 'ᄆ', '㉥' => 'ᄇ', '㉦' => 'ᄉ', '㉧' => 'ᄋ', '㉨' => 'ᄌ', '㉩' => 'ᄎ', '㉪' => 'ᄏ', '㉫' => 'ᄐ', '㉬' => 'ᄑ', '㉭' => 'ᄒ', '㉮' => '가', '㉯' => '나', '㉰' => '다', '㉱' => '라', '㉲' => '마', '㉳' => '바', '㉴' => '사', '㉵' => '아', '㉶' => '자', '㉷' => '차', '㉸' => '카', '㉹' => '타', '㉺' => '파', '㉻' => '하', '㉼' => '참고', '㉽' => '주의', '㉾' => '우', '㊀' => '一', '㊁' => '二', '㊂' => '三', '㊃' => '四', '㊄' => '五', '㊅' => '六', '㊆' => '七', '㊇' => '八', '㊈' => '九', '㊉' => '十', '㊊' => '月', '㊋' => '火', '㊌' => '水', '㊍' => '木', '㊎' => '金', '㊏' => '土', '㊐' => '日', '㊑' => '株', '㊒' => '有', '㊓' => '社', '㊔' => '名', '㊕' => '特', '㊖' => '財', '㊗' => '祝', '㊘' => '労', '㊙' => '秘', '㊚' => '男', '㊛' => '女', '㊜' => '適', '㊝' => '優', '㊞' => '印', '㊟' => '注', '㊠' => '項', '㊡' => '休', '㊢' => '写', '㊣' => '正', '㊤' => '上', '㊥' => '中', '㊦' => '下', '㊧' => '左', '㊨' => '右', '㊩' => '医', '㊪' => '宗', '㊫' => '学', '㊬' => '監', '㊭' => '企', '㊮' => '資', '㊯' => '協', '㊰' => '夜', '㊱' => '36', '㊲' => '37', '㊳' => '38', '㊴' => '39', '㊵' => '40', '㊶' => '41', '㊷' => '42', '㊸' => '43', '㊹' => '44', '㊺' => '45', '㊻' => '46', '㊼' => '47', '㊽' => '48', '㊾' => '49', '㊿' => '50', '㋀' => '1月', '㋁' => '2月', '㋂' => '3月', '㋃' => '4月', '㋄' => '5月', '㋅' => '6月', '㋆' => '7月', '㋇' => '8月', '㋈' => '9月', '㋉' => '10月', '㋊' => '11月', '㋋' => '12月', '㋌' => 'Hg', '㋍' => 'erg', '㋎' => 'eV', '㋏' => 'LTD', '㋐' => 'ア', '㋑' => 'イ', '㋒' => 'ウ', '㋓' => 'エ', '㋔' => 'オ', '㋕' => 'カ', '㋖' => 'キ', '㋗' => 'ク', '㋘' => 'ケ', '㋙' => 'コ', '㋚' => 'サ', '㋛' => 'シ', '㋜' => 'ス', '㋝' => 'セ', '㋞' => 'ソ', '㋟' => 'タ', '㋠' => 'チ', '㋡' => 'ツ', '㋢' => 'テ', '㋣' => 'ト', '㋤' => 'ナ', '㋥' => 'ニ', '㋦' => 'ヌ', '㋧' => 'ネ', '㋨' => 'ノ', '㋩' => 'ハ', '㋪' => 'ヒ', '㋫' => 'フ', '㋬' => 'ヘ', '㋭' => 'ホ', '㋮' => 'マ', '㋯' => 'ミ', '㋰' => 'ム', '㋱' => 'メ', '㋲' => 'モ', '㋳' => 'ヤ', '㋴' => 'ユ', '㋵' => 'ヨ', '㋶' => 'ラ', '㋷' => 'リ', '㋸' => 'ル', '㋹' => 'レ', '㋺' => 'ロ', '㋻' => 'ワ', '㋼' => 'ヰ', '㋽' => 'ヱ', '㋾' => 'ヲ', '㋿' => '令和', '㌀' => 'アパート', '㌁' => 'アルファ', '㌂' => 'アンペア', '㌃' => 'アール', '㌄' => 'イニング', '㌅' => 'インチ', '㌆' => 'ウォン', '㌇' => 'エスクード', '㌈' => 'エーカー', '㌉' => 'オンス', '㌊' => 'オーム', '㌋' => 'カイリ', '㌌' => 'カラット', '㌍' => 'カロリー', '㌎' => 'ガロン', '㌏' => 'ガンマ', '㌐' => 'ギガ', '㌑' => 'ギニー', '㌒' => 'キュリー', '㌓' => 'ギルダー', '㌔' => 'キロ', '㌕' => 'キログラム', '㌖' => 'キロメートル', '㌗' => 'キロワット', '㌘' => 'グラム', '㌙' => 'グラムトン', '㌚' => 'クルゼイロ', '㌛' => 'クローネ', '㌜' => 'ケース', '㌝' => 'コルナ', '㌞' => 'コーポ', '㌟' => 'サイクル', '㌠' => 'サンチーム', '㌡' => 'シリング', '㌢' => 'センチ', '㌣' => 'セント', '㌤' => 'ダース', '㌥' => 'デシ', '㌦' => 'ドル', '㌧' => 'トン', '㌨' => 'ナノ', '㌩' => 'ノット', '㌪' => 'ハイツ', '㌫' => 'パーセント', '㌬' => 'パーツ', '㌭' => 'バーレル', '㌮' => 'ピアストル', '㌯' => 'ピクル', '㌰' => 'ピコ', '㌱' => 'ビル', '㌲' => 'ファラッド', '㌳' => 'フィート', '㌴' => 'ブッシェル', '㌵' => 'フラン', '㌶' => 'ヘクタール', '㌷' => 'ペソ', '㌸' => 'ペニヒ', '㌹' => 'ヘルツ', '㌺' => 'ペンス', '㌻' => 'ページ', '㌼' => 'ベータ', '㌽' => 'ポイント', '㌾' => 'ボルト', '㌿' => 'ホン', '㍀' => 'ポンド', '㍁' => 'ホール', '㍂' => 'ホーン', '㍃' => 'マイクロ', '㍄' => 'マイル', '㍅' => 'マッハ', '㍆' => 'マルク', '㍇' => 'マンション', '㍈' => 'ミクロン', '㍉' => 'ミリ', '㍊' => 'ミリバール', '㍋' => 'メガ', '㍌' => 'メガトン', '㍍' => 'メートル', '㍎' => 'ヤード', '㍏' => 'ヤール', '㍐' => 'ユアン', '㍑' => 'リットル', '㍒' => 'リラ', '㍓' => 'ルピー', '㍔' => 'ルーブル', '㍕' => 'レム', '㍖' => 'レントゲン', '㍗' => 'ワット', '㍘' => '0点', '㍙' => '1点', '㍚' => '2点', '㍛' => '3点', '㍜' => '4点', '㍝' => '5点', '㍞' => '6点', '㍟' => '7点', '㍠' => '8点', '㍡' => '9点', '㍢' => '10点', '㍣' => '11点', '㍤' => '12点', '㍥' => '13点', '㍦' => '14点', '㍧' => '15点', '㍨' => '16点', '㍩' => '17点', '㍪' => '18点', '㍫' => '19点', '㍬' => '20点', '㍭' => '21点', '㍮' => '22点', '㍯' => '23点', '㍰' => '24点', '㍱' => 'hPa', '㍲' => 'da', '㍳' => 'AU', '㍴' => 'bar', '㍵' => 'oV', '㍶' => 'pc', '㍷' => 'dm', '㍸' => 'dm2', '㍹' => 'dm3', '㍺' => 'IU', '㍻' => '平成', '㍼' => '昭和', '㍽' => '大正', '㍾' => '明治', '㍿' => '株式会社', '㎀' => 'pA', '㎁' => 'nA', '㎂' => 'μA', '㎃' => 'mA', '㎄' => 'kA', '㎅' => 'KB', '㎆' => 'MB', '㎇' => 'GB', '㎈' => 'cal', '㎉' => 'kcal', '㎊' => 'pF', '㎋' => 'nF', '㎌' => 'μF', '㎍' => 'μg', '㎎' => 'mg', '㎏' => 'kg', '㎐' => 'Hz', '㎑' => 'kHz', '㎒' => 'MHz', '㎓' => 'GHz', '㎔' => 'THz', '㎕' => 'μl', '㎖' => 'ml', '㎗' => 'dl', '㎘' => 'kl', '㎙' => 'fm', '㎚' => 'nm', '㎛' => 'μm', '㎜' => 'mm', '㎝' => 'cm', '㎞' => 'km', '㎟' => 'mm2', '㎠' => 'cm2', '㎡' => 'm2', '㎢' => 'km2', '㎣' => 'mm3', '㎤' => 'cm3', '㎥' => 'm3', '㎦' => 'km3', '㎧' => 'm∕s', '㎨' => 'm∕s2', '㎩' => 'Pa', '㎪' => 'kPa', '㎫' => 'MPa', '㎬' => 'GPa', '㎭' => 'rad', '㎮' => 'rad∕s', '㎯' => 'rad∕s2', '㎰' => 'ps', '㎱' => 'ns', '㎲' => 'μs', '㎳' => 'ms', '㎴' => 'pV', '㎵' => 'nV', '㎶' => 'μV', '㎷' => 'mV', '㎸' => 'kV', '㎹' => 'MV', '㎺' => 'pW', '㎻' => 'nW', '㎼' => 'μW', '㎽' => 'mW', '㎾' => 'kW', '㎿' => 'MW', '㏀' => 'kΩ', '㏁' => 'MΩ', '㏂' => 'a.m.', '㏃' => 'Bq', '㏄' => 'cc', '㏅' => 'cd', '㏆' => 'C∕kg', '㏇' => 'Co.', '㏈' => 'dB', '㏉' => 'Gy', '㏊' => 'ha', '㏋' => 'HP', '㏌' => 'in', '㏍' => 'KK', '㏎' => 'KM', '㏏' => 'kt', '㏐' => 'lm', '㏑' => 'ln', '㏒' => 'log', '㏓' => 'lx', '㏔' => 'mb', '㏕' => 'mil', '㏖' => 'mol', '㏗' => 'PH', '㏘' => 'p.m.', '㏙' => 'PPM', '㏚' => 'PR', '㏛' => 'sr', '㏜' => 'Sv', '㏝' => 'Wb', '㏞' => 'V∕m', '㏟' => 'A∕m', '㏠' => '1日', '㏡' => '2日', '㏢' => '3日', '㏣' => '4日', '㏤' => '5日', '㏥' => '6日', '㏦' => '7日', '㏧' => '8日', '㏨' => '9日', '㏩' => '10日', '㏪' => '11日', '㏫' => '12日', '㏬' => '13日', '㏭' => '14日', '㏮' => '15日', '㏯' => '16日', '㏰' => '17日', '㏱' => '18日', '㏲' => '19日', '㏳' => '20日', '㏴' => '21日', '㏵' => '22日', '㏶' => '23日', '㏷' => '24日', '㏸' => '25日', '㏹' => '26日', '㏺' => '27日', '㏻' => '28日', '㏼' => '29日', '㏽' => '30日', '㏾' => '31日', '㏿' => 'gal', 'ꚜ' => 'ъ', 'ꚝ' => 'ь', 'ꝰ' => 'ꝯ', 'ꟸ' => 'Ħ', 'ꟹ' => 'œ', 'ꭜ' => 'ꜧ', 'ꭝ' => 'ꬷ', 'ꭞ' => 'ɫ', 'ꭟ' => 'ꭒ', 'ꭩ' => 'ʍ', 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', 'ffl' => 'ffl', 'ſt' => 'st', 'st' => 'st', 'ﬓ' => 'մն', 'ﬔ' => 'մե', 'ﬕ' => 'մի', 'ﬖ' => 'վն', 'ﬗ' => 'մխ', 'ﬠ' => 'ע', 'ﬡ' => 'א', 'ﬢ' => 'ד', 'ﬣ' => 'ה', 'ﬤ' => 'כ', 'ﬥ' => 'ל', 'ﬦ' => 'ם', 'ﬧ' => 'ר', 'ﬨ' => 'ת', '﬩' => '+', 'ﭏ' => 'אל', 'ﭐ' => 'ٱ', 'ﭑ' => 'ٱ', 'ﭒ' => 'ٻ', 'ﭓ' => 'ٻ', 'ﭔ' => 'ٻ', 'ﭕ' => 'ٻ', 'ﭖ' => 'پ', 'ﭗ' => 'پ', 'ﭘ' => 'پ', 'ﭙ' => 'پ', 'ﭚ' => 'ڀ', 'ﭛ' => 'ڀ', 'ﭜ' => 'ڀ', 'ﭝ' => 'ڀ', 'ﭞ' => 'ٺ', 'ﭟ' => 'ٺ', 'ﭠ' => 'ٺ', 'ﭡ' => 'ٺ', 'ﭢ' => 'ٿ', 'ﭣ' => 'ٿ', 'ﭤ' => 'ٿ', 'ﭥ' => 'ٿ', 'ﭦ' => 'ٹ', 'ﭧ' => 'ٹ', 'ﭨ' => 'ٹ', 'ﭩ' => 'ٹ', 'ﭪ' => 'ڤ', 'ﭫ' => 'ڤ', 'ﭬ' => 'ڤ', 'ﭭ' => 'ڤ', 'ﭮ' => 'ڦ', 'ﭯ' => 'ڦ', 'ﭰ' => 'ڦ', 'ﭱ' => 'ڦ', 'ﭲ' => 'ڄ', 'ﭳ' => 'ڄ', 'ﭴ' => 'ڄ', 'ﭵ' => 'ڄ', 'ﭶ' => 'ڃ', 'ﭷ' => 'ڃ', 'ﭸ' => 'ڃ', 'ﭹ' => 'ڃ', 'ﭺ' => 'چ', 'ﭻ' => 'چ', 'ﭼ' => 'چ', 'ﭽ' => 'چ', 'ﭾ' => 'ڇ', 'ﭿ' => 'ڇ', 'ﮀ' => 'ڇ', 'ﮁ' => 'ڇ', 'ﮂ' => 'ڍ', 'ﮃ' => 'ڍ', 'ﮄ' => 'ڌ', 'ﮅ' => 'ڌ', 'ﮆ' => 'ڎ', 'ﮇ' => 'ڎ', 'ﮈ' => 'ڈ', 'ﮉ' => 'ڈ', 'ﮊ' => 'ژ', 'ﮋ' => 'ژ', 'ﮌ' => 'ڑ', 'ﮍ' => 'ڑ', 'ﮎ' => 'ک', 'ﮏ' => 'ک', 'ﮐ' => 'ک', 'ﮑ' => 'ک', 'ﮒ' => 'گ', 'ﮓ' => 'گ', 'ﮔ' => 'گ', 'ﮕ' => 'گ', 'ﮖ' => 'ڳ', 'ﮗ' => 'ڳ', 'ﮘ' => 'ڳ', 'ﮙ' => 'ڳ', 'ﮚ' => 'ڱ', 'ﮛ' => 'ڱ', 'ﮜ' => 'ڱ', 'ﮝ' => 'ڱ', 'ﮞ' => 'ں', 'ﮟ' => 'ں', 'ﮠ' => 'ڻ', 'ﮡ' => 'ڻ', 'ﮢ' => 'ڻ', 'ﮣ' => 'ڻ', 'ﮤ' => 'ۀ', 'ﮥ' => 'ۀ', 'ﮦ' => 'ہ', 'ﮧ' => 'ہ', 'ﮨ' => 'ہ', 'ﮩ' => 'ہ', 'ﮪ' => 'ھ', 'ﮫ' => 'ھ', 'ﮬ' => 'ھ', 'ﮭ' => 'ھ', 'ﮮ' => 'ے', 'ﮯ' => 'ے', 'ﮰ' => 'ۓ', 'ﮱ' => 'ۓ', 'ﯓ' => 'ڭ', 'ﯔ' => 'ڭ', 'ﯕ' => 'ڭ', 'ﯖ' => 'ڭ', 'ﯗ' => 'ۇ', 'ﯘ' => 'ۇ', 'ﯙ' => 'ۆ', 'ﯚ' => 'ۆ', 'ﯛ' => 'ۈ', 'ﯜ' => 'ۈ', 'ﯝ' => 'ۇٴ', 'ﯞ' => 'ۋ', 'ﯟ' => 'ۋ', 'ﯠ' => 'ۅ', 'ﯡ' => 'ۅ', 'ﯢ' => 'ۉ', 'ﯣ' => 'ۉ', 'ﯤ' => 'ې', 'ﯥ' => 'ې', 'ﯦ' => 'ې', 'ﯧ' => 'ې', 'ﯨ' => 'ى', 'ﯩ' => 'ى', 'ﯪ' => 'ئا', 'ﯫ' => 'ئا', 'ﯬ' => 'ئە', 'ﯭ' => 'ئە', 'ﯮ' => 'ئو', 'ﯯ' => 'ئو', 'ﯰ' => 'ئۇ', 'ﯱ' => 'ئۇ', 'ﯲ' => 'ئۆ', 'ﯳ' => 'ئۆ', 'ﯴ' => 'ئۈ', 'ﯵ' => 'ئۈ', 'ﯶ' => 'ئې', 'ﯷ' => 'ئې', 'ﯸ' => 'ئې', 'ﯹ' => 'ئى', 'ﯺ' => 'ئى', 'ﯻ' => 'ئى', 'ﯼ' => 'ی', 'ﯽ' => 'ی', 'ﯾ' => 'ی', 'ﯿ' => 'ی', 'ﰀ' => 'ئج', 'ﰁ' => 'ئح', 'ﰂ' => 'ئم', 'ﰃ' => 'ئى', 'ﰄ' => 'ئي', 'ﰅ' => 'بج', 'ﰆ' => 'بح', 'ﰇ' => 'بخ', 'ﰈ' => 'بم', 'ﰉ' => 'بى', 'ﰊ' => 'بي', 'ﰋ' => 'تج', 'ﰌ' => 'تح', 'ﰍ' => 'تخ', 'ﰎ' => 'تم', 'ﰏ' => 'تى', 'ﰐ' => 'تي', 'ﰑ' => 'ثج', 'ﰒ' => 'ثم', 'ﰓ' => 'ثى', 'ﰔ' => 'ثي', 'ﰕ' => 'جح', 'ﰖ' => 'جم', 'ﰗ' => 'حج', 'ﰘ' => 'حم', 'ﰙ' => 'خج', 'ﰚ' => 'خح', 'ﰛ' => 'خم', 'ﰜ' => 'سج', 'ﰝ' => 'سح', 'ﰞ' => 'سخ', 'ﰟ' => 'سم', 'ﰠ' => 'صح', 'ﰡ' => 'صم', 'ﰢ' => 'ضج', 'ﰣ' => 'ضح', 'ﰤ' => 'ضخ', 'ﰥ' => 'ضم', 'ﰦ' => 'طح', 'ﰧ' => 'طم', 'ﰨ' => 'ظم', 'ﰩ' => 'عج', 'ﰪ' => 'عم', 'ﰫ' => 'غج', 'ﰬ' => 'غم', 'ﰭ' => 'فج', 'ﰮ' => 'فح', 'ﰯ' => 'فخ', 'ﰰ' => 'فم', 'ﰱ' => 'فى', 'ﰲ' => 'في', 'ﰳ' => 'قح', 'ﰴ' => 'قم', 'ﰵ' => 'قى', 'ﰶ' => 'قي', 'ﰷ' => 'كا', 'ﰸ' => 'كج', 'ﰹ' => 'كح', 'ﰺ' => 'كخ', 'ﰻ' => 'كل', 'ﰼ' => 'كم', 'ﰽ' => 'كى', 'ﰾ' => 'كي', 'ﰿ' => 'لج', 'ﱀ' => 'لح', 'ﱁ' => 'لخ', 'ﱂ' => 'لم', 'ﱃ' => 'لى', 'ﱄ' => 'لي', 'ﱅ' => 'مج', 'ﱆ' => 'مح', 'ﱇ' => 'مخ', 'ﱈ' => 'مم', 'ﱉ' => 'مى', 'ﱊ' => 'مي', 'ﱋ' => 'نج', 'ﱌ' => 'نح', 'ﱍ' => 'نخ', 'ﱎ' => 'نم', 'ﱏ' => 'نى', 'ﱐ' => 'ني', 'ﱑ' => 'هج', 'ﱒ' => 'هم', 'ﱓ' => 'هى', 'ﱔ' => 'هي', 'ﱕ' => 'يج', 'ﱖ' => 'يح', 'ﱗ' => 'يخ', 'ﱘ' => 'يم', 'ﱙ' => 'يى', 'ﱚ' => 'يي', 'ﱛ' => 'ذٰ', 'ﱜ' => 'رٰ', 'ﱝ' => 'ىٰ', 'ﱞ' => ' ٌّ', 'ﱟ' => ' ٍّ', 'ﱠ' => ' َّ', 'ﱡ' => ' ُّ', 'ﱢ' => ' ِّ', 'ﱣ' => ' ّٰ', 'ﱤ' => 'ئر', 'ﱥ' => 'ئز', 'ﱦ' => 'ئم', 'ﱧ' => 'ئن', 'ﱨ' => 'ئى', 'ﱩ' => 'ئي', 'ﱪ' => 'بر', 'ﱫ' => 'بز', 'ﱬ' => 'بم', 'ﱭ' => 'بن', 'ﱮ' => 'بى', 'ﱯ' => 'بي', 'ﱰ' => 'تر', 'ﱱ' => 'تز', 'ﱲ' => 'تم', 'ﱳ' => 'تن', 'ﱴ' => 'تى', 'ﱵ' => 'تي', 'ﱶ' => 'ثر', 'ﱷ' => 'ثز', 'ﱸ' => 'ثم', 'ﱹ' => 'ثن', 'ﱺ' => 'ثى', 'ﱻ' => 'ثي', 'ﱼ' => 'فى', 'ﱽ' => 'في', 'ﱾ' => 'قى', 'ﱿ' => 'قي', 'ﲀ' => 'كا', 'ﲁ' => 'كل', 'ﲂ' => 'كم', 'ﲃ' => 'كى', 'ﲄ' => 'كي', 'ﲅ' => 'لم', 'ﲆ' => 'لى', 'ﲇ' => 'لي', 'ﲈ' => 'ما', 'ﲉ' => 'مم', 'ﲊ' => 'نر', 'ﲋ' => 'نز', 'ﲌ' => 'نم', 'ﲍ' => 'نن', 'ﲎ' => 'نى', 'ﲏ' => 'ني', 'ﲐ' => 'ىٰ', 'ﲑ' => 'ير', 'ﲒ' => 'يز', 'ﲓ' => 'يم', 'ﲔ' => 'ين', 'ﲕ' => 'يى', 'ﲖ' => 'يي', 'ﲗ' => 'ئج', 'ﲘ' => 'ئح', 'ﲙ' => 'ئخ', 'ﲚ' => 'ئم', 'ﲛ' => 'ئه', 'ﲜ' => 'بج', 'ﲝ' => 'بح', 'ﲞ' => 'بخ', 'ﲟ' => 'بم', 'ﲠ' => 'به', 'ﲡ' => 'تج', 'ﲢ' => 'تح', 'ﲣ' => 'تخ', 'ﲤ' => 'تم', 'ﲥ' => 'ته', 'ﲦ' => 'ثم', 'ﲧ' => 'جح', 'ﲨ' => 'جم', 'ﲩ' => 'حج', 'ﲪ' => 'حم', 'ﲫ' => 'خج', 'ﲬ' => 'خم', 'ﲭ' => 'سج', 'ﲮ' => 'سح', 'ﲯ' => 'سخ', 'ﲰ' => 'سم', 'ﲱ' => 'صح', 'ﲲ' => 'صخ', 'ﲳ' => 'صم', 'ﲴ' => 'ضج', 'ﲵ' => 'ضح', 'ﲶ' => 'ضخ', 'ﲷ' => 'ضم', 'ﲸ' => 'طح', 'ﲹ' => 'ظم', 'ﲺ' => 'عج', 'ﲻ' => 'عم', 'ﲼ' => 'غج', 'ﲽ' => 'غم', 'ﲾ' => 'فج', 'ﲿ' => 'فح', 'ﳀ' => 'فخ', 'ﳁ' => 'فم', 'ﳂ' => 'قح', 'ﳃ' => 'قم', 'ﳄ' => 'كج', 'ﳅ' => 'كح', 'ﳆ' => 'كخ', 'ﳇ' => 'كل', 'ﳈ' => 'كم', 'ﳉ' => 'لج', 'ﳊ' => 'لح', 'ﳋ' => 'لخ', 'ﳌ' => 'لم', 'ﳍ' => 'له', 'ﳎ' => 'مج', 'ﳏ' => 'مح', 'ﳐ' => 'مخ', 'ﳑ' => 'مم', 'ﳒ' => 'نج', 'ﳓ' => 'نح', 'ﳔ' => 'نخ', 'ﳕ' => 'نم', 'ﳖ' => 'نه', 'ﳗ' => 'هج', 'ﳘ' => 'هم', 'ﳙ' => 'هٰ', 'ﳚ' => 'يج', 'ﳛ' => 'يح', 'ﳜ' => 'يخ', 'ﳝ' => 'يم', 'ﳞ' => 'يه', 'ﳟ' => 'ئم', 'ﳠ' => 'ئه', 'ﳡ' => 'بم', 'ﳢ' => 'به', 'ﳣ' => 'تم', 'ﳤ' => 'ته', 'ﳥ' => 'ثم', 'ﳦ' => 'ثه', 'ﳧ' => 'سم', 'ﳨ' => 'سه', 'ﳩ' => 'شم', 'ﳪ' => 'شه', 'ﳫ' => 'كل', 'ﳬ' => 'كم', 'ﳭ' => 'لم', 'ﳮ' => 'نم', 'ﳯ' => 'نه', 'ﳰ' => 'يم', 'ﳱ' => 'يه', 'ﳲ' => 'ـَّ', 'ﳳ' => 'ـُّ', 'ﳴ' => 'ـِّ', 'ﳵ' => 'طى', 'ﳶ' => 'طي', 'ﳷ' => 'عى', 'ﳸ' => 'عي', 'ﳹ' => 'غى', 'ﳺ' => 'غي', 'ﳻ' => 'سى', 'ﳼ' => 'سي', 'ﳽ' => 'شى', 'ﳾ' => 'شي', 'ﳿ' => 'حى', 'ﴀ' => 'حي', 'ﴁ' => 'جى', 'ﴂ' => 'جي', 'ﴃ' => 'خى', 'ﴄ' => 'خي', 'ﴅ' => 'صى', 'ﴆ' => 'صي', 'ﴇ' => 'ضى', 'ﴈ' => 'ضي', 'ﴉ' => 'شج', 'ﴊ' => 'شح', 'ﴋ' => 'شخ', 'ﴌ' => 'شم', 'ﴍ' => 'شر', 'ﴎ' => 'سر', 'ﴏ' => 'صر', 'ﴐ' => 'ضر', 'ﴑ' => 'طى', 'ﴒ' => 'طي', 'ﴓ' => 'عى', 'ﴔ' => 'عي', 'ﴕ' => 'غى', 'ﴖ' => 'غي', 'ﴗ' => 'سى', 'ﴘ' => 'سي', 'ﴙ' => 'شى', 'ﴚ' => 'شي', 'ﴛ' => 'حى', 'ﴜ' => 'حي', 'ﴝ' => 'جى', 'ﴞ' => 'جي', 'ﴟ' => 'خى', 'ﴠ' => 'خي', 'ﴡ' => 'صى', 'ﴢ' => 'صي', 'ﴣ' => 'ضى', 'ﴤ' => 'ضي', 'ﴥ' => 'شج', 'ﴦ' => 'شح', 'ﴧ' => 'شخ', 'ﴨ' => 'شم', 'ﴩ' => 'شر', 'ﴪ' => 'سر', 'ﴫ' => 'صر', 'ﴬ' => 'ضر', 'ﴭ' => 'شج', 'ﴮ' => 'شح', 'ﴯ' => 'شخ', 'ﴰ' => 'شم', 'ﴱ' => 'سه', 'ﴲ' => 'شه', 'ﴳ' => 'طم', 'ﴴ' => 'سج', 'ﴵ' => 'سح', 'ﴶ' => 'سخ', 'ﴷ' => 'شج', 'ﴸ' => 'شح', 'ﴹ' => 'شخ', 'ﴺ' => 'طم', 'ﴻ' => 'ظم', 'ﴼ' => 'اً', 'ﴽ' => 'اً', 'ﵐ' => 'تجم', 'ﵑ' => 'تحج', 'ﵒ' => 'تحج', 'ﵓ' => 'تحم', 'ﵔ' => 'تخم', 'ﵕ' => 'تمج', 'ﵖ' => 'تمح', 'ﵗ' => 'تمخ', 'ﵘ' => 'جمح', 'ﵙ' => 'جمح', 'ﵚ' => 'حمي', 'ﵛ' => 'حمى', 'ﵜ' => 'سحج', 'ﵝ' => 'سجح', 'ﵞ' => 'سجى', 'ﵟ' => 'سمح', 'ﵠ' => 'سمح', 'ﵡ' => 'سمج', 'ﵢ' => 'سمم', 'ﵣ' => 'سمم', 'ﵤ' => 'صحح', 'ﵥ' => 'صحح', 'ﵦ' => 'صمم', 'ﵧ' => 'شحم', 'ﵨ' => 'شحم', 'ﵩ' => 'شجي', 'ﵪ' => 'شمخ', 'ﵫ' => 'شمخ', 'ﵬ' => 'شمم', 'ﵭ' => 'شمم', 'ﵮ' => 'ضحى', 'ﵯ' => 'ضخم', 'ﵰ' => 'ضخم', 'ﵱ' => 'طمح', 'ﵲ' => 'طمح', 'ﵳ' => 'طمم', 'ﵴ' => 'طمي', 'ﵵ' => 'عجم', 'ﵶ' => 'عمم', 'ﵷ' => 'عمم', 'ﵸ' => 'عمى', 'ﵹ' => 'غمم', 'ﵺ' => 'غمي', 'ﵻ' => 'غمى', 'ﵼ' => 'فخم', 'ﵽ' => 'فخم', 'ﵾ' => 'قمح', 'ﵿ' => 'قمم', 'ﶀ' => 'لحم', 'ﶁ' => 'لحي', 'ﶂ' => 'لحى', 'ﶃ' => 'لجج', 'ﶄ' => 'لجج', 'ﶅ' => 'لخم', 'ﶆ' => 'لخم', 'ﶇ' => 'لمح', 'ﶈ' => 'لمح', 'ﶉ' => 'محج', 'ﶊ' => 'محم', 'ﶋ' => 'محي', 'ﶌ' => 'مجح', 'ﶍ' => 'مجم', 'ﶎ' => 'مخج', 'ﶏ' => 'مخم', 'ﶒ' => 'مجخ', 'ﶓ' => 'همج', 'ﶔ' => 'همم', 'ﶕ' => 'نحم', 'ﶖ' => 'نحى', 'ﶗ' => 'نجم', 'ﶘ' => 'نجم', 'ﶙ' => 'نجى', 'ﶚ' => 'نمي', 'ﶛ' => 'نمى', 'ﶜ' => 'يمم', 'ﶝ' => 'يمم', 'ﶞ' => 'بخي', 'ﶟ' => 'تجي', 'ﶠ' => 'تجى', 'ﶡ' => 'تخي', 'ﶢ' => 'تخى', 'ﶣ' => 'تمي', 'ﶤ' => 'تمى', 'ﶥ' => 'جمي', 'ﶦ' => 'جحى', 'ﶧ' => 'جمى', 'ﶨ' => 'سخى', 'ﶩ' => 'صحي', 'ﶪ' => 'شحي', 'ﶫ' => 'ضحي', 'ﶬ' => 'لجي', 'ﶭ' => 'لمي', 'ﶮ' => 'يحي', 'ﶯ' => 'يجي', 'ﶰ' => 'يمي', 'ﶱ' => 'ممي', 'ﶲ' => 'قمي', 'ﶳ' => 'نحي', 'ﶴ' => 'قمح', 'ﶵ' => 'لحم', 'ﶶ' => 'عمي', 'ﶷ' => 'كمي', 'ﶸ' => 'نجح', 'ﶹ' => 'مخي', 'ﶺ' => 'لجم', 'ﶻ' => 'كمم', 'ﶼ' => 'لجم', 'ﶽ' => 'نجح', 'ﶾ' => 'جحي', 'ﶿ' => 'حجي', 'ﷀ' => 'مجي', 'ﷁ' => 'فمي', 'ﷂ' => 'بحي', 'ﷃ' => 'كمم', 'ﷄ' => 'عجم', 'ﷅ' => 'صمم', 'ﷆ' => 'سخي', 'ﷇ' => 'نجي', 'ﷰ' => 'صلے', 'ﷱ' => 'قلے', 'ﷲ' => 'الله', 'ﷳ' => 'اكبر', 'ﷴ' => 'محمد', 'ﷵ' => 'صلعم', 'ﷶ' => 'رسول', 'ﷷ' => 'عليه', 'ﷸ' => 'وسلم', 'ﷹ' => 'صلى', 'ﷺ' => 'صلى الله عليه وسلم', 'ﷻ' => 'جل جلاله', '﷼' => 'ریال', '︐' => ',', '︑' => '、', '︒' => '。', '︓' => ':', '︔' => ';', '︕' => '!', '︖' => '?', '︗' => '〖', '︘' => '〗', '︙' => '...', '︰' => '..', '︱' => '—', '︲' => '–', '︳' => '_', '︴' => '_', '︵' => '(', '︶' => ')', '︷' => '{', '︸' => '}', '︹' => '〔', '︺' => '〕', '︻' => '【', '︼' => '】', '︽' => '《', '︾' => '》', '︿' => '〈', '﹀' => '〉', '﹁' => '「', '﹂' => '」', '﹃' => '『', '﹄' => '』', '﹇' => '[', '﹈' => ']', '﹉' => ' ̅', '﹊' => ' ̅', '﹋' => ' ̅', '﹌' => ' ̅', '﹍' => '_', '﹎' => '_', '﹏' => '_', '﹐' => ',', '﹑' => '、', '﹒' => '.', '﹔' => ';', '﹕' => ':', '﹖' => '?', '﹗' => '!', '﹘' => '—', '﹙' => '(', '﹚' => ')', '﹛' => '{', '﹜' => '}', '﹝' => '〔', '﹞' => '〕', '﹟' => '#', '﹠' => '&', '﹡' => '*', '﹢' => '+', '﹣' => '-', '﹤' => '<', '﹥' => '>', '﹦' => '=', '﹨' => '\\', '﹩' => '$', '﹪' => '%', '﹫' => '@', 'ﹰ' => ' ً', 'ﹱ' => 'ـً', 'ﹲ' => ' ٌ', 'ﹴ' => ' ٍ', 'ﹶ' => ' َ', 'ﹷ' => 'ـَ', 'ﹸ' => ' ُ', 'ﹹ' => 'ـُ', 'ﹺ' => ' ِ', 'ﹻ' => 'ـِ', 'ﹼ' => ' ّ', 'ﹽ' => 'ـّ', 'ﹾ' => ' ْ', 'ﹿ' => 'ـْ', 'ﺀ' => 'ء', 'ﺁ' => 'آ', 'ﺂ' => 'آ', 'ﺃ' => 'أ', 'ﺄ' => 'أ', 'ﺅ' => 'ؤ', 'ﺆ' => 'ؤ', 'ﺇ' => 'إ', 'ﺈ' => 'إ', 'ﺉ' => 'ئ', 'ﺊ' => 'ئ', 'ﺋ' => 'ئ', 'ﺌ' => 'ئ', 'ﺍ' => 'ا', 'ﺎ' => 'ا', 'ﺏ' => 'ب', 'ﺐ' => 'ب', 'ﺑ' => 'ب', 'ﺒ' => 'ب', 'ﺓ' => 'ة', 'ﺔ' => 'ة', 'ﺕ' => 'ت', 'ﺖ' => 'ت', 'ﺗ' => 'ت', 'ﺘ' => 'ت', 'ﺙ' => 'ث', 'ﺚ' => 'ث', 'ﺛ' => 'ث', 'ﺜ' => 'ث', 'ﺝ' => 'ج', 'ﺞ' => 'ج', 'ﺟ' => 'ج', 'ﺠ' => 'ج', 'ﺡ' => 'ح', 'ﺢ' => 'ح', 'ﺣ' => 'ح', 'ﺤ' => 'ح', 'ﺥ' => 'خ', 'ﺦ' => 'خ', 'ﺧ' => 'خ', 'ﺨ' => 'خ', 'ﺩ' => 'د', 'ﺪ' => 'د', 'ﺫ' => 'ذ', 'ﺬ' => 'ذ', 'ﺭ' => 'ر', 'ﺮ' => 'ر', 'ﺯ' => 'ز', 'ﺰ' => 'ز', 'ﺱ' => 'س', 'ﺲ' => 'س', 'ﺳ' => 'س', 'ﺴ' => 'س', 'ﺵ' => 'ش', 'ﺶ' => 'ش', 'ﺷ' => 'ش', 'ﺸ' => 'ش', 'ﺹ' => 'ص', 'ﺺ' => 'ص', 'ﺻ' => 'ص', 'ﺼ' => 'ص', 'ﺽ' => 'ض', 'ﺾ' => 'ض', 'ﺿ' => 'ض', 'ﻀ' => 'ض', 'ﻁ' => 'ط', 'ﻂ' => 'ط', 'ﻃ' => 'ط', 'ﻄ' => 'ط', 'ﻅ' => 'ظ', 'ﻆ' => 'ظ', 'ﻇ' => 'ظ', 'ﻈ' => 'ظ', 'ﻉ' => 'ع', 'ﻊ' => 'ع', 'ﻋ' => 'ع', 'ﻌ' => 'ع', 'ﻍ' => 'غ', 'ﻎ' => 'غ', 'ﻏ' => 'غ', 'ﻐ' => 'غ', 'ﻑ' => 'ف', 'ﻒ' => 'ف', 'ﻓ' => 'ف', 'ﻔ' => 'ف', 'ﻕ' => 'ق', 'ﻖ' => 'ق', 'ﻗ' => 'ق', 'ﻘ' => 'ق', 'ﻙ' => 'ك', 'ﻚ' => 'ك', 'ﻛ' => 'ك', 'ﻜ' => 'ك', 'ﻝ' => 'ل', 'ﻞ' => 'ل', 'ﻟ' => 'ل', 'ﻠ' => 'ل', 'ﻡ' => 'م', 'ﻢ' => 'م', 'ﻣ' => 'م', 'ﻤ' => 'م', 'ﻥ' => 'ن', 'ﻦ' => 'ن', 'ﻧ' => 'ن', 'ﻨ' => 'ن', 'ﻩ' => 'ه', 'ﻪ' => 'ه', 'ﻫ' => 'ه', 'ﻬ' => 'ه', 'ﻭ' => 'و', 'ﻮ' => 'و', 'ﻯ' => 'ى', 'ﻰ' => 'ى', 'ﻱ' => 'ي', 'ﻲ' => 'ي', 'ﻳ' => 'ي', 'ﻴ' => 'ي', 'ﻵ' => 'لآ', 'ﻶ' => 'لآ', 'ﻷ' => 'لأ', 'ﻸ' => 'لأ', 'ﻹ' => 'لإ', 'ﻺ' => 'لإ', 'ﻻ' => 'لا', 'ﻼ' => 'لا', '!' => '!', '"' => '"', '#' => '#', '$' => '$', '%' => '%', '&' => '&', ''' => '\'', '(' => '(', ')' => ')', '*' => '*', '+' => '+', ',' => ',', '-' => '-', '.' => '.', '/' => '/', '0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', ':' => ':', ';' => ';', '<' => '<', '=' => '=', '>' => '>', '?' => '?', '@' => '@', 'A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D', 'E' => 'E', 'F' => 'F', 'G' => 'G', 'H' => 'H', 'I' => 'I', 'J' => 'J', 'K' => 'K', 'L' => 'L', 'M' => 'M', 'N' => 'N', 'O' => 'O', 'P' => 'P', 'Q' => 'Q', 'R' => 'R', 'S' => 'S', 'T' => 'T', 'U' => 'U', 'V' => 'V', 'W' => 'W', 'X' => 'X', 'Y' => 'Y', 'Z' => 'Z', '[' => '[', '\' => '\\', ']' => ']', '^' => '^', '_' => '_', '`' => '`', 'a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd', 'e' => 'e', 'f' => 'f', 'g' => 'g', 'h' => 'h', 'i' => 'i', 'j' => 'j', 'k' => 'k', 'l' => 'l', 'm' => 'm', 'n' => 'n', 'o' => 'o', 'p' => 'p', 'q' => 'q', 'r' => 'r', 's' => 's', 't' => 't', 'u' => 'u', 'v' => 'v', 'w' => 'w', 'x' => 'x', 'y' => 'y', 'z' => 'z', '{' => '{', '|' => '|', '}' => '}', '~' => '~', '⦅' => '⦅', '⦆' => '⦆', '。' => '。', '「' => '「', '」' => '」', '、' => '、', '・' => '・', 'ヲ' => 'ヲ', 'ァ' => 'ァ', 'ィ' => 'ィ', 'ゥ' => 'ゥ', 'ェ' => 'ェ', 'ォ' => 'ォ', 'ャ' => 'ャ', 'ュ' => 'ュ', 'ョ' => 'ョ', 'ッ' => 'ッ', 'ー' => 'ー', 'ア' => 'ア', 'イ' => 'イ', 'ウ' => 'ウ', 'エ' => 'エ', 'オ' => 'オ', 'カ' => 'カ', 'キ' => 'キ', 'ク' => 'ク', 'ケ' => 'ケ', 'コ' => 'コ', 'サ' => 'サ', 'シ' => 'シ', 'ス' => 'ス', 'セ' => 'セ', 'ソ' => 'ソ', 'タ' => 'タ', 'チ' => 'チ', 'ツ' => 'ツ', 'テ' => 'テ', 'ト' => 'ト', 'ナ' => 'ナ', 'ニ' => 'ニ', 'ヌ' => 'ヌ', 'ネ' => 'ネ', 'ノ' => 'ノ', 'ハ' => 'ハ', 'ヒ' => 'ヒ', 'フ' => 'フ', 'ヘ' => 'ヘ', 'ホ' => 'ホ', 'マ' => 'マ', 'ミ' => 'ミ', 'ム' => 'ム', 'メ' => 'メ', 'モ' => 'モ', 'ヤ' => 'ヤ', 'ユ' => 'ユ', 'ヨ' => 'ヨ', 'ラ' => 'ラ', 'リ' => 'リ', 'ル' => 'ル', 'レ' => 'レ', 'ロ' => 'ロ', 'ワ' => 'ワ', 'ン' => 'ン', '゙' => '゙', '゚' => '゚', 'ᅠ' => 'ᅠ', 'ᄀ' => 'ᄀ', 'ᄁ' => 'ᄁ', 'ᆪ' => 'ᆪ', 'ᄂ' => 'ᄂ', 'ᆬ' => 'ᆬ', 'ᆭ' => 'ᆭ', 'ᄃ' => 'ᄃ', 'ᄄ' => 'ᄄ', 'ᄅ' => 'ᄅ', 'ᆰ' => 'ᆰ', 'ᆱ' => 'ᆱ', 'ᆲ' => 'ᆲ', 'ᆳ' => 'ᆳ', 'ᆴ' => 'ᆴ', 'ᆵ' => 'ᆵ', 'ᄚ' => 'ᄚ', 'ᄆ' => 'ᄆ', 'ᄇ' => 'ᄇ', 'ᄈ' => 'ᄈ', 'ᄡ' => 'ᄡ', 'ᄉ' => 'ᄉ', 'ᄊ' => 'ᄊ', 'ᄋ' => 'ᄋ', 'ᄌ' => 'ᄌ', 'ᄍ' => 'ᄍ', 'ᄎ' => 'ᄎ', 'ᄏ' => 'ᄏ', 'ᄐ' => 'ᄐ', 'ᄑ' => 'ᄑ', 'ᄒ' => 'ᄒ', 'ᅡ' => 'ᅡ', 'ᅢ' => 'ᅢ', 'ᅣ' => 'ᅣ', 'ᅤ' => 'ᅤ', 'ᅥ' => 'ᅥ', 'ᅦ' => 'ᅦ', 'ᅧ' => 'ᅧ', 'ᅨ' => 'ᅨ', 'ᅩ' => 'ᅩ', 'ᅪ' => 'ᅪ', 'ᅫ' => 'ᅫ', 'ᅬ' => 'ᅬ', 'ᅭ' => 'ᅭ', 'ᅮ' => 'ᅮ', 'ᅯ' => 'ᅯ', 'ᅰ' => 'ᅰ', 'ᅱ' => 'ᅱ', 'ᅲ' => 'ᅲ', 'ᅳ' => 'ᅳ', 'ᅴ' => 'ᅴ', 'ᅵ' => 'ᅵ', '¢' => '¢', '£' => '£', '¬' => '¬', ' ̄' => ' ̄', '¦' => '¦', '¥' => '¥', '₩' => '₩', '│' => '│', '←' => '←', '↑' => '↑', '→' => '→', '↓' => '↓', '■' => '■', '○' => '○', '𝐀' => 'A', '𝐁' => 'B', '𝐂' => 'C', '𝐃' => 'D', '𝐄' => 'E', '𝐅' => 'F', '𝐆' => 'G', '𝐇' => 'H', '𝐈' => 'I', '𝐉' => 'J', '𝐊' => 'K', '𝐋' => 'L', '𝐌' => 'M', '𝐍' => 'N', '𝐎' => 'O', '𝐏' => 'P', '𝐐' => 'Q', '𝐑' => 'R', '𝐒' => 'S', '𝐓' => 'T', '𝐔' => 'U', '𝐕' => 'V', '𝐖' => 'W', '𝐗' => 'X', '𝐘' => 'Y', '𝐙' => 'Z', '𝐚' => 'a', '𝐛' => 'b', '𝐜' => 'c', '𝐝' => 'd', '𝐞' => 'e', '𝐟' => 'f', '𝐠' => 'g', '𝐡' => 'h', '𝐢' => 'i', '𝐣' => 'j', '𝐤' => 'k', '𝐥' => 'l', '𝐦' => 'm', '𝐧' => 'n', '𝐨' => 'o', '𝐩' => 'p', '𝐪' => 'q', '𝐫' => 'r', '𝐬' => 's', '𝐭' => 't', '𝐮' => 'u', '𝐯' => 'v', '𝐰' => 'w', '𝐱' => 'x', '𝐲' => 'y', '𝐳' => 'z', '𝐴' => 'A', '𝐵' => 'B', '𝐶' => 'C', '𝐷' => 'D', '𝐸' => 'E', '𝐹' => 'F', '𝐺' => 'G', '𝐻' => 'H', '𝐼' => 'I', '𝐽' => 'J', '𝐾' => 'K', '𝐿' => 'L', '𝑀' => 'M', '𝑁' => 'N', '𝑂' => 'O', '𝑃' => 'P', '𝑄' => 'Q', '𝑅' => 'R', '𝑆' => 'S', '𝑇' => 'T', '𝑈' => 'U', '𝑉' => 'V', '𝑊' => 'W', '𝑋' => 'X', '𝑌' => 'Y', '𝑍' => 'Z', '𝑎' => 'a', '𝑏' => 'b', '𝑐' => 'c', '𝑑' => 'd', '𝑒' => 'e', '𝑓' => 'f', '𝑔' => 'g', '𝑖' => 'i', '𝑗' => 'j', '𝑘' => 'k', '𝑙' => 'l', '𝑚' => 'm', '𝑛' => 'n', '𝑜' => 'o', '𝑝' => 'p', '𝑞' => 'q', '𝑟' => 'r', '𝑠' => 's', '𝑡' => 't', '𝑢' => 'u', '𝑣' => 'v', '𝑤' => 'w', '𝑥' => 'x', '𝑦' => 'y', '𝑧' => 'z', '𝑨' => 'A', '𝑩' => 'B', '𝑪' => 'C', '𝑫' => 'D', '𝑬' => 'E', '𝑭' => 'F', '𝑮' => 'G', '𝑯' => 'H', '𝑰' => 'I', '𝑱' => 'J', '𝑲' => 'K', '𝑳' => 'L', '𝑴' => 'M', '𝑵' => 'N', '𝑶' => 'O', '𝑷' => 'P', '𝑸' => 'Q', '𝑹' => 'R', '𝑺' => 'S', '𝑻' => 'T', '𝑼' => 'U', '𝑽' => 'V', '𝑾' => 'W', '𝑿' => 'X', '𝒀' => 'Y', '𝒁' => 'Z', '𝒂' => 'a', '𝒃' => 'b', '𝒄' => 'c', '𝒅' => 'd', '𝒆' => 'e', '𝒇' => 'f', '𝒈' => 'g', '𝒉' => 'h', '𝒊' => 'i', '𝒋' => 'j', '𝒌' => 'k', '𝒍' => 'l', '𝒎' => 'm', '𝒏' => 'n', '𝒐' => 'o', '𝒑' => 'p', '𝒒' => 'q', '𝒓' => 'r', '𝒔' => 's', '𝒕' => 't', '𝒖' => 'u', '𝒗' => 'v', '𝒘' => 'w', '𝒙' => 'x', '𝒚' => 'y', '𝒛' => 'z', '𝒜' => 'A', '𝒞' => 'C', '𝒟' => 'D', '𝒢' => 'G', '𝒥' => 'J', '𝒦' => 'K', '𝒩' => 'N', '𝒪' => 'O', '𝒫' => 'P', '𝒬' => 'Q', '𝒮' => 'S', '𝒯' => 'T', '𝒰' => 'U', '𝒱' => 'V', '𝒲' => 'W', '𝒳' => 'X', '𝒴' => 'Y', '𝒵' => 'Z', '𝒶' => 'a', '𝒷' => 'b', '𝒸' => 'c', '𝒹' => 'd', '𝒻' => 'f', '𝒽' => 'h', '𝒾' => 'i', '𝒿' => 'j', '𝓀' => 'k', '𝓁' => 'l', '𝓂' => 'm', '𝓃' => 'n', '𝓅' => 'p', '𝓆' => 'q', '𝓇' => 'r', '𝓈' => 's', '𝓉' => 't', '𝓊' => 'u', '𝓋' => 'v', '𝓌' => 'w', '𝓍' => 'x', '𝓎' => 'y', '𝓏' => 'z', '𝓐' => 'A', '𝓑' => 'B', '𝓒' => 'C', '𝓓' => 'D', '𝓔' => 'E', '𝓕' => 'F', '𝓖' => 'G', '𝓗' => 'H', '𝓘' => 'I', '𝓙' => 'J', '𝓚' => 'K', '𝓛' => 'L', '𝓜' => 'M', '𝓝' => 'N', '𝓞' => 'O', '𝓟' => 'P', '𝓠' => 'Q', '𝓡' => 'R', '𝓢' => 'S', '𝓣' => 'T', '𝓤' => 'U', '𝓥' => 'V', '𝓦' => 'W', '𝓧' => 'X', '𝓨' => 'Y', '𝓩' => 'Z', '𝓪' => 'a', '𝓫' => 'b', '𝓬' => 'c', '𝓭' => 'd', '𝓮' => 'e', '𝓯' => 'f', '𝓰' => 'g', '𝓱' => 'h', '𝓲' => 'i', '𝓳' => 'j', '𝓴' => 'k', '𝓵' => 'l', '𝓶' => 'm', '𝓷' => 'n', '𝓸' => 'o', '𝓹' => 'p', '𝓺' => 'q', '𝓻' => 'r', '𝓼' => 's', '𝓽' => 't', '𝓾' => 'u', '𝓿' => 'v', '𝔀' => 'w', '𝔁' => 'x', '𝔂' => 'y', '𝔃' => 'z', '𝔄' => 'A', '𝔅' => 'B', '𝔇' => 'D', '𝔈' => 'E', '𝔉' => 'F', '𝔊' => 'G', '𝔍' => 'J', '𝔎' => 'K', '𝔏' => 'L', '𝔐' => 'M', '𝔑' => 'N', '𝔒' => 'O', '𝔓' => 'P', '𝔔' => 'Q', '𝔖' => 'S', '𝔗' => 'T', '𝔘' => 'U', '𝔙' => 'V', '𝔚' => 'W', '𝔛' => 'X', '𝔜' => 'Y', '𝔞' => 'a', '𝔟' => 'b', '𝔠' => 'c', '𝔡' => 'd', '𝔢' => 'e', '𝔣' => 'f', '𝔤' => 'g', '𝔥' => 'h', '𝔦' => 'i', '𝔧' => 'j', '𝔨' => 'k', '𝔩' => 'l', '𝔪' => 'm', '𝔫' => 'n', '𝔬' => 'o', '𝔭' => 'p', '𝔮' => 'q', '𝔯' => 'r', '𝔰' => 's', '𝔱' => 't', '𝔲' => 'u', '𝔳' => 'v', '𝔴' => 'w', '𝔵' => 'x', '𝔶' => 'y', '𝔷' => 'z', '𝔸' => 'A', '𝔹' => 'B', '𝔻' => 'D', '𝔼' => 'E', '𝔽' => 'F', '𝔾' => 'G', '𝕀' => 'I', '𝕁' => 'J', '𝕂' => 'K', '𝕃' => 'L', '𝕄' => 'M', '𝕆' => 'O', '𝕊' => 'S', '𝕋' => 'T', '𝕌' => 'U', '𝕍' => 'V', '𝕎' => 'W', '𝕏' => 'X', '𝕐' => 'Y', '𝕒' => 'a', '𝕓' => 'b', '𝕔' => 'c', '𝕕' => 'd', '𝕖' => 'e', '𝕗' => 'f', '𝕘' => 'g', '𝕙' => 'h', '𝕚' => 'i', '𝕛' => 'j', '𝕜' => 'k', '𝕝' => 'l', '𝕞' => 'm', '𝕟' => 'n', '𝕠' => 'o', '𝕡' => 'p', '𝕢' => 'q', '𝕣' => 'r', '𝕤' => 's', '𝕥' => 't', '𝕦' => 'u', '𝕧' => 'v', '𝕨' => 'w', '𝕩' => 'x', '𝕪' => 'y', '𝕫' => 'z', '𝕬' => 'A', '𝕭' => 'B', '𝕮' => 'C', '𝕯' => 'D', '𝕰' => 'E', '𝕱' => 'F', '𝕲' => 'G', '𝕳' => 'H', '𝕴' => 'I', '𝕵' => 'J', '𝕶' => 'K', '𝕷' => 'L', '𝕸' => 'M', '𝕹' => 'N', '𝕺' => 'O', '𝕻' => 'P', '𝕼' => 'Q', '𝕽' => 'R', '𝕾' => 'S', '𝕿' => 'T', '𝖀' => 'U', '𝖁' => 'V', '𝖂' => 'W', '𝖃' => 'X', '𝖄' => 'Y', '𝖅' => 'Z', '𝖆' => 'a', '𝖇' => 'b', '𝖈' => 'c', '𝖉' => 'd', '𝖊' => 'e', '𝖋' => 'f', '𝖌' => 'g', '𝖍' => 'h', '𝖎' => 'i', '𝖏' => 'j', '𝖐' => 'k', '𝖑' => 'l', '𝖒' => 'm', '𝖓' => 'n', '𝖔' => 'o', '𝖕' => 'p', '𝖖' => 'q', '𝖗' => 'r', '𝖘' => 's', '𝖙' => 't', '𝖚' => 'u', '𝖛' => 'v', '𝖜' => 'w', '𝖝' => 'x', '𝖞' => 'y', '𝖟' => 'z', '𝖠' => 'A', '𝖡' => 'B', '𝖢' => 'C', '𝖣' => 'D', '𝖤' => 'E', '𝖥' => 'F', '𝖦' => 'G', '𝖧' => 'H', '𝖨' => 'I', '𝖩' => 'J', '𝖪' => 'K', '𝖫' => 'L', '𝖬' => 'M', '𝖭' => 'N', '𝖮' => 'O', '𝖯' => 'P', '𝖰' => 'Q', '𝖱' => 'R', '𝖲' => 'S', '𝖳' => 'T', '𝖴' => 'U', '𝖵' => 'V', '𝖶' => 'W', '𝖷' => 'X', '𝖸' => 'Y', '𝖹' => 'Z', '𝖺' => 'a', '𝖻' => 'b', '𝖼' => 'c', '𝖽' => 'd', '𝖾' => 'e', '𝖿' => 'f', '𝗀' => 'g', '𝗁' => 'h', '𝗂' => 'i', '𝗃' => 'j', '𝗄' => 'k', '𝗅' => 'l', '𝗆' => 'm', '𝗇' => 'n', '𝗈' => 'o', '𝗉' => 'p', '𝗊' => 'q', '𝗋' => 'r', '𝗌' => 's', '𝗍' => 't', '𝗎' => 'u', '𝗏' => 'v', '𝗐' => 'w', '𝗑' => 'x', '𝗒' => 'y', '𝗓' => 'z', '𝗔' => 'A', '𝗕' => 'B', '𝗖' => 'C', '𝗗' => 'D', '𝗘' => 'E', '𝗙' => 'F', '𝗚' => 'G', '𝗛' => 'H', '𝗜' => 'I', '𝗝' => 'J', '𝗞' => 'K', '𝗟' => 'L', '𝗠' => 'M', '𝗡' => 'N', '𝗢' => 'O', '𝗣' => 'P', '𝗤' => 'Q', '𝗥' => 'R', '𝗦' => 'S', '𝗧' => 'T', '𝗨' => 'U', '𝗩' => 'V', '𝗪' => 'W', '𝗫' => 'X', '𝗬' => 'Y', '𝗭' => 'Z', '𝗮' => 'a', '𝗯' => 'b', '𝗰' => 'c', '𝗱' => 'd', '𝗲' => 'e', '𝗳' => 'f', '𝗴' => 'g', '𝗵' => 'h', '𝗶' => 'i', '𝗷' => 'j', '𝗸' => 'k', '𝗹' => 'l', '𝗺' => 'm', '𝗻' => 'n', '𝗼' => 'o', '𝗽' => 'p', '𝗾' => 'q', '𝗿' => 'r', '𝘀' => 's', '𝘁' => 't', '𝘂' => 'u', '𝘃' => 'v', '𝘄' => 'w', '𝘅' => 'x', '𝘆' => 'y', '𝘇' => 'z', '𝘈' => 'A', '𝘉' => 'B', '𝘊' => 'C', '𝘋' => 'D', '𝘌' => 'E', '𝘍' => 'F', '𝘎' => 'G', '𝘏' => 'H', '𝘐' => 'I', '𝘑' => 'J', '𝘒' => 'K', '𝘓' => 'L', '𝘔' => 'M', '𝘕' => 'N', '𝘖' => 'O', '𝘗' => 'P', '𝘘' => 'Q', '𝘙' => 'R', '𝘚' => 'S', '𝘛' => 'T', '𝘜' => 'U', '𝘝' => 'V', '𝘞' => 'W', '𝘟' => 'X', '𝘠' => 'Y', '𝘡' => 'Z', '𝘢' => 'a', '𝘣' => 'b', '𝘤' => 'c', '𝘥' => 'd', '𝘦' => 'e', '𝘧' => 'f', '𝘨' => 'g', '𝘩' => 'h', '𝘪' => 'i', '𝘫' => 'j', '𝘬' => 'k', '𝘭' => 'l', '𝘮' => 'm', '𝘯' => 'n', '𝘰' => 'o', '𝘱' => 'p', '𝘲' => 'q', '𝘳' => 'r', '𝘴' => 's', '𝘵' => 't', '𝘶' => 'u', '𝘷' => 'v', '𝘸' => 'w', '𝘹' => 'x', '𝘺' => 'y', '𝘻' => 'z', '𝘼' => 'A', '𝘽' => 'B', '𝘾' => 'C', '𝘿' => 'D', '𝙀' => 'E', '𝙁' => 'F', '𝙂' => 'G', '𝙃' => 'H', '𝙄' => 'I', '𝙅' => 'J', '𝙆' => 'K', '𝙇' => 'L', '𝙈' => 'M', '𝙉' => 'N', '𝙊' => 'O', '𝙋' => 'P', '𝙌' => 'Q', '𝙍' => 'R', '𝙎' => 'S', '𝙏' => 'T', '𝙐' => 'U', '𝙑' => 'V', '𝙒' => 'W', '𝙓' => 'X', '𝙔' => 'Y', '𝙕' => 'Z', '𝙖' => 'a', '𝙗' => 'b', '𝙘' => 'c', '𝙙' => 'd', '𝙚' => 'e', '𝙛' => 'f', '𝙜' => 'g', '𝙝' => 'h', '𝙞' => 'i', '𝙟' => 'j', '𝙠' => 'k', '𝙡' => 'l', '𝙢' => 'm', '𝙣' => 'n', '𝙤' => 'o', '𝙥' => 'p', '𝙦' => 'q', '𝙧' => 'r', '𝙨' => 's', '𝙩' => 't', '𝙪' => 'u', '𝙫' => 'v', '𝙬' => 'w', '𝙭' => 'x', '𝙮' => 'y', '𝙯' => 'z', '𝙰' => 'A', '𝙱' => 'B', '𝙲' => 'C', '𝙳' => 'D', '𝙴' => 'E', '𝙵' => 'F', '𝙶' => 'G', '𝙷' => 'H', '𝙸' => 'I', '𝙹' => 'J', '𝙺' => 'K', '𝙻' => 'L', '𝙼' => 'M', '𝙽' => 'N', '𝙾' => 'O', '𝙿' => 'P', '𝚀' => 'Q', '𝚁' => 'R', '𝚂' => 'S', '𝚃' => 'T', '𝚄' => 'U', '𝚅' => 'V', '𝚆' => 'W', '𝚇' => 'X', '𝚈' => 'Y', '𝚉' => 'Z', '𝚊' => 'a', '𝚋' => 'b', '𝚌' => 'c', '𝚍' => 'd', '𝚎' => 'e', '𝚏' => 'f', '𝚐' => 'g', '𝚑' => 'h', '𝚒' => 'i', '𝚓' => 'j', '𝚔' => 'k', '𝚕' => 'l', '𝚖' => 'm', '𝚗' => 'n', '𝚘' => 'o', '𝚙' => 'p', '𝚚' => 'q', '𝚛' => 'r', '𝚜' => 's', '𝚝' => 't', '𝚞' => 'u', '𝚟' => 'v', '𝚠' => 'w', '𝚡' => 'x', '𝚢' => 'y', '𝚣' => 'z', '𝚤' => 'ı', '𝚥' => 'ȷ', '𝚨' => 'Α', '𝚩' => 'Β', '𝚪' => 'Γ', '𝚫' => 'Δ', '𝚬' => 'Ε', '𝚭' => 'Ζ', '𝚮' => 'Η', '𝚯' => 'Θ', '𝚰' => 'Ι', '𝚱' => 'Κ', '𝚲' => 'Λ', '𝚳' => 'Μ', '𝚴' => 'Ν', '𝚵' => 'Ξ', '𝚶' => 'Ο', '𝚷' => 'Π', '𝚸' => 'Ρ', '𝚹' => 'Θ', '𝚺' => 'Σ', '𝚻' => 'Τ', '𝚼' => 'Υ', '𝚽' => 'Φ', '𝚾' => 'Χ', '𝚿' => 'Ψ', '𝛀' => 'Ω', '𝛁' => '∇', '𝛂' => 'α', '𝛃' => 'β', '𝛄' => 'γ', '𝛅' => 'δ', '𝛆' => 'ε', '𝛇' => 'ζ', '𝛈' => 'η', '𝛉' => 'θ', '𝛊' => 'ι', '𝛋' => 'κ', '𝛌' => 'λ', '𝛍' => 'μ', '𝛎' => 'ν', '𝛏' => 'ξ', '𝛐' => 'ο', '𝛑' => 'π', '𝛒' => 'ρ', '𝛓' => 'ς', '𝛔' => 'σ', '𝛕' => 'τ', '𝛖' => 'υ', '𝛗' => 'φ', '𝛘' => 'χ', '𝛙' => 'ψ', '𝛚' => 'ω', '𝛛' => '∂', '𝛜' => 'ε', '𝛝' => 'θ', '𝛞' => 'κ', '𝛟' => 'φ', '𝛠' => 'ρ', '𝛡' => 'π', '𝛢' => 'Α', '𝛣' => 'Β', '𝛤' => 'Γ', '𝛥' => 'Δ', '𝛦' => 'Ε', '𝛧' => 'Ζ', '𝛨' => 'Η', '𝛩' => 'Θ', '𝛪' => 'Ι', '𝛫' => 'Κ', '𝛬' => 'Λ', '𝛭' => 'Μ', '𝛮' => 'Ν', '𝛯' => 'Ξ', '𝛰' => 'Ο', '𝛱' => 'Π', '𝛲' => 'Ρ', '𝛳' => 'Θ', '𝛴' => 'Σ', '𝛵' => 'Τ', '𝛶' => 'Υ', '𝛷' => 'Φ', '𝛸' => 'Χ', '𝛹' => 'Ψ', '𝛺' => 'Ω', '𝛻' => '∇', '𝛼' => 'α', '𝛽' => 'β', '𝛾' => 'γ', '𝛿' => 'δ', '𝜀' => 'ε', '𝜁' => 'ζ', '𝜂' => 'η', '𝜃' => 'θ', '𝜄' => 'ι', '𝜅' => 'κ', '𝜆' => 'λ', '𝜇' => 'μ', '𝜈' => 'ν', '𝜉' => 'ξ', '𝜊' => 'ο', '𝜋' => 'π', '𝜌' => 'ρ', '𝜍' => 'ς', '𝜎' => 'σ', '𝜏' => 'τ', '𝜐' => 'υ', '𝜑' => 'φ', '𝜒' => 'χ', '𝜓' => 'ψ', '𝜔' => 'ω', '𝜕' => '∂', '𝜖' => 'ε', '𝜗' => 'θ', '𝜘' => 'κ', '𝜙' => 'φ', '𝜚' => 'ρ', '𝜛' => 'π', '𝜜' => 'Α', '𝜝' => 'Β', '𝜞' => 'Γ', '𝜟' => 'Δ', '𝜠' => 'Ε', '𝜡' => 'Ζ', '𝜢' => 'Η', '𝜣' => 'Θ', '𝜤' => 'Ι', '𝜥' => 'Κ', '𝜦' => 'Λ', '𝜧' => 'Μ', '𝜨' => 'Ν', '𝜩' => 'Ξ', '𝜪' => 'Ο', '𝜫' => 'Π', '𝜬' => 'Ρ', '𝜭' => 'Θ', '𝜮' => 'Σ', '𝜯' => 'Τ', '𝜰' => 'Υ', '𝜱' => 'Φ', '𝜲' => 'Χ', '𝜳' => 'Ψ', '𝜴' => 'Ω', '𝜵' => '∇', '𝜶' => 'α', '𝜷' => 'β', '𝜸' => 'γ', '𝜹' => 'δ', '𝜺' => 'ε', '𝜻' => 'ζ', '𝜼' => 'η', '𝜽' => 'θ', '𝜾' => 'ι', '𝜿' => 'κ', '𝝀' => 'λ', '𝝁' => 'μ', '𝝂' => 'ν', '𝝃' => 'ξ', '𝝄' => 'ο', '𝝅' => 'π', '𝝆' => 'ρ', '𝝇' => 'ς', '𝝈' => 'σ', '𝝉' => 'τ', '𝝊' => 'υ', '𝝋' => 'φ', '𝝌' => 'χ', '𝝍' => 'ψ', '𝝎' => 'ω', '𝝏' => '∂', '𝝐' => 'ε', '𝝑' => 'θ', '𝝒' => 'κ', '𝝓' => 'φ', '𝝔' => 'ρ', '𝝕' => 'π', '𝝖' => 'Α', '𝝗' => 'Β', '𝝘' => 'Γ', '𝝙' => 'Δ', '𝝚' => 'Ε', '𝝛' => 'Ζ', '𝝜' => 'Η', '𝝝' => 'Θ', '𝝞' => 'Ι', '𝝟' => 'Κ', '𝝠' => 'Λ', '𝝡' => 'Μ', '𝝢' => 'Ν', '𝝣' => 'Ξ', '𝝤' => 'Ο', '𝝥' => 'Π', '𝝦' => 'Ρ', '𝝧' => 'Θ', '𝝨' => 'Σ', '𝝩' => 'Τ', '𝝪' => 'Υ', '𝝫' => 'Φ', '𝝬' => 'Χ', '𝝭' => 'Ψ', '𝝮' => 'Ω', '𝝯' => '∇', '𝝰' => 'α', '𝝱' => 'β', '𝝲' => 'γ', '𝝳' => 'δ', '𝝴' => 'ε', '𝝵' => 'ζ', '𝝶' => 'η', '𝝷' => 'θ', '𝝸' => 'ι', '𝝹' => 'κ', '𝝺' => 'λ', '𝝻' => 'μ', '𝝼' => 'ν', '𝝽' => 'ξ', '𝝾' => 'ο', '𝝿' => 'π', '𝞀' => 'ρ', '𝞁' => 'ς', '𝞂' => 'σ', '𝞃' => 'τ', '𝞄' => 'υ', '𝞅' => 'φ', '𝞆' => 'χ', '𝞇' => 'ψ', '𝞈' => 'ω', '𝞉' => '∂', '𝞊' => 'ε', '𝞋' => 'θ', '𝞌' => 'κ', '𝞍' => 'φ', '𝞎' => 'ρ', '𝞏' => 'π', '𝞐' => 'Α', '𝞑' => 'Β', '𝞒' => 'Γ', '𝞓' => 'Δ', '𝞔' => 'Ε', '𝞕' => 'Ζ', '𝞖' => 'Η', '𝞗' => 'Θ', '𝞘' => 'Ι', '𝞙' => 'Κ', '𝞚' => 'Λ', '𝞛' => 'Μ', '𝞜' => 'Ν', '𝞝' => 'Ξ', '𝞞' => 'Ο', '𝞟' => 'Π', '𝞠' => 'Ρ', '𝞡' => 'Θ', '𝞢' => 'Σ', '𝞣' => 'Τ', '𝞤' => 'Υ', '𝞥' => 'Φ', '𝞦' => 'Χ', '𝞧' => 'Ψ', '𝞨' => 'Ω', '𝞩' => '∇', '𝞪' => 'α', '𝞫' => 'β', '𝞬' => 'γ', '𝞭' => 'δ', '𝞮' => 'ε', '𝞯' => 'ζ', '𝞰' => 'η', '𝞱' => 'θ', '𝞲' => 'ι', '𝞳' => 'κ', '𝞴' => 'λ', '𝞵' => 'μ', '𝞶' => 'ν', '𝞷' => 'ξ', '𝞸' => 'ο', '𝞹' => 'π', '𝞺' => 'ρ', '𝞻' => 'ς', '𝞼' => 'σ', '𝞽' => 'τ', '𝞾' => 'υ', '𝞿' => 'φ', '𝟀' => 'χ', '𝟁' => 'ψ', '𝟂' => 'ω', '𝟃' => '∂', '𝟄' => 'ε', '𝟅' => 'θ', '𝟆' => 'κ', '𝟇' => 'φ', '𝟈' => 'ρ', '𝟉' => 'π', '𝟊' => 'Ϝ', '𝟋' => 'ϝ', '𝟎' => '0', '𝟏' => '1', '𝟐' => '2', '𝟑' => '3', '𝟒' => '4', '𝟓' => '5', '𝟔' => '6', '𝟕' => '7', '𝟖' => '8', '𝟗' => '9', '𝟘' => '0', '𝟙' => '1', '𝟚' => '2', '𝟛' => '3', '𝟜' => '4', '𝟝' => '5', '𝟞' => '6', '𝟟' => '7', '𝟠' => '8', '𝟡' => '9', '𝟢' => '0', '𝟣' => '1', '𝟤' => '2', '𝟥' => '3', '𝟦' => '4', '𝟧' => '5', '𝟨' => '6', '𝟩' => '7', '𝟪' => '8', '𝟫' => '9', '𝟬' => '0', '𝟭' => '1', '𝟮' => '2', '𝟯' => '3', '𝟰' => '4', '𝟱' => '5', '𝟲' => '6', '𝟳' => '7', '𝟴' => '8', '𝟵' => '9', '𝟶' => '0', '𝟷' => '1', '𝟸' => '2', '𝟹' => '3', '𝟺' => '4', '𝟻' => '5', '𝟼' => '6', '𝟽' => '7', '𝟾' => '8', '𝟿' => '9', '𞸀' => 'ا', '𞸁' => 'ب', '𞸂' => 'ج', '𞸃' => 'د', '𞸅' => 'و', '𞸆' => 'ز', '𞸇' => 'ح', '𞸈' => 'ط', '𞸉' => 'ي', '𞸊' => 'ك', '𞸋' => 'ل', '𞸌' => 'م', '𞸍' => 'ن', '𞸎' => 'س', '𞸏' => 'ع', '𞸐' => 'ف', '𞸑' => 'ص', '𞸒' => 'ق', '𞸓' => 'ر', '𞸔' => 'ش', '𞸕' => 'ت', '𞸖' => 'ث', '𞸗' => 'خ', '𞸘' => 'ذ', '𞸙' => 'ض', '𞸚' => 'ظ', '𞸛' => 'غ', '𞸜' => 'ٮ', '𞸝' => 'ں', '𞸞' => 'ڡ', '𞸟' => 'ٯ', '𞸡' => 'ب', '𞸢' => 'ج', '𞸤' => 'ه', '𞸧' => 'ح', '𞸩' => 'ي', '𞸪' => 'ك', '𞸫' => 'ل', '𞸬' => 'م', '𞸭' => 'ن', '𞸮' => 'س', '𞸯' => 'ع', '𞸰' => 'ف', '𞸱' => 'ص', '𞸲' => 'ق', '𞸴' => 'ش', '𞸵' => 'ت', '𞸶' => 'ث', '𞸷' => 'خ', '𞸹' => 'ض', '𞸻' => 'غ', '𞹂' => 'ج', '𞹇' => 'ح', '𞹉' => 'ي', '𞹋' => 'ل', '𞹍' => 'ن', '𞹎' => 'س', '𞹏' => 'ع', '𞹑' => 'ص', '𞹒' => 'ق', '𞹔' => 'ش', '𞹗' => 'خ', '𞹙' => 'ض', '𞹛' => 'غ', '𞹝' => 'ں', '𞹟' => 'ٯ', '𞹡' => 'ب', '𞹢' => 'ج', '𞹤' => 'ه', '𞹧' => 'ح', '𞹨' => 'ط', '𞹩' => 'ي', '𞹪' => 'ك', '𞹬' => 'م', '𞹭' => 'ن', '𞹮' => 'س', '𞹯' => 'ع', '𞹰' => 'ف', '𞹱' => 'ص', '𞹲' => 'ق', '𞹴' => 'ش', '𞹵' => 'ت', '𞹶' => 'ث', '𞹷' => 'خ', '𞹹' => 'ض', '𞹺' => 'ظ', '𞹻' => 'غ', '𞹼' => 'ٮ', '𞹾' => 'ڡ', '𞺀' => 'ا', '𞺁' => 'ب', '𞺂' => 'ج', '𞺃' => 'د', '𞺄' => 'ه', '𞺅' => 'و', '𞺆' => 'ز', '𞺇' => 'ح', '𞺈' => 'ط', '𞺉' => 'ي', '𞺋' => 'ل', '𞺌' => 'م', '𞺍' => 'ن', '𞺎' => 'س', '𞺏' => 'ع', '𞺐' => 'ف', '𞺑' => 'ص', '𞺒' => 'ق', '𞺓' => 'ر', '𞺔' => 'ش', '𞺕' => 'ت', '𞺖' => 'ث', '𞺗' => 'خ', '𞺘' => 'ذ', '𞺙' => 'ض', '𞺚' => 'ظ', '𞺛' => 'غ', '𞺡' => 'ب', '𞺢' => 'ج', '𞺣' => 'د', '𞺥' => 'و', '𞺦' => 'ز', '𞺧' => 'ح', '𞺨' => 'ط', '𞺩' => 'ي', '𞺫' => 'ل', '𞺬' => 'م', '𞺭' => 'ن', '𞺮' => 'س', '𞺯' => 'ع', '𞺰' => 'ف', '𞺱' => 'ص', '𞺲' => 'ق', '𞺳' => 'ر', '𞺴' => 'ش', '𞺵' => 'ت', '𞺶' => 'ث', '𞺷' => 'خ', '𞺸' => 'ذ', '𞺹' => 'ض', '𞺺' => 'ظ', '𞺻' => 'غ', '🄀' => '0.', '🄁' => '0,', '🄂' => '1,', '🄃' => '2,', '🄄' => '3,', '🄅' => '4,', '🄆' => '5,', '🄇' => '6,', '🄈' => '7,', '🄉' => '8,', '🄊' => '9,', '🄐' => '(A)', '🄑' => '(B)', '🄒' => '(C)', '🄓' => '(D)', '🄔' => '(E)', '🄕' => '(F)', '🄖' => '(G)', '🄗' => '(H)', '🄘' => '(I)', '🄙' => '(J)', '🄚' => '(K)', '🄛' => '(L)', '🄜' => '(M)', '🄝' => '(N)', '🄞' => '(O)', '🄟' => '(P)', '🄠' => '(Q)', '🄡' => '(R)', '🄢' => '(S)', '🄣' => '(T)', '🄤' => '(U)', '🄥' => '(V)', '🄦' => '(W)', '🄧' => '(X)', '🄨' => '(Y)', '🄩' => '(Z)', '🄪' => '〔S〕', '🄫' => 'C', '🄬' => 'R', '🄭' => 'CD', '🄮' => 'WZ', '🄰' => 'A', '🄱' => 'B', '🄲' => 'C', '🄳' => 'D', '🄴' => 'E', '🄵' => 'F', '🄶' => 'G', '🄷' => 'H', '🄸' => 'I', '🄹' => 'J', '🄺' => 'K', '🄻' => 'L', '🄼' => 'M', '🄽' => 'N', '🄾' => 'O', '🄿' => 'P', '🅀' => 'Q', '🅁' => 'R', '🅂' => 'S', '🅃' => 'T', '🅄' => 'U', '🅅' => 'V', '🅆' => 'W', '🅇' => 'X', '🅈' => 'Y', '🅉' => 'Z', '🅊' => 'HV', '🅋' => 'MV', '🅌' => 'SD', '🅍' => 'SS', '🅎' => 'PPV', '🅏' => 'WC', '🅪' => 'MC', '🅫' => 'MD', '🅬' => 'MR', '🆐' => 'DJ', '🈀' => 'ほか', '🈁' => 'ココ', '🈂' => 'サ', '🈐' => '手', '🈑' => '字', '🈒' => '双', '🈓' => 'デ', '🈔' => '二', '🈕' => '多', '🈖' => '解', '🈗' => '天', '🈘' => '交', '🈙' => '映', '🈚' => '無', '🈛' => '料', '🈜' => '前', '🈝' => '後', '🈞' => '再', '🈟' => '新', '🈠' => '初', '🈡' => '終', '🈢' => '生', '🈣' => '販', '🈤' => '声', '🈥' => '吹', '🈦' => '演', '🈧' => '投', '🈨' => '捕', '🈩' => '一', '🈪' => '三', '🈫' => '遊', '🈬' => '左', '🈭' => '中', '🈮' => '右', '🈯' => '指', '🈰' => '走', '🈱' => '打', '🈲' => '禁', '🈳' => '空', '🈴' => '合', '🈵' => '満', '🈶' => '有', '🈷' => '月', '🈸' => '申', '🈹' => '割', '🈺' => '営', '🈻' => '配', '🉀' => '〔本〕', '🉁' => '〔三〕', '🉂' => '〔二〕', '🉃' => '〔安〕', '🉄' => '〔点〕', '🉅' => '〔打〕', '🉆' => '〔盗〕', '🉇' => '〔勝〕', '🉈' => '〔敗〕', '🉐' => '得', '🉑' => '可', '🯰' => '0', '🯱' => '1', '🯲' => '2', '🯳' => '3', '🯴' => '4', '🯵' => '5', '🯶' => '6', '🯷' => '7', '🯸' => '8', '🯹' => '9', ); vendor-patched/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php 0000777 00000044033 15251156632 0033512 0 ustar 00 php-toolkit/DataLiberation <?php return array( 'À' => 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '΅' => '΅', 'Ά' => 'Ά', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ὲ' => 'ὲ', 'ὴ' => 'ὴ', 'ὶ' => 'ὶ', 'ὸ' => 'ὸ', 'ὺ' => 'ὺ', 'ὼ' => 'ὼ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'ᾼ' => 'ᾼ', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Ὴ' => 'Ὴ', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ῥ' => 'Ῥ', '῭' => '῭', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ὼ' => 'Ὼ', 'ῼ' => 'ῼ', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸', ); DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php 0000777 00000032501 15251156632 0032267 0 ustar 00 php-toolkit <?php return array( '̀' => 230, '́' => 230, '̂' => 230, '̃' => 230, '̄' => 230, '̅' => 230, '̆' => 230, '̇' => 230, '̈' => 230, '̉' => 230, '̊' => 230, '̋' => 230, '̌' => 230, '̍' => 230, '̎' => 230, '̏' => 230, '̐' => 230, '̑' => 230, '̒' => 230, '̓' => 230, '̔' => 230, '̕' => 232, '̖' => 220, '̗' => 220, '̘' => 220, '̙' => 220, '̚' => 232, '̛' => 216, '̜' => 220, '̝' => 220, '̞' => 220, '̟' => 220, '̠' => 220, '̡' => 202, '̢' => 202, '̣' => 220, '̤' => 220, '̥' => 220, '̦' => 220, '̧' => 202, '̨' => 202, '̩' => 220, '̪' => 220, '̫' => 220, '̬' => 220, '̭' => 220, '̮' => 220, '̯' => 220, '̰' => 220, '̱' => 220, '̲' => 220, '̳' => 220, '̴' => 1, '̵' => 1, '̶' => 1, '̷' => 1, '̸' => 1, '̹' => 220, '̺' => 220, '̻' => 220, '̼' => 220, '̽' => 230, '̾' => 230, '̿' => 230, '̀' => 230, '́' => 230, '͂' => 230, '̓' => 230, '̈́' => 230, 'ͅ' => 240, '͆' => 230, '͇' => 220, '͈' => 220, '͉' => 220, '͊' => 230, '͋' => 230, '͌' => 230, '͍' => 220, '͎' => 220, '͐' => 230, '͑' => 230, '͒' => 230, '͓' => 220, '͔' => 220, '͕' => 220, '͖' => 220, '͗' => 230, '͘' => 232, '͙' => 220, '͚' => 220, '͛' => 230, '͜' => 233, '͝' => 234, '͞' => 234, '͟' => 233, '͠' => 234, '͡' => 234, '͢' => 233, 'ͣ' => 230, 'ͤ' => 230, 'ͥ' => 230, 'ͦ' => 230, 'ͧ' => 230, 'ͨ' => 230, 'ͩ' => 230, 'ͪ' => 230, 'ͫ' => 230, 'ͬ' => 230, 'ͭ' => 230, 'ͮ' => 230, 'ͯ' => 230, '҃' => 230, '҄' => 230, '҅' => 230, '҆' => 230, '҇' => 230, '֑' => 220, '֒' => 230, '֓' => 230, '֔' => 230, '֕' => 230, '֖' => 220, '֗' => 230, '֘' => 230, '֙' => 230, '֚' => 222, '֛' => 220, '֜' => 230, '֝' => 230, '֞' => 230, '֟' => 230, '֠' => 230, '֡' => 230, '֢' => 220, '֣' => 220, '֤' => 220, '֥' => 220, '֦' => 220, '֧' => 220, '֨' => 230, '֩' => 230, '֪' => 220, '֫' => 230, '֬' => 230, '֭' => 222, '֮' => 228, '֯' => 230, 'ְ' => 10, 'ֱ' => 11, 'ֲ' => 12, 'ֳ' => 13, 'ִ' => 14, 'ֵ' => 15, 'ֶ' => 16, 'ַ' => 17, 'ָ' => 18, 'ֹ' => 19, 'ֺ' => 19, 'ֻ' => 20, 'ּ' => 21, 'ֽ' => 22, 'ֿ' => 23, 'ׁ' => 24, 'ׂ' => 25, 'ׄ' => 230, 'ׅ' => 220, 'ׇ' => 18, 'ؐ' => 230, 'ؑ' => 230, 'ؒ' => 230, 'ؓ' => 230, 'ؔ' => 230, 'ؕ' => 230, 'ؖ' => 230, 'ؗ' => 230, 'ؘ' => 30, 'ؙ' => 31, 'ؚ' => 32, 'ً' => 27, 'ٌ' => 28, 'ٍ' => 29, 'َ' => 30, 'ُ' => 31, 'ِ' => 32, 'ّ' => 33, 'ْ' => 34, 'ٓ' => 230, 'ٔ' => 230, 'ٕ' => 220, 'ٖ' => 220, 'ٗ' => 230, '٘' => 230, 'ٙ' => 230, 'ٚ' => 230, 'ٛ' => 230, 'ٜ' => 220, 'ٝ' => 230, 'ٞ' => 230, 'ٟ' => 220, 'ٰ' => 35, 'ۖ' => 230, 'ۗ' => 230, 'ۘ' => 230, 'ۙ' => 230, 'ۚ' => 230, 'ۛ' => 230, 'ۜ' => 230, '۟' => 230, '۠' => 230, 'ۡ' => 230, 'ۢ' => 230, 'ۣ' => 220, 'ۤ' => 230, 'ۧ' => 230, 'ۨ' => 230, '۪' => 220, '۫' => 230, '۬' => 230, 'ۭ' => 220, 'ܑ' => 36, 'ܰ' => 230, 'ܱ' => 220, 'ܲ' => 230, 'ܳ' => 230, 'ܴ' => 220, 'ܵ' => 230, 'ܶ' => 230, 'ܷ' => 220, 'ܸ' => 220, 'ܹ' => 220, 'ܺ' => 230, 'ܻ' => 220, 'ܼ' => 220, 'ܽ' => 230, 'ܾ' => 220, 'ܿ' => 230, '݀' => 230, '݁' => 230, '݂' => 220, '݃' => 230, '݄' => 220, '݅' => 230, '݆' => 220, '݇' => 230, '݈' => 220, '݉' => 230, '݊' => 230, '߫' => 230, '߬' => 230, '߭' => 230, '߮' => 230, '߯' => 230, '߰' => 230, '߱' => 230, '߲' => 220, '߳' => 230, '߽' => 220, 'ࠖ' => 230, 'ࠗ' => 230, '࠘' => 230, '࠙' => 230, 'ࠛ' => 230, 'ࠜ' => 230, 'ࠝ' => 230, 'ࠞ' => 230, 'ࠟ' => 230, 'ࠠ' => 230, 'ࠡ' => 230, 'ࠢ' => 230, 'ࠣ' => 230, 'ࠥ' => 230, 'ࠦ' => 230, 'ࠧ' => 230, 'ࠩ' => 230, 'ࠪ' => 230, 'ࠫ' => 230, 'ࠬ' => 230, '࠭' => 230, '࡙' => 220, '࡚' => 220, '࡛' => 220, '࣓' => 220, 'ࣔ' => 230, 'ࣕ' => 230, 'ࣖ' => 230, 'ࣗ' => 230, 'ࣘ' => 230, 'ࣙ' => 230, 'ࣚ' => 230, 'ࣛ' => 230, 'ࣜ' => 230, 'ࣝ' => 230, 'ࣞ' => 230, 'ࣟ' => 230, '࣠' => 230, '࣡' => 230, 'ࣣ' => 220, 'ࣤ' => 230, 'ࣥ' => 230, 'ࣦ' => 220, 'ࣧ' => 230, 'ࣨ' => 230, 'ࣩ' => 220, '࣪' => 230, '࣫' => 230, '࣬' => 230, '࣭' => 220, '࣮' => 220, '࣯' => 220, 'ࣰ' => 27, 'ࣱ' => 28, 'ࣲ' => 29, 'ࣳ' => 230, 'ࣴ' => 230, 'ࣵ' => 230, 'ࣶ' => 220, 'ࣷ' => 230, 'ࣸ' => 230, 'ࣹ' => 220, 'ࣺ' => 220, 'ࣻ' => 230, 'ࣼ' => 230, 'ࣽ' => 230, 'ࣾ' => 230, 'ࣿ' => 230, '़' => 7, '्' => 9, '॑' => 230, '॒' => 220, '॓' => 230, '॔' => 230, '়' => 7, '্' => 9, '৾' => 230, '਼' => 7, '੍' => 9, '઼' => 7, '્' => 9, '଼' => 7, '୍' => 9, '்' => 9, '్' => 9, 'ౕ' => 84, 'ౖ' => 91, '಼' => 7, '್' => 9, '഻' => 9, '഼' => 9, '്' => 9, '්' => 9, 'ุ' => 103, 'ู' => 103, 'ฺ' => 9, '่' => 107, '้' => 107, '๊' => 107, '๋' => 107, 'ຸ' => 118, 'ູ' => 118, '຺' => 9, '່' => 122, '້' => 122, '໊' => 122, '໋' => 122, '༘' => 220, '༙' => 220, '༵' => 220, '༷' => 220, '༹' => 216, 'ཱ' => 129, 'ི' => 130, 'ུ' => 132, 'ེ' => 130, 'ཻ' => 130, 'ོ' => 130, 'ཽ' => 130, 'ྀ' => 130, 'ྂ' => 230, 'ྃ' => 230, '྄' => 9, '྆' => 230, '྇' => 230, '࿆' => 220, '့' => 7, '္' => 9, '်' => 9, 'ႍ' => 220, '፝' => 230, '፞' => 230, '፟' => 230, '᜔' => 9, '᜴' => 9, '្' => 9, '៝' => 230, 'ᢩ' => 228, '᤹' => 222, '᤺' => 230, '᤻' => 220, 'ᨗ' => 230, 'ᨘ' => 220, '᩠' => 9, '᩵' => 230, '᩶' => 230, '᩷' => 230, '᩸' => 230, '᩹' => 230, '᩺' => 230, '᩻' => 230, '᩼' => 230, '᩿' => 220, '᪰' => 230, '᪱' => 230, '᪲' => 230, '᪳' => 230, '᪴' => 230, '᪵' => 220, '᪶' => 220, '᪷' => 220, '᪸' => 220, '᪹' => 220, '᪺' => 220, '᪻' => 230, '᪼' => 230, '᪽' => 220, 'ᪿ' => 220, 'ᫀ' => 220, '᬴' => 7, '᭄' => 9, '᭫' => 230, '᭬' => 220, '᭭' => 230, '᭮' => 230, '᭯' => 230, '᭰' => 230, '᭱' => 230, '᭲' => 230, '᭳' => 230, '᮪' => 9, '᮫' => 9, '᯦' => 7, '᯲' => 9, '᯳' => 9, '᰷' => 7, '᳐' => 230, '᳑' => 230, '᳒' => 230, '᳔' => 1, '᳕' => 220, '᳖' => 220, '᳗' => 220, '᳘' => 220, '᳙' => 220, '᳚' => 230, '᳛' => 230, '᳜' => 220, '᳝' => 220, '᳞' => 220, '᳟' => 220, '᳠' => 230, '᳢' => 1, '᳣' => 1, '᳤' => 1, '᳥' => 1, '᳦' => 1, '᳧' => 1, '᳨' => 1, '᳭' => 220, '᳴' => 230, '᳸' => 230, '᳹' => 230, '᷀' => 230, '᷁' => 230, '᷂' => 220, '᷃' => 230, '᷄' => 230, '᷅' => 230, '᷆' => 230, '᷇' => 230, '᷈' => 230, '᷉' => 230, '᷊' => 220, '᷋' => 230, '᷌' => 230, '᷍' => 234, '᷎' => 214, '᷏' => 220, '᷐' => 202, '᷑' => 230, '᷒' => 230, 'ᷓ' => 230, 'ᷔ' => 230, 'ᷕ' => 230, 'ᷖ' => 230, 'ᷗ' => 230, 'ᷘ' => 230, 'ᷙ' => 230, 'ᷚ' => 230, 'ᷛ' => 230, 'ᷜ' => 230, 'ᷝ' => 230, 'ᷞ' => 230, 'ᷟ' => 230, 'ᷠ' => 230, 'ᷡ' => 230, 'ᷢ' => 230, 'ᷣ' => 230, 'ᷤ' => 230, 'ᷥ' => 230, 'ᷦ' => 230, 'ᷧ' => 230, 'ᷨ' => 230, 'ᷩ' => 230, 'ᷪ' => 230, 'ᷫ' => 230, 'ᷬ' => 230, 'ᷭ' => 230, 'ᷮ' => 230, 'ᷯ' => 230, 'ᷰ' => 230, 'ᷱ' => 230, 'ᷲ' => 230, 'ᷳ' => 230, 'ᷴ' => 230, '᷵' => 230, '᷶' => 232, '᷷' => 228, '᷸' => 228, '᷹' => 220, '᷻' => 230, '᷼' => 233, '᷽' => 220, '᷾' => 230, '᷿' => 220, '⃐' => 230, '⃑' => 230, '⃒' => 1, '⃓' => 1, '⃔' => 230, '⃕' => 230, '⃖' => 230, '⃗' => 230, '⃘' => 1, '⃙' => 1, '⃚' => 1, '⃛' => 230, '⃜' => 230, '⃡' => 230, '⃥' => 1, '⃦' => 1, '⃧' => 230, '⃨' => 220, '⃩' => 230, '⃪' => 1, '⃫' => 1, '⃬' => 220, '⃭' => 220, '⃮' => 220, '⃯' => 220, '⃰' => 230, '⳯' => 230, '⳰' => 230, '⳱' => 230, '⵿' => 9, 'ⷠ' => 230, 'ⷡ' => 230, 'ⷢ' => 230, 'ⷣ' => 230, 'ⷤ' => 230, 'ⷥ' => 230, 'ⷦ' => 230, 'ⷧ' => 230, 'ⷨ' => 230, 'ⷩ' => 230, 'ⷪ' => 230, 'ⷫ' => 230, 'ⷬ' => 230, 'ⷭ' => 230, 'ⷮ' => 230, 'ⷯ' => 230, 'ⷰ' => 230, 'ⷱ' => 230, 'ⷲ' => 230, 'ⷳ' => 230, 'ⷴ' => 230, 'ⷵ' => 230, 'ⷶ' => 230, 'ⷷ' => 230, 'ⷸ' => 230, 'ⷹ' => 230, 'ⷺ' => 230, 'ⷻ' => 230, 'ⷼ' => 230, 'ⷽ' => 230, 'ⷾ' => 230, 'ⷿ' => 230, '〪' => 218, '〫' => 228, '〬' => 232, '〭' => 222, '〮' => 224, '〯' => 224, '゙' => 8, '゚' => 8, '꙯' => 230, 'ꙴ' => 230, 'ꙵ' => 230, 'ꙶ' => 230, 'ꙷ' => 230, 'ꙸ' => 230, 'ꙹ' => 230, 'ꙺ' => 230, 'ꙻ' => 230, '꙼' => 230, '꙽' => 230, 'ꚞ' => 230, 'ꚟ' => 230, '꛰' => 230, '꛱' => 230, '꠆' => 9, '꠬' => 9, '꣄' => 9, '꣠' => 230, '꣡' => 230, '꣢' => 230, '꣣' => 230, '꣤' => 230, '꣥' => 230, '꣦' => 230, '꣧' => 230, '꣨' => 230, '꣩' => 230, '꣪' => 230, '꣫' => 230, '꣬' => 230, '꣭' => 230, '꣮' => 230, '꣯' => 230, '꣰' => 230, '꣱' => 230, '꤫' => 220, '꤬' => 220, '꤭' => 220, '꥓' => 9, '꦳' => 7, '꧀' => 9, 'ꪰ' => 230, 'ꪲ' => 230, 'ꪳ' => 230, 'ꪴ' => 220, 'ꪷ' => 230, 'ꪸ' => 230, 'ꪾ' => 230, '꪿' => 230, '꫁' => 230, '꫶' => 9, '꯭' => 9, 'ﬞ' => 26, '︠' => 230, '︡' => 230, '︢' => 230, '︣' => 230, '︤' => 230, '︥' => 230, '︦' => 230, '︧' => 220, '︨' => 220, '︩' => 220, '︪' => 220, '︫' => 220, '︬' => 220, '︭' => 220, '︮' => 230, '︯' => 230, '𐇽' => 220, '𐋠' => 220, '𐍶' => 230, '𐍷' => 230, '𐍸' => 230, '𐍹' => 230, '𐍺' => 230, '𐨍' => 220, '𐨏' => 230, '𐨸' => 230, '𐨹' => 1, '𐨺' => 220, '𐨿' => 9, '𐫥' => 230, '𐫦' => 220, '𐴤' => 230, '𐴥' => 230, '𐴦' => 230, '𐴧' => 230, '𐺫' => 230, '𐺬' => 230, '𐽆' => 220, '𐽇' => 220, '𐽈' => 230, '𐽉' => 230, '𐽊' => 230, '𐽋' => 220, '𐽌' => 230, '𐽍' => 220, '𐽎' => 220, '𐽏' => 220, '𐽐' => 220, '𑁆' => 9, '𑁿' => 9, '𑂹' => 9, '𑂺' => 7, '𑄀' => 230, '𑄁' => 230, '𑄂' => 230, '𑄳' => 9, '𑄴' => 9, '𑅳' => 7, '𑇀' => 9, '𑇊' => 7, '𑈵' => 9, '𑈶' => 7, '𑋩' => 7, '𑋪' => 9, '𑌻' => 7, '𑌼' => 7, '𑍍' => 9, '𑍦' => 230, '𑍧' => 230, '𑍨' => 230, '𑍩' => 230, '𑍪' => 230, '𑍫' => 230, '𑍬' => 230, '𑍰' => 230, '𑍱' => 230, '𑍲' => 230, '𑍳' => 230, '𑍴' => 230, '𑑂' => 9, '𑑆' => 7, '𑑞' => 230, '𑓂' => 9, '𑓃' => 7, '𑖿' => 9, '𑗀' => 7, '𑘿' => 9, '𑚶' => 9, '𑚷' => 7, '𑜫' => 9, '𑠹' => 9, '𑠺' => 7, '𑤽' => 9, '𑤾' => 9, '𑥃' => 7, '𑧠' => 9, '𑨴' => 9, '𑩇' => 9, '𑪙' => 9, '𑰿' => 9, '𑵂' => 7, '𑵄' => 9, '𑵅' => 9, '𑶗' => 9, '𖫰' => 1, '𖫱' => 1, '𖫲' => 1, '𖫳' => 1, '𖫴' => 1, '𖬰' => 230, '𖬱' => 230, '𖬲' => 230, '𖬳' => 230, '𖬴' => 230, '𖬵' => 230, '𖬶' => 230, '𖿰' => 6, '𖿱' => 6, '𛲞' => 1, '𝅥' => 216, '𝅦' => 216, '𝅧' => 1, '𝅨' => 1, '𝅩' => 1, '𝅭' => 226, '𝅮' => 216, '𝅯' => 216, '𝅰' => 216, '𝅱' => 216, '𝅲' => 216, '𝅻' => 220, '𝅼' => 220, '𝅽' => 220, '𝅾' => 220, '𝅿' => 220, '𝆀' => 220, '𝆁' => 220, '𝆂' => 220, '𝆅' => 230, '𝆆' => 230, '𝆇' => 230, '𝆈' => 230, '𝆉' => 230, '𝆊' => 220, '𝆋' => 220, '𝆪' => 230, '𝆫' => 230, '𝆬' => 230, '𝆭' => 230, '𝉂' => 230, '𝉃' => 230, '𝉄' => 230, '𞀀' => 230, '𞀁' => 230, '𞀂' => 230, '𞀃' => 230, '𞀄' => 230, '𞀅' => 230, '𞀆' => 230, '𞀈' => 230, '𞀉' => 230, '𞀊' => 230, '𞀋' => 230, '𞀌' => 230, '𞀍' => 230, '𞀎' => 230, '𞀏' => 230, '𞀐' => 230, '𞀑' => 230, '𞀒' => 230, '𞀓' => 230, '𞀔' => 230, '𞀕' => 230, '𞀖' => 230, '𞀗' => 230, '𞀘' => 230, '𞀛' => 230, '𞀜' => 230, '𞀝' => 230, '𞀞' => 230, '𞀟' => 230, '𞀠' => 230, '𞀡' => 230, '𞀣' => 230, '𞀤' => 230, '𞀦' => 230, '𞀧' => 230, '𞀨' => 230, '𞀩' => 230, '𞀪' => 230, '𞄰' => 230, '𞄱' => 230, '𞄲' => 230, '𞄳' => 230, '𞄴' => 230, '𞄵' => 230, '𞄶' => 230, '𞋬' => 230, '𞋭' => 230, '𞋮' => 230, '𞋯' => 230, '𞣐' => 220, '𞣑' => 220, '𞣒' => 220, '𞣓' => 220, '𞣔' => 220, '𞣕' => 220, '𞣖' => 220, '𞥄' => 230, '𞥅' => 230, '𞥆' => 230, '𞥇' => 230, '𞥈' => 230, '𞥉' => 230, '𞥊' => 7, ); php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-intl-normalizer/LICENSE 0000777 00000002054 15251156632 0024310 0 ustar 00 Copyright (c) 2015-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/bootstrap.php 0000777 00000003072 15251156632 0023643 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Php80 as p; if ( PHP_VERSION_ID >= 80000 ) { return; } if ( ! defined( 'FILTER_VALIDATE_BOOL' ) && defined( 'FILTER_VALIDATE_BOOLEAN' ) ) { define( 'FILTER_VALIDATE_BOOL', FILTER_VALIDATE_BOOLEAN ); } if ( ! function_exists( 'fdiv' ) ) { function fdiv( float $num1, float $num2 ): float { return p\Php80::fdiv( $num1, $num2 ); } } if ( ! function_exists( 'preg_last_error_msg' ) ) { function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } } if ( ! function_exists( 'str_contains' ) ) { function str_contains( ?string $haystack, ?string $needle ): bool { return p\Php80::str_contains( $haystack ?? '', $needle ?? '' ); } } if ( ! function_exists( 'str_starts_with' ) ) { function str_starts_with( ?string $haystack, ?string $needle ): bool { return p\Php80::str_starts_with( $haystack ?? '', $needle ?? '' ); } } if ( ! function_exists( 'str_ends_with' ) ) { function str_ends_with( ?string $haystack, ?string $needle ): bool { return p\Php80::str_ends_with( $haystack ?? '', $needle ?? '' ); } } if ( ! function_exists( 'get_debug_type' ) ) { function get_debug_type( $value ): string { return p\Php80::get_debug_type( $value ); } } if ( ! function_exists( 'get_resource_id' ) ) { function get_resource_id( $resource ): int { return p\Php80::get_resource_id( $resource ); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/composer.json 0000777 00000001522 15251156632 0023635 0 ustar 00 { "name": "symfony/polyfill-php80", "type": "library", "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "keywords": [ "polyfill", "shim", "compatibility", "portable" ], "homepage": "https://symfony.com", "license": "MIT", "authors": [ { "name": "Ion Bazan", "email": "ion.bazan@gmail.com" }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "require": { "php": ">=7.2" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, "files": [ "bootstrap.php" ], "classmap": [ "Resources/stubs" ] }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/README.md 0000777 00000001572 15251156632 0022377 0 ustar 00 # Symfony Polyfill / Php80 This component provides features added to PHP 8.0 core: - [`Stringable`](https://php.net/stringable) interface - [`fdiv`](https://php.net/fdiv) - [`ValueError`](https://php.net/valueerror) class - [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class - `FILTER_VALIDATE_BOOL` constant - [`get_debug_type`](https://php.net/get_debug_type) - [`PhpToken`](https://php.net/phptoken) class - [`preg_last_error_msg`](https://php.net/preg_last_error_msg) - [`str_contains`](https://php.net/str_contains) - [`str_starts_with`](https://php.net/str_starts_with) - [`str_ends_with`](https://php.net/str_ends_with) - [`get_resource_id`](https://php.net/get_resource_id) More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). # License This library is released under the [MIT license](LICENSE). php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/PhpToken.php 0000777 00000003771 15251156632 0023364 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Php80; use function in_array; use function is_string; use function ord; use function strlen; use const T_COMMENT; use const T_DOC_COMMENT; use const T_OPEN_TAG; use const T_WHITESPACE; /** * @author Fedonyuk Anton <info@ensostudio.ru> * * @internal */ class PhpToken { /** * @var int */ public $id; /** * @var string */ public $text; /** * @var int */ public $line; /** * @var int */ public $pos; public function __construct( int $id, string $text, int $line = - 1, int $position = - 1 ) { $this->id = $id; $this->text = $text; $this->line = $line; $this->pos = $position; } public function getTokenName(): ?string { if ( 'UNKNOWN' === $name = token_name( $this->id ) ) { $name = strlen( $this->text ) > 1 || ord( $this->text ) < 32 ? null : $this->text; } return $name; } /** * @param int|string|array $kind */ public function is( $kind ): bool { foreach ( (array) $kind as $value ) { if ( in_array( $value, [ $this->id, $this->text ], true ) ) { return true; } } return false; } public function isIgnorable(): bool { return in_array( $this->id, [ T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG ], true ); } public function __toString(): string { return (string) $this->text; } /** * @return static[] */ public static function tokenize( string $code, int $flags = 0 ): array { $line = 1; $position = 0; $tokens = token_get_all( $code, $flags ); foreach ( $tokens as $index => $token ) { if ( is_string( $token ) ) { $id = ord( $token ); $text = $token; } else { [ $id, $text, $line ] = $token; } $tokens[ $index ] = new static( $id, $text, $line, $position ); $position += strlen( $text ); } return $tokens; } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/LICENSE 0000777 00000002054 15251156632 0022121 0 ustar 00 Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/Php80.php 0000777 00000007022 15251156632 0022524 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Php80; use __PHP_Incomplete_Class; use TypeError; use function get_class; use function is_array; use function is_bool; use function is_float; use function is_int; use function is_object; use function is_resource; use function is_string; use function strlen; use const PREG_BACKTRACK_LIMIT_ERROR; use const PREG_BAD_UTF8_ERROR; use const PREG_BAD_UTF8_OFFSET_ERROR; use const PREG_INTERNAL_ERROR; use const PREG_JIT_STACKLIMIT_ERROR; use const PREG_NO_ERROR; use const PREG_RECURSION_LIMIT_ERROR; /** * @author Ion Bazan <ion.bazan@gmail.com> * @author Nico Oelgart <nicoswd@gmail.com> * @author Nicolas Grekas <p@tchwork.com> * * @internal */ final class Php80 { public static function fdiv( float $dividend, float $divisor ): float { return @( $dividend / $divisor ); } public static function get_debug_type( $value ): string { switch ( true ) { case null === $value: return 'null'; case is_bool( $value ): return 'bool'; case is_string( $value ): return 'string'; case is_array( $value ): return 'array'; case is_int( $value ): return 'int'; case is_float( $value ): return 'float'; case is_object( $value ): break; case $value instanceof __PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; default: if ( null === $type = @get_resource_type( $value ) ) { return 'unknown'; } if ( 'Unknown' === $type ) { $type = 'closed'; } return "resource ($type)"; } $class = get_class( $value ); if ( false === strpos( $class, '@' ) ) { return $class; } return ( get_parent_class( $class ) ?: key( class_implements( $class ) ) ?: 'class' ) . '@anonymous'; } public static function get_resource_id( $res ): int { if ( ! is_resource( $res ) && null === @get_resource_type( $res ) ) { throw new TypeError( sprintf( 'Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type( $res ) ) ); } return (int) $res; } public static function preg_last_error_msg(): string { switch ( preg_last_error() ) { case PREG_INTERNAL_ERROR: return 'Internal error'; case PREG_BAD_UTF8_ERROR: return 'Malformed UTF-8 characters, possibly incorrectly encoded'; case PREG_BAD_UTF8_OFFSET_ERROR: return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; case PREG_BACKTRACK_LIMIT_ERROR: return 'Backtrack limit exhausted'; case PREG_RECURSION_LIMIT_ERROR: return 'Recursion limit exhausted'; case PREG_JIT_STACKLIMIT_ERROR: return 'JIT stack limit exhausted'; case PREG_NO_ERROR: return 'No error'; default: return 'Unknown error'; } } public static function str_contains( string $haystack, string $needle ): bool { return '' === $needle || false !== strpos( $haystack, $needle ); } public static function str_starts_with( string $haystack, string $needle ): bool { return 0 === strncmp( $haystack, $needle, strlen( $needle ) ); } public static function str_ends_with( string $haystack, string $needle ): bool { if ( '' === $needle || $needle === $haystack ) { return true; } if ( '' === $haystack ) { return false; } $needleLength = strlen( $needle ); return $needleLength <= strlen( $haystack ) && 0 === substr_compare( $haystack, $needle, - $needleLength ); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/Resources/stubs/ValueError.php 0000777 00000000465 15251156632 0027031 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ( PHP_VERSION_ID < 80000 ) { class ValueError extends Error { } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/Resources/stubs/Attribute.php 0000777 00000001306 15251156632 0026701 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #[Attribute( Attribute::TARGET_CLASS )] final class Attribute { public const TARGET_CLASS = 1; public const TARGET_FUNCTION = 2; public const TARGET_METHOD = 4; public const TARGET_PROPERTY = 8; public const TARGET_CLASS_CONSTANT = 16; public const TARGET_PARAMETER = 32; public const TARGET_ALL = 63; public const IS_REPEATABLE = 64; /** @var int */ public $flags; public function __construct( int $flags = self::TARGET_ALL ) { $this->flags = $flags; } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/Resources/stubs/PhpToken.php 0000777 00000000560 15251156632 0026467 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ( PHP_VERSION_ID < 80000 && extension_loaded( 'tokenizer' ) ) { class PhpToken extends Symfony\Polyfill\Php80\PhpToken { } } DataLiberation/vendor-patched/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php 0000777 00000000476 15251156632 0030557 0 ustar 00 php-toolkit <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ( PHP_VERSION_ID < 80000 ) { class UnhandledMatchError extends Error { } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-php80/Resources/stubs/Stringable.php 0000777 00000000553 15251156632 0027033 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ( PHP_VERSION_ID < 80000 ) { interface Stringable { /** * @return string */ public function __toString(); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-ctype/bootstrap.php 0000777 00000003243 15251156632 0024030 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Ctype as p; if ( PHP_VERSION_ID >= 80000 ) { return require __DIR__ . '/bootstrap80.php'; } if ( ! function_exists( 'ctype_alnum' ) ) { function ctype_alnum( $text ) { return p\Ctype::ctype_alnum( $text ); } } if ( ! function_exists( 'ctype_alpha' ) ) { function ctype_alpha( $text ) { return p\Ctype::ctype_alpha( $text ); } } if ( ! function_exists( 'ctype_cntrl' ) ) { function ctype_cntrl( $text ) { return p\Ctype::ctype_cntrl( $text ); } } if ( ! function_exists( 'ctype_digit' ) ) { function ctype_digit( $text ) { return p\Ctype::ctype_digit( $text ); } } if ( ! function_exists( 'ctype_graph' ) ) { function ctype_graph( $text ) { return p\Ctype::ctype_graph( $text ); } } if ( ! function_exists( 'ctype_lower' ) ) { function ctype_lower( $text ) { return p\Ctype::ctype_lower( $text ); } } if ( ! function_exists( 'ctype_print' ) ) { function ctype_print( $text ) { return p\Ctype::ctype_print( $text ); } } if ( ! function_exists( 'ctype_punct' ) ) { function ctype_punct( $text ) { return p\Ctype::ctype_punct( $text ); } } if ( ! function_exists( 'ctype_space' ) ) { function ctype_space( $text ) { return p\Ctype::ctype_space( $text ); } } if ( ! function_exists( 'ctype_upper' ) ) { function ctype_upper( $text ) { return p\Ctype::ctype_upper( $text ); } } if ( ! function_exists( 'ctype_xdigit' ) ) { function ctype_xdigit( $text ) { return p\Ctype::ctype_xdigit( $text ); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-ctype/README.md 0000777 00000000501 15251156632 0022553 0 ustar 00 # Symfony Polyfill / Ctype This component provides `ctype_*` functions to users who run php versions without the ctype extension. More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). # License This library is released under the [MIT license](LICENSE). php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-ctype/Ctype.php 0000777 00000014016 15251156632 0023077 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Ctype; use function chr; use function is_int; use function is_string; use const E_USER_DEPRECATED; use const PHP_VERSION_ID; /** * Ctype implementation through regex. * * @internal * * @author Gert de Pagter <BackEndTea@gmail.com> */ final class Ctype { /** * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. * * @see https://php.net/ctype-alnum * * @param mixed $text * * @return bool */ public static function ctype_alnum( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^A-Za-z0-9]/', $text ); } /** * Returns TRUE if every character in text is a letter, FALSE otherwise. * * @see https://php.net/ctype-alpha * * @param mixed $text * * @return bool */ public static function ctype_alpha( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^A-Za-z]/', $text ); } /** * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. * * @see https://php.net/ctype-cntrl * * @param mixed $text * * @return bool */ public static function ctype_cntrl( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^\x00-\x1f\x7f]/', $text ); } /** * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. * * @see https://php.net/ctype-digit * * @param mixed $text * * @return bool */ public static function ctype_digit( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^0-9]/', $text ); } /** * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. * * @see https://php.net/ctype-graph * * @param mixed $text * * @return bool */ public static function ctype_graph( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^!-~]/', $text ); } /** * Returns TRUE if every character in text is a lowercase letter. * * @see https://php.net/ctype-lower * * @param mixed $text * * @return bool */ public static function ctype_lower( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^a-z]/', $text ); } /** * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. * * @see https://php.net/ctype-print * * @param mixed $text * * @return bool */ public static function ctype_print( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^ -~]/', $text ); } /** * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. * * @see https://php.net/ctype-punct * * @param mixed $text * * @return bool */ public static function ctype_punct( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^!-\/\:-@\[-`\{-~]/', $text ); } /** * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. * * @see https://php.net/ctype-space * * @param mixed $text * * @return bool */ public static function ctype_space( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^\s]/', $text ); } /** * Returns TRUE if every character in text is an uppercase letter. * * @see https://php.net/ctype-upper * * @param mixed $text * * @return bool */ public static function ctype_upper( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^A-Z]/', $text ); } /** * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. * * @see https://php.net/ctype-xdigit * * @param mixed $text * * @return bool */ public static function ctype_xdigit( $text ) { $text = self::convert_int_to_char_for_ctype( $text, __FUNCTION__ ); return is_string( $text ) && '' !== $text && ! preg_match( '/[^A-Fa-f0-9]/', $text ); } /** * Converts integers to their char versions according to normal ctype behaviour, if needed. * * If an integer between -128 and 255 inclusive is provided, * it is interpreted as the ASCII value of a single character * (negative values have 256 added in order to allow characters in the Extended ASCII range). * Any other integer is interpreted as a string containing the decimal digits of the integer. * * @param mixed $int * @param string $function * * @return mixed */ private static function convert_int_to_char_for_ctype( $int, $function ) { if ( ! is_int( $int ) ) { return $int; } if ( $int < - 128 || $int > 255 ) { return (string) $int; } if ( PHP_VERSION_ID >= 80100 ) { @trigger_error( $function . '(): Argument of type int will be interpreted as string in the future', E_USER_DEPRECATED ); } if ( $int < 0 ) { $int += 256; } return chr( $int ); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-ctype/composer.json 0000777 00000001445 15251156632 0024026 0 ustar 00 { "name": "symfony/polyfill-ctype", "type": "library", "description": "Symfony polyfill for ctype functions", "keywords": [ "polyfill", "compatibility", "portable", "ctype" ], "homepage": "https://symfony.com", "license": "MIT", "authors": [ { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "require": { "php": ">=7.2" }, "provide": { "ext-ctype": "*" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, "files": [ "bootstrap.php" ] }, "suggest": { "ext-ctype": "For best performance" }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-ctype/bootstrap80.php 0000777 00000004024 15251156632 0024176 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Ctype as p; if ( ! function_exists( 'ctype_alnum' ) ) { /** * @param mixed $text */ function ctype_alnum( $text ): bool { return p\Ctype::ctype_alnum( $text ); } } if ( ! function_exists( 'ctype_alpha' ) ) { /** * @param mixed $text */ function ctype_alpha( $text ): bool { return p\Ctype::ctype_alpha( $text ); } } if ( ! function_exists( 'ctype_cntrl' ) ) { /** * @param mixed $text */ function ctype_cntrl( $text ): bool { return p\Ctype::ctype_cntrl( $text ); } } if ( ! function_exists( 'ctype_digit' ) ) { /** * @param mixed $text */ function ctype_digit( $text ): bool { return p\Ctype::ctype_digit( $text ); } } if ( ! function_exists( 'ctype_graph' ) ) { /** * @param mixed $text */ function ctype_graph( $text ): bool { return p\Ctype::ctype_graph( $text ); } } if ( ! function_exists( 'ctype_lower' ) ) { /** * @param mixed $text */ function ctype_lower( $text ): bool { return p\Ctype::ctype_lower( $text ); } } if ( ! function_exists( 'ctype_print' ) ) { /** * @param mixed $text */ function ctype_print( $text ): bool { return p\Ctype::ctype_print( $text ); } } if ( ! function_exists( 'ctype_punct' ) ) { /** * @param mixed $text */ function ctype_punct( $text ): bool { return p\Ctype::ctype_punct( $text ); } } if ( ! function_exists( 'ctype_space' ) ) { /** * @param mixed $text */ function ctype_space( $text ): bool { return p\Ctype::ctype_space( $text ); } } if ( ! function_exists( 'ctype_upper' ) ) { /** * @param mixed $text */ function ctype_upper( $text ): bool { return p\Ctype::ctype_upper( $text ); } } if ( ! function_exists( 'ctype_xdigit' ) ) { /** * @param mixed $text */ function ctype_xdigit( $text ): bool { return p\Ctype::ctype_xdigit( $text ); } } php-toolkit/DataLiberation/vendor-patched/symfony/polyfill-ctype/LICENSE 0000777 00000002054 15251156632 0022306 0 ustar 00 Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/symfony/deprecation-contracts/LICENSE 0000777 00000002054 15251156632 0023625 0 ustar 00 Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/vendor-patched/symfony/deprecation-contracts/README.md 0000777 00000002222 15251156632 0024074 0 ustar 00 # Symfony Deprecation Contracts A generic function and convention to trigger deprecation notices. This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. The function requires at least 3 arguments: - the name of the Composer package that is triggering the deprecation - the version of the package that introduced the deprecation - the message of the deprecation - more arguments can be provided: they will be inserted in the message using `printf()` formatting Example: ```php trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); ``` This will generate the following message: `Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` While not recommended, the deprecation notices can be completely ignored by declaring an empty `function trigger_deprecation() {}` in your application. php-toolkit/DataLiberation/vendor-patched/symfony/deprecation-contracts/composer.json 0000777 00000001225 15251156632 0025341 0 ustar 00 { "name": "symfony/deprecation-contracts", "type": "library", "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "require": { "php": ">=8.1" }, "autoload": { "files": [ "function.php" ] }, "minimum-stability": "dev", "extra": { "branch-alias": { "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", "url": "https://github.com/symfony/contracts" } } } php-toolkit/DataLiberation/vendor-patched/symfony/deprecation-contracts/function.php 0000777 00000001760 15251156632 0025161 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!function_exists('trigger_deprecation')) { /** * Triggers a silenced deprecation notice. * * @param string $package The name of the Composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message The message of the deprecation * @param mixed ...$args Values to insert in the message using printf() formatting * * @author Nicolas Grekas <p@tchwork.com> */ function trigger_deprecation(string $package, string $version, string $message, ...$args): void { @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); } } php-toolkit/DataLiberation/vendor-patched/symfony/deprecation-contracts/CHANGELOG.md 0000777 00000000225 15251156632 0024427 0 ustar 00 # CHANGELOG The changelog is maintained for all Symfony contracts at the following URL: https://github.com/symfony/contracts/blob/main/CHANGELOG.md php-toolkit/DataLiberation/vendor-patched/README.md 0000777 00000000204 15251156632 0016073 0 ustar 00 # Vendor Patches The libraries in this directory have been downgraded to PHP 7.2 compatibility using Rector and some manual fixes. php-toolkit/DataLiberation/vendor-patched/brick/math/CHANGELOG.md 0000777 00000045323 15251156632 0020463 0 ustar 00 # Changelog All notable changes to this project will be documented in this file. ## UNRELEASED (0.12.2) 👌 **Improvements** - Add missing `RoundingNecessaryException` to the `@throws` annotation of `BigNumber::of()` ## [0.12.1](https://github.com/brick/math/releases/tag/0.12.1) - 2023-11-29 ⚡️ **Performance improvements** - `BigNumber::of()` is now faster, thanks to [@SebastienDug](https://github.com/SebastienDug) in [#77](https://github.com/brick/math/pull/77). ## [0.12.0](https://github.com/brick/math/releases/tag/0.12.0) - 2023-11-26 💥 **Breaking changes** - Minimum PHP version is now 8.1 - `RoundingMode` is now an `enum`; if you're type-hinting rounding modes, you need to type-hint against `RoundingMode` instead of `int` now - `BigNumber` classes do not implement the `Serializable` interface anymore (they use the [new custom object serialization mechanism](https://wiki.php.net/rfc/custom_object_serialization)) - The following breaking changes only affect you if you're creating your own `BigNumber` subclasses: - the return type of `BigNumber::of()` is now `static` - `BigNumber` has a new abstract method `from()` - all `public` and `protected` functions of `BigNumber` are now `final` ## [0.11.0](https://github.com/brick/math/releases/tag/0.11.0) - 2023-01-16 💥 **Breaking changes** - Minimum PHP version is now 8.0 - Methods accepting a union of types are now strongly typed<sup>*</sup> - `MathException` now extends `Exception` instead of `RuntimeException` <sup>* You may now run into type errors if you were passing `Stringable` objects to `of()` or any of the methods internally calling `of()`, with `strict_types` enabled. You can fix this by casting `Stringable` objects to `string` first.</sup> ## [0.10.2](https://github.com/brick/math/releases/tag/0.10.2) - 2022-08-11 👌 **Improvements** - `BigRational::toFloat()` now simplifies the fraction before performing division (#73) thanks to @olsavmic ## [0.10.1](https://github.com/brick/math/releases/tag/0.10.1) - 2022-08-02 ✨ **New features** - `BigInteger::gcdMultiple()` returns the GCD of multiple `BigInteger` numbers ## [0.10.0](https://github.com/brick/math/releases/tag/0.10.0) - 2022-06-18 💥 **Breaking changes** - Minimum PHP version is now 7.4 ## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15 🚀 **Compatibility with PHP 8.1** - Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (#60) thanks @TRowbotham ## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20 🐛 **Bug fix** - Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55). ## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19 ✨ **New features** - `BigInteger::not()` returns the bitwise `NOT` value 🐛 **Bug fixes** - `BigInteger::toBytes()` could return an incorrect binary representation for some numbers - The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available ## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18 👌 **Improvements** - `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal` 💥 **Breaking changes** - Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead - Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead ## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19 🐛 **Bug fix** - `BigInteger::toBytes()` could return an incorrect binary representation for some numbers - The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available ## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18 🚑 **Critical fix** - This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle. ✨ **New features** - `BigInteger::modInverse()` calculates a modular multiplicative inverse - `BigInteger::fromBytes()` creates a `BigInteger` from a byte string - `BigInteger::toBytes()` converts a `BigInteger` to a byte string - `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length - `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds 💩 **Deprecations** - `BigInteger::powerMod()` is now deprecated in favour of `modPow()` ## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15 🐛 **Fixes** - added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable` ⚡️ **Optimizations** - additional optimization in `BigInteger::remainder()` ## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18 ✨ **New features** - `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit ## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16 ✨ **New features** - `BigInteger::isEven()` tests whether the number is even - `BigInteger::isOdd()` tests whether the number is odd - `BigInteger::testBit()` tests if a bit is set - `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number ## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03 🛠️ **Maintenance release** Classes are now annotated for better static analysis with [psalm](https://psalm.dev/). This is a maintenance release: no bug fixes, no new features, no breaking changes. ## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23 ✨ **New feature** `BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto. ## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21 ✨ **New feature** `BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different. ## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08 ⚡️ **Performance improvements** A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24. ## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25 🐛 **Bug fixes** - `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected) ✨ **New features** - `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet - `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation. 💩 **Deprecations** - `BigInteger::parse()` is now deprecated in favour of `fromBase()` `BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences: - the `$base` parameter is required, it does not default to `10` - it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed ## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20 **Improvements** - Safer conversion from `float` when using custom locales - **Much faster** `NativeCalculator` implementation 🚀 You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before. ## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11 **New method** `BigNumber::sum()` returns the sum of one or more numbers. ## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12 **Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20). Thanks @manowark 👍 ## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07 **New method** `BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale. ## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06 **New method** `BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k). **New exception** `NegativeNumberException` is thrown when calling `sqrt()` on a negative number. ## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08 **Performance update** - Further improvement of `toInt()` performance - `NativeCalculator` can now perform some multiplications more efficiently ## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07 Performance optimization of `toInt()` methods. ## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13 **Breaking changes** The following deprecated methods have been removed. Use the new method name instead: | Method removed | Replacement method | | --- | --- | | `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` | | `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` | --- **New features** `BigInteger` has been augmented with 5 new methods for bitwise operations: | New method | Description | | --- | --- | | `and()` | performs a bitwise `AND` operation on two numbers | | `or()` | performs a bitwise `OR` operation on two numbers | | `xor()` | performs a bitwise `XOR` operation on two numbers | | `shiftedLeft()` | returns the number shifted left by a number of bits | | `shiftedRight()` | returns the number shifted right by a number of bits | Thanks to @DASPRiD 👍 ## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20 **New method:** `BigDecimal::hasNonZeroFractionalPart()` **Renamed/deprecated methods:** - `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated - `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated ## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21 **Performance update** `BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available. ## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01 This is a maintenance release, no code has been changed. - When installed with `--no-dev`, the autoloader does not autoload tests anymore - Tests and other files unnecessary for production are excluded from the dist package This will help make installations more compact. ## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02 Methods renamed: - `BigNumber:sign()` has been renamed to `getSign()` - `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()` - `BigDecimal::scale()` has been renamed to `getScale()` - `BigDecimal::integral()` has been renamed to `getIntegral()` - `BigDecimal::fraction()` has been renamed to `getFraction()` - `BigRational::numerator()` has been renamed to `getNumerator()` - `BigRational::denominator()` has been renamed to `getDenominator()` Classes renamed: - `ArithmeticException` has been renamed to `MathException` ## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02 The base class for all exceptions is now `MathException`. `ArithmeticException` has been deprecated, and will be removed in 0.7.0. ## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02 A number of methods have been renamed: - `BigNumber:sign()` is deprecated; use `getSign()` instead - `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead - `BigDecimal::scale()` is deprecated; use `getScale()` instead - `BigDecimal::integral()` is deprecated; use `getIntegral()` instead - `BigDecimal::fraction()` is deprecated; use `getFraction()` instead - `BigRational::numerator()` is deprecated; use `getNumerator()` instead - `BigRational::denominator()` is deprecated; use `getDenominator()` instead The old methods will be removed in version 0.7.0. ## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25 - Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5` - Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead - Method `BigNumber::toInteger()` has been renamed to `toInt()` ## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17 `BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php). The JSON output is always a string. ## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31 This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. ## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06 The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again. ## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05 **New method: `BigNumber::toScale()`** This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary. ## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04 **New features** - Common `BigNumber` interface for all classes, with the following methods: - `sign()` and derived methods (`isZero()`, `isPositive()`, ...) - `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types - `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods - `toInteger()` and `toFloat()` conversion methods to native types - Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type - New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits - New methods: `BigRational::quotient()` and `remainder()` - Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException` - Factory methods `zero()`, `one()` and `ten()` available in all classes - Rounding mode reintroduced in `BigInteger::dividedBy()` This release also comes with many performance improvements. --- **Breaking changes** - `BigInteger`: - `getSign()` is renamed to `sign()` - `toString()` is renamed to `toBase()` - `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour - `BigDecimal`: - `getSign()` is renamed to `sign()` - `getUnscaledValue()` is renamed to `unscaledValue()` - `getScale()` is renamed to `scale()` - `getIntegral()` is renamed to `integral()` - `getFraction()` is renamed to `fraction()` - `divideAndRemainder()` is renamed to `quotientAndRemainder()` - `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode - `toBigInteger()` does not accept a `$roundingMode` parameter anymore - `toBigRational()` does not simplify the fraction anymore; explicitly add `->simplified()` to get the previous behaviour - `BigRational`: - `getSign()` is renamed to `sign()` - `getNumerator()` is renamed to `numerator()` - `getDenominator()` is renamed to `denominator()` - `of()` is renamed to `nd()`, while `parse()` is renamed to `of()` - Miscellaneous: - `ArithmeticException` is moved to an `Exception\` sub-namespace - `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException` ## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31 Backport of two bug fixes from the 0.5 branch: - `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected - Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. ## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16 New method: `BigDecimal::stripTrailingZeros()` ## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12 Introducing a `BigRational` class, to perform calculations on fractions of any size. ## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12 Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`. `BigInteger::dividedBy()` now always returns the quotient of the division. ## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31 Backport of two bug fixes from the 0.5 branch: - `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected - Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. ## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11 New methods: - `BigInteger::remainder()` returns the remainder of a division only - `BigInteger::gcd()` returns the greatest common divisor of two numbers ## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07 Fix `toString()` not handling negative numbers. ## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07 `BigInteger` and `BigDecimal` now have a `getSign()` method that returns: - `-1` if the number is negative - `0` if the number is zero - `1` if the number is positive ## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05 Minor performance improvements ## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04 The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`. ## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04 Stronger immutability guarantee for `BigInteger` and `BigDecimal`. So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that. ## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02 Added `BigDecimal::divideAndRemainder()` ## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22 - `min()` and `max()` do not accept an `array` anymore, but a variable number of parameters - **minimum PHP version is now 5.6** - continuous integration with PHP 7 ## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01 - Added `BigInteger::power()` - Added HHVM support ## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31 First beta release. php-toolkit/DataLiberation/vendor-patched/brick/math/src/Internal/Calculator/GmpCalculator.php 0000777 00000004420 15251156632 0026545 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Internal\Calculator; use Brick\Math\Internal\Calculator; /** * Calculator implementation built around the GMP library. * * @internal * * @psalm-immutable */ class GmpCalculator extends Calculator { public function add(string $a, string $b) : string { return \gmp_strval(\gmp_add($a, $b)); } public function sub(string $a, string $b) : string { return \gmp_strval(\gmp_sub($a, $b)); } public function mul(string $a, string $b) : string { return \gmp_strval(\gmp_mul($a, $b)); } public function divQ(string $a, string $b) : string { return \gmp_strval(\gmp_div_q($a, $b)); } public function divR(string $a, string $b) : string { return \gmp_strval(\gmp_div_r($a, $b)); } public function divQR(string $a, string $b) : array { [$q, $r] = \gmp_div_qr($a, $b); return [ \gmp_strval($q), \gmp_strval($r) ]; } public function pow(string $a, int $e) : string { return \gmp_strval(\gmp_pow($a, $e)); } public function modInverse(string $x, string $m) : ?string { $result = \gmp_invert($x, $m); if ($result === false) { return null; } return \gmp_strval($result); } public function modPow(string $base, string $exp, string $mod) : string { return \gmp_strval(\gmp_powm($base, $exp, $mod)); } public function gcd(string $a, string $b) : string { return \gmp_strval(\gmp_gcd($a, $b)); } public function fromBase(string $number, int $base) : string { return \gmp_strval(\gmp_init($number, $base)); } public function toBase(string $number, int $base) : string { return \gmp_strval($number, $base); } public function and(string $a, string $b) : string { return \gmp_strval(\gmp_and($a, $b)); } public function or(string $a, string $b) : string { return \gmp_strval(\gmp_or($a, $b)); } public function xor(string $a, string $b) : string { return \gmp_strval(\gmp_xor($a, $b)); } public function sqrt(string $n) : string { return \gmp_strval(\gmp_sqrt($n)); } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Internal/Calculator/NativeCalculator.php 0000777 00000032645 15251156632 0027262 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Internal\Calculator; use Brick\Math\Internal\Calculator; /** * Calculator implementation using only native PHP code. * * @internal * * @psalm-immutable */ class NativeCalculator extends Calculator { /** * The max number of digits the platform can natively add, subtract, multiply or divide without overflow. * For multiplication, this represents the max sum of the lengths of both operands. * * In addition, it is assumed that an extra digit can hold a carry (1) without overflowing. * Example: 32-bit: max number 1,999,999,999 (9 digits + carry) * 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry) * @readonly * @var int */ private $maxDigits; /** * @codeCoverageIgnore */ public function __construct() { switch (PHP_INT_SIZE) { case 4: $this->maxDigits = 9; break; case 8: $this->maxDigits = 18; break; default: throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.'); } } public function add(string $a, string $b) : string { /** * @psalm-var numeric-string $a * @psalm-var numeric-string $b */ $result = $a + $b; if (is_int($result)) { return (string) $result; } if ($a === '0') { return $b; } if ($b === '0') { return $a; } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig); if ($aNeg) { $result = $this->neg($result); } return $result; } public function sub(string $a, string $b) : string { return $this->add($a, $this->neg($b)); } public function mul(string $a, string $b) : string { /** * @psalm-var numeric-string $a * @psalm-var numeric-string $b */ $result = $a * $b; if (is_int($result)) { return (string) $result; } if ($a === '0' || $b === '0') { return '0'; } if ($a === '1') { return $b; } if ($b === '1') { return $a; } if ($a === '-1') { return $this->neg($b); } if ($b === '-1') { return $this->neg($a); } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $result = $this->doMul($aDig, $bDig); if ($aNeg !== $bNeg) { $result = $this->neg($result); } return $result; } public function divQ(string $a, string $b) : string { return $this->divQR($a, $b)[0]; } public function divR(string $a, string $b): string { return $this->divQR($a, $b)[1]; } public function divQR(string $a, string $b) : array { if ($a === '0') { return ['0', '0']; } if ($a === $b) { return ['1', '0']; } if ($b === '1') { return [$a, '0']; } if ($b === '-1') { return [$this->neg($a), '0']; } /** @psalm-var numeric-string $a */ $na = $a * 1; // cast to number if (is_int($na)) { /** @psalm-var numeric-string $b */ $nb = $b * 1; if (is_int($nb)) { // the only division that may overflow is PHP_INT_MIN / -1, // which cannot happen here as we've already handled a divisor of -1 above. $q = intdiv($na, $nb); $r = $na % $nb; return [ (string) $q, (string) $r ]; } } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); [$q, $r] = $this->doDiv($aDig, $bDig); if ($aNeg !== $bNeg) { $q = $this->neg($q); } if ($aNeg) { $r = $this->neg($r); } return [$q, $r]; } public function pow(string $a, int $e) : string { if ($e === 0) { return '1'; } if ($e === 1) { return $a; } $odd = $e % 2; $e -= $odd; $aa = $this->mul($a, $a); /** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */ $result = $this->pow($aa, $e / 2); if ($odd === 1) { $result = $this->mul($result, $a); } return $result; } /** * Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/ */ public function modPow(string $base, string $exp, string $mod) : string { // special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0) if ($base === '0' && $exp === '0' && $mod === '1') { return '0'; } // special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0) if ($exp === '0' && $mod === '1') { return '0'; } $x = $base; $res = '1'; // numbers are positive, so we can use remainder instead of modulo $x = $this->divR($x, $mod); while ($exp !== '0') { if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd $res = $this->divR($this->mul($res, $x), $mod); } $exp = $this->divQ($exp, '2'); $x = $this->divR($this->mul($x, $x), $mod); } return $res; } /** * Adapted from https://cp-algorithms.com/num_methods/roots_newton.html */ public function sqrt(string $n) : string { if ($n === '0') { return '0'; } // initial approximation $x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1); $decreased = false; for (;;) { $nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2'); if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) { break; } $decreased = $this->cmp($nx, $x) < 0; $x = $nx; } return $x; } /** * Performs the addition of two non-signed large integers. */ private function doAdd(string $a, string $b) : string { [$a, $b, $length] = $this->pad($a, $b); $carry = 0; $result = ''; for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { $blockLength = $this->maxDigits; if ($i < 0) { $blockLength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } /** @psalm-var numeric-string $blockA */ $blockA = \substr($a, $i, $blockLength); /** @psalm-var numeric-string $blockB */ $blockB = \substr($b, $i, $blockLength); $sum = (string) ($blockA + $blockB + $carry); $sumLength = \strlen($sum); if ($sumLength > $blockLength) { $sum = \substr($sum, 1); $carry = 1; } else { if ($sumLength < $blockLength) { $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; } $carry = 0; } $result = $sum . $result; if ($i === 0) { break; } } if ($carry === 1) { $result = '1' . $result; } return $result; } /** * Performs the subtraction of two non-signed large integers. */ private function doSub(string $a, string $b) : string { if ($a === $b) { return '0'; } // Ensure that we always subtract to a positive result: biggest minus smallest. $cmp = $this->doCmp($a, $b); $invert = ($cmp === -1); if ($invert) { $c = $a; $a = $b; $b = $c; } [$a, $b, $length] = $this->pad($a, $b); $carry = 0; $result = ''; $complement = 10 ** $this->maxDigits; for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { $blockLength = $this->maxDigits; if ($i < 0) { $blockLength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } /** @psalm-var numeric-string $blockA */ $blockA = \substr($a, $i, $blockLength); /** @psalm-var numeric-string $blockB */ $blockB = \substr($b, $i, $blockLength); $sum = $blockA - $blockB - $carry; if ($sum < 0) { $sum += $complement; $carry = 1; } else { $carry = 0; } $sum = (string) $sum; $sumLength = \strlen($sum); if ($sumLength < $blockLength) { $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; } $result = $sum . $result; if ($i === 0) { break; } } // Carry cannot be 1 when the loop ends, as a > b assert($carry === 0); $result = \ltrim($result, '0'); if ($invert) { $result = $this->neg($result); } return $result; } /** * Performs the multiplication of two non-signed large integers. */ private function doMul(string $a, string $b) : string { $x = \strlen($a); $y = \strlen($b); $maxDigits = \intdiv($this->maxDigits, 2); $complement = 10 ** $maxDigits; $result = '0'; for ($i = $x - $maxDigits;; $i -= $maxDigits) { $blockALength = $maxDigits; if ($i < 0) { $blockALength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } $blockA = (int) \substr($a, $i, $blockALength); $line = ''; $carry = 0; for ($j = $y - $maxDigits;; $j -= $maxDigits) { $blockBLength = $maxDigits; if ($j < 0) { $blockBLength += $j; /** @psalm-suppress LoopInvalidation */ $j = 0; } $blockB = (int) \substr($b, $j, $blockBLength); $mul = $blockA * $blockB + $carry; $value = $mul % $complement; $carry = ($mul - $value) / $complement; $value = (string) $value; $value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT); $line = $value . $line; if ($j === 0) { break; } } if ($carry !== 0) { $line = $carry . $line; } $line = \ltrim($line, '0'); if ($line !== '') { $line .= \str_repeat('0', $x - $blockALength - $i); $result = $this->add($result, $line); } if ($i === 0) { break; } } return $result; } /** * Performs the division of two non-signed large integers. * * @return string[] The quotient and remainder. */ private function doDiv(string $a, string $b) : array { $cmp = $this->doCmp($a, $b); if ($cmp === -1) { return ['0', $a]; } $x = \strlen($a); $y = \strlen($b); // we now know that a >= b && x >= y $q = '0'; // quotient $r = $a; // remainder $z = $y; // focus length, always $y or $y+1 for (;;) { $focus = \substr($a, 0, $z); $cmp = $this->doCmp($focus, $b); if ($cmp === -1) { if ($z === $x) { // remainder < dividend break; } $z++; } $zeros = \str_repeat('0', $x - $z); $q = $this->add($q, '1' . $zeros); $a = $this->sub($a, $b . $zeros); $r = $a; if ($r === '0') { // remainder == 0 break; } $x = \strlen($a); if ($x < $y) { // remainder < dividend break; } $z = $y; } return [$q, $r]; } /** * Compares two non-signed large numbers. * * @psalm-return -1|0|1 */ private function doCmp(string $a, string $b) : int { $x = \strlen($a); $y = \strlen($b); $cmp = $x <=> $y; if ($cmp !== 0) { return $cmp; } return \strcmp($a, $b) <=> 0; // enforce -1|0|1 } /** * Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length. * * The numbers must only consist of digits, without leading minus sign. * * @return array{string, string, int} */ private function pad(string $a, string $b) : array { $x = \strlen($a); $y = \strlen($b); if ($x > $y) { $b = \str_repeat('0', $x - $y) . $b; return [$a, $b, $x]; } if ($x < $y) { $a = \str_repeat('0', $y - $x) . $a; return [$a, $b, $y]; } return [$a, $b, $x]; } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Internal/Calculator/BcMathCalculator.php 0000777 00000002370 15251156632 0027162 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Internal\Calculator; use Brick\Math\Internal\Calculator; /** * Calculator implementation built around the bcmath library. * * @internal * * @psalm-immutable */ class BcMathCalculator extends Calculator { public function add(string $a, string $b) : string { return \bcadd($a, $b, 0); } public function sub(string $a, string $b) : string { return \bcsub($a, $b, 0); } public function mul(string $a, string $b) : string { return \bcmul($a, $b, 0); } public function divQ(string $a, string $b) : string { return \bcdiv($a, $b, 0); } public function divR(string $a, string $b) : string { return \bcmod($a, $b, 0); } public function divQR(string $a, string $b) : array { $q = \bcdiv($a, $b, 0); $r = \bcmod($a, $b, 0); return [$q, $r]; } public function pow(string $a, int $e) : string { return \bcpow($a, (string) $e, 0); } public function modPow(string $base, string $exp, string $mod) : string { return \bcpowmod($base, $exp, $mod, 0); } public function sqrt(string $n) : string { return \bcsqrt($n, 0); } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Internal/Calculator.php 0000777 00000046054 15251156632 0024021 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Internal; use Brick\Math\Exception\RoundingNecessaryException; use Brick\Math\RoundingMode; /** * Performs basic operations on arbitrary size integers. * * Unless otherwise specified, all parameters must be validated as non-empty strings of digits, * without leading zero, and with an optional leading minus sign if the number is not zero. * * Any other parameter format will lead to undefined behaviour. * All methods must return strings respecting this format, unless specified otherwise. * * @internal * * @psalm-immutable */ abstract class Calculator { /** * The maximum exponent value allowed for the pow() method. */ public const MAX_POWER = 1000000; /** * The alphabet for converting from and to base 2 to 36, lowercase. */ public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'; /** * The Calculator instance in use. * @var \Brick\Math\Internal\Calculator|null */ private static $instance; /** * Sets the Calculator instance to use. * * An instance is typically set only in unit tests: the autodetect is usually the best option. * * @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect. */ final public static function set(?Calculator $calculator) : void { self::$instance = $calculator; } /** * Returns the Calculator instance to use. * * If none has been explicitly set, the fastest available implementation will be returned. * * @psalm-pure * @psalm-suppress ImpureStaticProperty */ final public static function get() : Calculator { if (self::$instance === null) { /** @psalm-suppress ImpureMethodCall */ self::$instance = self::detect(); } return self::$instance; } /** * Returns the fastest available Calculator implementation. * * @codeCoverageIgnore */ private static function detect() : Calculator { if (\extension_loaded('gmp')) { return new Calculator\GmpCalculator(); } if (\extension_loaded('bcmath')) { return new Calculator\BcMathCalculator(); } return new Calculator\NativeCalculator(); } /** * Extracts the sign & digits of the operands. * * @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits. */ final protected function init(string $a, string $b) : array { return [ $aNeg = ($a[0] === '-'), $bNeg = ($b[0] === '-'), $aNeg ? \substr($a, 1) : $a, $bNeg ? \substr($b, 1) : $b, ]; } /** * Returns the absolute value of a number. */ final public function abs(string $n) : string { return ($n[0] === '-') ? \substr($n, 1) : $n; } /** * Negates a number. */ final public function neg(string $n) : string { if ($n === '0') { return '0'; } if ($n[0] === '-') { return \substr($n, 1); } return '-' . $n; } /** * Compares two numbers. * * @psalm-return -1|0|1 * * @return int -1 if the first number is less than, 0 if equal to, 1 if greater than the second number. */ final public function cmp(string $a, string $b) : int { [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); if ($aNeg && ! $bNeg) { return -1; } if ($bNeg && ! $aNeg) { return 1; } $aLen = \strlen($aDig); $bLen = \strlen($bDig); if ($aLen < $bLen) { $result = -1; } elseif ($aLen > $bLen) { $result = 1; } else { $result = $aDig <=> $bDig; } return $aNeg ? -$result : $result; } /** * Adds two numbers. */ abstract public function add(string $a, string $b) : string; /** * Subtracts two numbers. */ abstract public function sub(string $a, string $b) : string; /** * Multiplies two numbers. */ abstract public function mul(string $a, string $b) : string; /** * Returns the quotient of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return string The quotient. */ abstract public function divQ(string $a, string $b) : string; /** * Returns the remainder of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return string The remainder. */ abstract public function divR(string $a, string $b) : string; /** * Returns the quotient and remainder of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return array{string, string} An array containing the quotient and remainder. */ abstract public function divQR(string $a, string $b) : array; /** * Exponentiates a number. * * @param string $a The base number. * @param int $e The exponent, validated as an integer between 0 and MAX_POWER. * * @return string The power. */ abstract public function pow(string $a, int $e) : string; /** * @param string $b The modulus; must not be zero. */ public function mod(string $a, string $b) : string { return $this->divR($this->add($this->divR($a, $b), $b), $b); } /** * Returns the modular multiplicative inverse of $x modulo $m. * * If $x has no multiplicative inverse mod m, this method must return null. * * This method can be overridden by the concrete implementation if the underlying library has built-in support. * * @param string $m The modulus; must not be negative or zero. */ public function modInverse(string $x, string $m) : ?string { if ($m === '1') { return '0'; } $modVal = $x; if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) { $modVal = $this->mod($x, $m); } [$g, $x] = $this->gcdExtended($modVal, $m); if ($g !== '1') { return null; } return $this->mod($this->add($this->mod($x, $m), $m), $m); } /** * Raises a number into power with modulo. * * @param string $base The base number; must be positive or zero. * @param string $exp The exponent; must be positive or zero. * @param string $mod The modulus; must be strictly positive. */ abstract public function modPow(string $base, string $exp, string $mod) : string; /** * Returns the greatest common divisor of the two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for GCD calculations. * * @return string The GCD, always positive, or zero if both arguments are zero. */ public function gcd(string $a, string $b) : string { if ($a === '0') { return $this->abs($b); } if ($b === '0') { return $this->abs($a); } return $this->gcd($b, $this->divR($a, $b)); } /** * @return array{string, string, string} GCD, X, Y */ private function gcdExtended(string $a, string $b) : array { if ($a === '0') { return [$b, '0', '1']; } [$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a); $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); $y = $x1; return [$gcd, $x, $y]; } /** * Returns the square root of the given number, rounded down. * * The result is the largest x such that x² ≤ n. * The input MUST NOT be negative. */ abstract public function sqrt(string $n) : string; /** * Converts a number from an arbitrary base. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for base conversion. * * @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base. * @param int $base The base of the number, validated from 2 to 36. * * @return string The converted number, following the Calculator conventions. */ public function fromBase(string $number, int $base) : string { return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base); } /** * Converts a number to an arbitrary base. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for base conversion. * * @param string $number The number to convert, following the Calculator conventions. * @param int $base The base to convert to, validated from 2 to 36. * * @return string The converted number, lowercase. */ public function toBase(string $number, int $base) : string { $negative = ($number[0] === '-'); if ($negative) { $number = \substr($number, 1); } $number = $this->toArbitraryBase($number, self::ALPHABET, $base); if ($negative) { return '-' . $number; } return $number; } /** * Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10. * * @param string $number The number to convert, validated as a non-empty string, * containing only chars in the given alphabet/base. * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. * @param int $base The base of the number, validated from 2 to alphabet length. * * @return string The number in base 10, following the Calculator conventions. */ final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string { // remove leading "zeros" $number = \ltrim($number, $alphabet[0]); if ($number === '') { return '0'; } // optimize for "one" if ($number === $alphabet[1]) { return '1'; } $result = '0'; $power = '1'; $base = (string) $base; for ($i = \strlen($number) - 1; $i >= 0; $i--) { $index = \strpos($alphabet, $number[$i]); if ($index !== 0) { $result = $this->add($result, ($index === 1) ? $power : $this->mul($power, (string) $index) ); } if ($i !== 0) { $power = $this->mul($power, $base); } } return $result; } /** * Converts a non-negative number to an arbitrary base using a custom alphabet. * * @param string $number The number to convert, positive or zero, following the Calculator conventions. * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. * @param int $base The base to convert to, validated from 2 to alphabet length. * * @return string The converted number in the given alphabet. */ final public function toArbitraryBase(string $number, string $alphabet, int $base) : string { if ($number === '0') { return $alphabet[0]; } $base = (string) $base; $result = ''; while ($number !== '0') { [$number, $remainder] = $this->divQR($number, $base); $remainder = (int) $remainder; $result .= $alphabet[$remainder]; } return \strrev($result); } /** * Performs a rounded division. * * Rounding is performed when the remainder of the division is not zero. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * @param mixed $roundingMode The rounding mode. * * @throws \InvalidArgumentException If the rounding mode is invalid. * @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary. * * @psalm-suppress ImpureFunctionCall * @param \Brick\Math\RoundingMode::* $roundingMode */ final public function divRound(string $a, string $b, $roundingMode) : string { [$quotient, $remainder] = $this->divQR($a, $b); $hasDiscardedFraction = ($remainder !== '0'); $isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-'); $discardedFractionSign = function() use ($remainder, $b) : int { $r = $this->abs($this->mul($remainder, '2')); $b = $this->abs($b); return $this->cmp($r, $b); }; $increment = false; switch ($roundingMode) { case RoundingMode::UNNECESSARY: if ($hasDiscardedFraction) { throw RoundingNecessaryException::roundingNecessary(); } break; case RoundingMode::UP: $increment = $hasDiscardedFraction; break; case RoundingMode::DOWN: break; case RoundingMode::CEILING: $increment = $hasDiscardedFraction && $isPositiveOrZero; break; case RoundingMode::FLOOR: $increment = $hasDiscardedFraction && ! $isPositiveOrZero; break; case RoundingMode::HALF_UP: $increment = $discardedFractionSign() >= 0; break; case RoundingMode::HALF_DOWN: $increment = $discardedFractionSign() > 0; break; case RoundingMode::HALF_CEILING: $increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0; break; case RoundingMode::HALF_FLOOR: $increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; break; case RoundingMode::HALF_EVEN: $lastDigit = (int) $quotient[-1]; $lastDigitIsEven = ($lastDigit % 2 === 0); $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; break; default: throw new \InvalidArgumentException('Invalid rounding mode.'); } if ($increment) { return $this->add($quotient, $isPositiveOrZero ? '1' : '-1'); } return $quotient; } /** * Calculates bitwise AND of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. */ public function and(string $a, string $b) : string { return $this->bitwise('and', $a, $b); } /** * Calculates bitwise OR of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. */ public function or(string $a, string $b) : string { return $this->bitwise('or', $a, $b); } /** * Calculates bitwise XOR of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. */ public function xor(string $a, string $b) : string { return $this->bitwise('xor', $a, $b); } /** * Performs a bitwise operation on a decimal number. * * @param 'and'|'or'|'xor' $operator The operator to use. * @param string $a The left operand. * @param string $b The right operand. */ private function bitwise(string $operator, string $a, string $b) : string { [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $aBin = $this->toBinary($aDig); $bBin = $this->toBinary($bDig); $aLen = \strlen($aBin); $bLen = \strlen($bBin); if ($aLen > $bLen) { $bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin; } elseif ($bLen > $aLen) { $aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin; } if ($aNeg) { $aBin = $this->twosComplement($aBin); } if ($bNeg) { $bBin = $this->twosComplement($bBin); } switch ($operator) { case 'and': $value = $aBin & $bBin; break; case 'or': $value = $aBin | $bBin; break; case 'xor': $value = $aBin ^ $bBin; break; } switch ($operator) { case 'and': $negative = ($aNeg and $bNeg); break; case 'or': $negative = ($aNeg or $bNeg); break; case 'xor': $negative = ($aNeg xor $bNeg); break; } if ($negative) { $value = $this->twosComplement($value); } $result = $this->toDecimal($value); return $negative ? $this->neg($result) : $result; } /** * @param string $number A positive, binary number. */ private function twosComplement(string $number) : string { $xor = \str_repeat("\xff", \strlen($number)); $number ^= $xor; for ($i = \strlen($number) - 1; $i >= 0; $i--) { $byte = \ord($number[$i]); if (++$byte !== 256) { $number[$i] = \chr($byte); break; } $number[$i] = "\x00"; if ($i === 0) { $number = "\x01" . $number; } } return $number; } /** * Converts a decimal number to a binary string. * * @param string $number The number to convert, positive or zero, only digits. */ private function toBinary(string $number) : string { $result = ''; while ($number !== '0') { [$number, $remainder] = $this->divQR($number, '256'); $result .= \chr((int) $remainder); } return \strrev($result); } /** * Returns the positive decimal representation of a binary number. * * @param string $bytes The bytes representing the number. */ private function toDecimal(string $bytes) : string { $result = '0'; $power = '1'; for ($i = \strlen($bytes) - 1; $i >= 0; $i--) { $index = \ord($bytes[$i]); if ($index !== 0) { $result = $this->add($result, ($index === 1) ? $power : $this->mul($power, (string) $index) ); } if ($i !== 0) { $power = $this->mul($power, '256'); } } return $result; } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/RoundingMode.php 0000777 00000000727 15251156632 0022543 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math; class RoundingMode { public const UNNECESSARY = 'unnecessary'; public const UP = 'up'; public const DOWN = 'down'; public const CEILING = 'ceiling'; public const FLOOR = 'floor'; public const HALF_UP = 'half_up'; public const HALF_DOWN = 'half_down'; public const HALF_CEILING = 'half_ceiling'; public const HALF_FLOOR = 'half_floor'; public const HALF_EVEN = 'half_even'; } php-toolkit/DataLiberation/vendor-patched/brick/math/src/BigNumber.php 0000777 00000040131 15251156632 0022014 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math; use Brick\Math\Exception\DivisionByZeroException; use Brick\Math\Exception\MathException; use Brick\Math\Exception\NumberFormatException; use Brick\Math\Exception\RoundingNecessaryException; /** * Common interface for arbitrary-precision rational numbers. * * @psalm-immutable */ abstract class BigNumber implements \JsonSerializable { /** * The regular expression used to parse integer or decimal numbers. */ private const PARSE_REGEXP_NUMERICAL = '/^' . '(?<sign>[\-\+])?' . '(?<integral>[0-9]+)?' . '(?<point>\.)?' . '(?<fractional>[0-9]+)?' . '(?:[eE](?<exponent>[\-\+]?[0-9]+))?' . '$/'; /** * The regular expression used to parse rational numbers. */ private const PARSE_REGEXP_RATIONAL = '/^' . '(?<sign>[\-\+])?' . '(?<numerator>[0-9]+)' . '\/?' . '(?<denominator>[0-9]+)' . '$/'; /** * Creates a BigNumber of the given value. * * The concrete return type is dependent on the given value, with the following rules: * * - BigNumber instances are returned as is * - integer numbers are returned as BigInteger * - floating point numbers are converted to a string then parsed as such * - strings containing a `/` character are returned as BigRational * - strings containing a `.` character or using an exponential notation are returned as BigDecimal * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger * * @throws NumberFormatException If the format of the number is not valid. * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. * @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding. * * @psalm-pure * @param \Brick\Math\BigNumber|int|float|string $value * @return static */ final public static function of($value) { $value = self::_of($value); if (static::class === BigNumber::class) { // https://github.com/vimeo/psalm/issues/10309 assert($value instanceof static); return $value; } return static::from($value); } /** * @throws NumberFormatException If the format of the number is not valid. * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. * * @psalm-pure * @param \Brick\Math\BigNumber|int|float|string $value */ private static function _of($value) : BigNumber { if ($value instanceof BigNumber) { return $value; } if (\is_int($value)) { return new BigInteger((string) $value); } if (is_float($value)) { $value = (string) $value; } if (strpos($value, '/') !== false) { // Rational number if (\preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw NumberFormatException::invalidFormat($value); } $sign = $matches['sign']; $numerator = $matches['numerator']; $denominator = $matches['denominator']; assert($numerator !== null); assert($denominator !== null); $numerator = self::cleanUp($sign, $numerator); $denominator = self::cleanUp(null, $denominator); if ($denominator === '0') { throw DivisionByZeroException::denominatorMustNotBeZero(); } return new BigRational( new BigInteger($numerator), new BigInteger($denominator), false ); } else { // Integer or decimal number if (\preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw NumberFormatException::invalidFormat($value); } $sign = $matches['sign']; $point = $matches['point']; $integral = $matches['integral']; $fractional = $matches['fractional']; $exponent = $matches['exponent']; if ($integral === null && $fractional === null) { throw NumberFormatException::invalidFormat($value); } if ($integral === null) { $integral = '0'; } if ($point !== null || $exponent !== null) { $fractional = ($fractional ?? ''); $exponent = ($exponent !== null) ? (int)$exponent : 0; if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { throw new NumberFormatException('Exponent too large.'); } $unscaledValue = self::cleanUp($sign, $integral . $fractional); $scale = \strlen($fractional) - $exponent; if ($scale < 0) { if ($unscaledValue !== '0') { $unscaledValue .= \str_repeat('0', -$scale); } $scale = 0; } return new BigDecimal($unscaledValue, $scale); } $integral = self::cleanUp($sign, $integral); return new BigInteger($integral); } } /** * Overridden by subclasses to convert a BigNumber to an instance of the subclass. * * @throws RoundingNecessaryException If the value cannot be converted. * * @psalm-pure * @return static */ abstract protected static function from(BigNumber $number); /** * Proxy method to access BigInteger's protected constructor from sibling classes. * * @internal * @psalm-pure */ final protected function newBigInteger(string $value) : BigInteger { return new BigInteger($value); } /** * Proxy method to access BigDecimal's protected constructor from sibling classes. * * @internal * @psalm-pure */ final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal { return new BigDecimal($value, $scale); } /** * Proxy method to access BigRational's protected constructor from sibling classes. * * @internal * @psalm-pure */ final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational { return new BigRational($numerator, $denominator, $checkDenominator); } /** * Returns the minimum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible * to an instance of the class this method is called on. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-pure * @return static */ final public static function min(...$values) { $min = null; foreach ($values as $value) { $value = static::of($value); if ($min === null || $value->isLessThan($min)) { $min = $value; } } if ($min === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $min; } /** * Returns the maximum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible * to an instance of the class this method is called on. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-pure * @return static */ final public static function max(...$values) { $max = null; foreach ($values as $value) { $value = static::of($value); if ($max === null || $value->isGreaterThan($max)) { $max = $value; } } if ($max === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $max; } /** * Returns the sum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible * to an instance of the class this method is called on. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-pure * @return static */ final public static function sum(...$values) { /** @var static|null $sum */ $sum = null; foreach ($values as $value) { $value = static::of($value); $sum = $sum === null ? $value : self::add($sum, $value); } if ($sum === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $sum; } /** * Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException. * * @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to * concrete classes the responsibility to perform the addition themselves or delegate it to the given number, * depending on their ability to perform the operation. This will also require a version bump because we're * potentially breaking custom BigNumber implementations (if any...) * * @psalm-pure */ private static function add(BigNumber $a, BigNumber $b) : BigNumber { if ($a instanceof BigRational) { return $a->plus($b); } if ($b instanceof BigRational) { return $b->plus($a); } if ($a instanceof BigDecimal) { return $a->plus($b); } if ($b instanceof BigDecimal) { return $b->plus($a); } /** @var BigInteger $a */ return $a->plus($b); } /** * Removes optional leading zeros and applies sign. * * @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'. * @param string $number The number, validated as a non-empty string of digits. * * @psalm-pure */ private static function cleanUp(?string $sign, string $number) : string { $number = \ltrim($number, '0'); if ($number === '') { return '0'; } return $sign === '-' ? '-' . $number : $number; } /** * Checks if this number is equal to the given one. * @param \Brick\Math\BigNumber|int|float|string $that */ final public function isEqualTo($that) : bool { return $this->compareTo($that) === 0; } /** * Checks if this number is strictly lower than the given one. * @param \Brick\Math\BigNumber|int|float|string $that */ final public function isLessThan($that) : bool { return $this->compareTo($that) < 0; } /** * Checks if this number is lower than or equal to the given one. * @param \Brick\Math\BigNumber|int|float|string $that */ final public function isLessThanOrEqualTo($that) : bool { return $this->compareTo($that) <= 0; } /** * Checks if this number is strictly greater than the given one. * @param \Brick\Math\BigNumber|int|float|string $that */ final public function isGreaterThan($that) : bool { return $this->compareTo($that) > 0; } /** * Checks if this number is greater than or equal to the given one. * @param \Brick\Math\BigNumber|int|float|string $that */ final public function isGreaterThanOrEqualTo($that) : bool { return $this->compareTo($that) >= 0; } /** * Checks if this number equals zero. */ final public function isZero() : bool { return $this->getSign() === 0; } /** * Checks if this number is strictly negative. */ final public function isNegative() : bool { return $this->getSign() < 0; } /** * Checks if this number is negative or zero. */ final public function isNegativeOrZero() : bool { return $this->getSign() <= 0; } /** * Checks if this number is strictly positive. */ final public function isPositive() : bool { return $this->getSign() > 0; } /** * Checks if this number is positive or zero. */ final public function isPositiveOrZero() : bool { return $this->getSign() >= 0; } /** * Returns the sign of this number. * * @psalm-return -1|0|1 * * @return int -1 if the number is negative, 0 if zero, 1 if positive. */ abstract public function getSign() : int; /** * Compares this number to the given one. * * @psalm-return -1|0|1 * * @return int -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`. * * @throws MathException If the number is not valid. * @param \Brick\Math\BigNumber|int|float|string $that */ abstract public function compareTo($that) : int; /** * Converts this number to a BigInteger. * * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. */ abstract public function toBigInteger() : BigInteger; /** * Converts this number to a BigDecimal. * * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. */ abstract public function toBigDecimal() : BigDecimal; /** * Converts this number to a BigRational. */ abstract public function toBigRational() : BigRational; /** * Converts this number to a BigDecimal with the given scale, using rounding if necessary. * * @param int $scale The scale of the resulting `BigDecimal`. * @param mixed $roundingMode An optional rounding mode, defaults to UNNECESSARY. * * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. * This only applies when RoundingMode::UNNECESSARY is used. * @param \Brick\Math\RoundingMode::* $roundingMode */ abstract public function toScale(int $scale, $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; /** * Returns the exact value of this number as a native integer. * * If this number cannot be converted to a native integer without losing precision, an exception is thrown. * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. * * @throws MathException If this number cannot be exactly converted to a native integer. */ abstract public function toInt() : int; /** * Returns an approximation of this number as a floating-point value. * * Note that this method can discard information as the precision of a floating-point value * is inherently limited. * * If the number is greater than the largest representable floating point number, positive infinity is returned. * If the number is less than the smallest representable floating point number, negative infinity is returned. */ abstract public function toFloat() : float; /** * Returns a string representation of this number. * * The output of this method can be parsed by the `of()` factory method; * this will yield an object equal to this one, without any information loss. */ abstract public function __toString() : string; final public function jsonSerialize() : string { return $this->__toString(); } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/BigDecimal.php 0000777 00000051621 15251156632 0022130 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math; use Brick\Math\Exception\DivisionByZeroException; use Brick\Math\Exception\MathException; use Brick\Math\Exception\NegativeNumberException; use Brick\Math\Internal\Calculator; /** * Immutable, arbitrary-precision signed decimal numbers. * * @psalm-immutable */ final class BigDecimal extends BigNumber { /** * The unscaled value of this decimal number. * * This is a string of digits with an optional leading minus sign. * No leading zero must be present. * No leading minus sign must be present if the value is 0. * @readonly * @var string */ private $value; /** * The scale (number of digits after the decimal point) of this decimal number. * * This must be zero or more. * @readonly * @var int */ private $scale; /** * Protected constructor. Use a factory method to obtain an instance. * * @param string $value The unscaled value, validated. * @param int $scale The scale, validated. */ protected function __construct(string $value, int $scale = 0) { $this->value = $value; $this->scale = $scale; } /** * @psalm-pure * @return static */ protected static function from(BigNumber $number) { return $number->toBigDecimal(); } /** * Creates a BigDecimal from an unscaled value and a scale. * * Example: `(12345, 3)` will result in the BigDecimal `12.345`. * * @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger. * @param int $scale The scale of the number, positive or zero. * * @throws \InvalidArgumentException If the scale is negative. * * @psalm-pure */ public static function ofUnscaledValue($value, int $scale = 0) : BigDecimal { if ($scale < 0) { throw new \InvalidArgumentException('The scale cannot be negative.'); } return new BigDecimal((string) BigInteger::of($value), $scale); } /** * Returns a BigDecimal representing zero, with a scale of zero. * * @psalm-pure */ public static function zero() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $zero */ static $zero; if ($zero === null) { $zero = new BigDecimal('0'); } return $zero; } /** * Returns a BigDecimal representing one, with a scale of zero. * * @psalm-pure */ public static function one() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $one */ static $one; if ($one === null) { $one = new BigDecimal('1'); } return $one; } /** * Returns a BigDecimal representing ten, with a scale of zero. * * @psalm-pure */ public static function ten() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $ten */ static $ten; if ($ten === null) { $ten = new BigDecimal('10'); } return $ten; } /** * Returns the sum of this number and the given one. * * The result has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal. * * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. */ public function plus($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0' && $that->scale <= $this->scale) { return $this; } if ($this->value === '0' && $this->scale <= $that->scale) { return $that; } [$a, $b] = $this->scaleValues($this, $that); $value = Calculator::get()->add($a, $b); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($value, $scale); } /** * Returns the difference of this number and the given one. * * The result has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal. * * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. */ public function minus($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0' && $that->scale <= $this->scale) { return $this; } [$a, $b] = $this->scaleValues($this, $that); $value = Calculator::get()->sub($a, $b); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($value, $scale); } /** * Returns the product of this number and the given one. * * The result has a scale of `$this->scale + $that->scale`. * * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal. * * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal. */ public function multipliedBy($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '1' && $that->scale === 0) { return $this; } if ($this->value === '1' && $this->scale === 0) { return $that; } $value = Calculator::get()->mul($this->value, $that->value); $scale = $this->scale + $that->scale; return new BigDecimal($value, $scale); } /** * Returns the result of the division of this number by the given one, at the given scale. * * @param BigNumber|int|float|string $that The divisor. * @param int|null $scale The desired scale, or null to use the scale of this number. * @param mixed $roundingMode An optional rounding mode, defaults to UNNECESSARY. * * @throws \InvalidArgumentException If the scale or rounding mode is invalid. * @throws MathException If the number is invalid, is zero, or rounding was necessary. * @param \Brick\Math\RoundingMode::* $roundingMode */ public function dividedBy($that, ?int $scale = null, $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } if ($scale === null) { $scale = $this->scale; } elseif ($scale < 0) { throw new \InvalidArgumentException('Scale cannot be negative.'); } if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) { return $this; } $p = $this->valueWithMinScale($that->scale + $scale); $q = $that->valueWithMinScale($this->scale - $scale); $result = Calculator::get()->divRound($p, $q, $roundingMode); return new BigDecimal($result, $scale); } /** * Returns the exact result of the division of this number by the given one. * * The scale of the result is automatically calculated to fit all the fraction digits. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero, * or the result yields an infinite number of digits. */ public function exactlyDividedBy($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } [, $b] = $this->scaleValues($this, $that); $d = \rtrim($b, '0'); $scale = \strlen($b) - \strlen($d); $calculator = Calculator::get(); foreach ([5, 2] as $prime) { for (;;) { $lastDigit = (int) $d[-1]; if ($lastDigit % $prime !== 0) { break; } $d = $calculator->divQ($d, (string) $prime); $scale++; } } return $this->dividedBy($that, $scale)->stripTrailingZeros(); } /** * Returns this number exponentiated to the given value. * * The result has a scale of `$this->scale * $exponent`. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigDecimal { if ($exponent === 0) { return BigDecimal::one(); } if ($exponent === 1) { return $this; } if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { throw new \InvalidArgumentException(\sprintf( 'The exponent %d is not in the range 0 to %d.', $exponent, Calculator::MAX_POWER )); } return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent); } /** * Returns the quotient of the division of this number by the given one. * * The quotient has a scale of `0`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function quotient($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); $quotient = Calculator::get()->divQ($p, $q); return new BigDecimal($quotient, 0); } /** * Returns the remainder of the division of this number by the given one. * * The remainder has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function remainder($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); $remainder = Calculator::get()->divR($p, $q); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($remainder, $scale); } /** * Returns the quotient and remainder of the division of this number by the given one. * * The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @return BigDecimal[] An array containing the quotient and the remainder. * * @psalm-return array{BigDecimal, BigDecimal} * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function quotientAndRemainder($that) : array { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); [$quotient, $remainder] = Calculator::get()->divQR($p, $q); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; $quotient = new BigDecimal($quotient, 0); $remainder = new BigDecimal($remainder, $scale); return [$quotient, $remainder]; } /** * Returns the square root of this number, rounded down to the given number of decimals. * * @throws \InvalidArgumentException If the scale is negative. * @throws NegativeNumberException If this number is negative. */ public function sqrt(int $scale) : BigDecimal { if ($scale < 0) { throw new \InvalidArgumentException('Scale cannot be negative.'); } if ($this->value === '0') { return new BigDecimal('0', $scale); } if ($this->value[0] === '-') { throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); } $value = $this->value; $addDigits = 2 * $scale - $this->scale; if ($addDigits > 0) { // add zeros $value .= \str_repeat('0', $addDigits); } elseif ($addDigits < 0) { // trim digits if (-$addDigits >= \strlen($this->value)) { // requesting a scale too low, will always yield a zero result return new BigDecimal('0', $scale); } $value = \substr($value, 0, $addDigits); } $value = Calculator::get()->sqrt($value); return new BigDecimal($value, $scale); } /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the left. */ public function withPointMovedLeft(int $n) : BigDecimal { if ($n === 0) { return $this; } if ($n < 0) { return $this->withPointMovedRight(-$n); } return new BigDecimal($this->value, $this->scale + $n); } /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the right. */ public function withPointMovedRight(int $n) : BigDecimal { if ($n === 0) { return $this; } if ($n < 0) { return $this->withPointMovedLeft(-$n); } $value = $this->value; $scale = $this->scale - $n; if ($scale < 0) { if ($value !== '0') { $value .= \str_repeat('0', -$scale); } $scale = 0; } return new BigDecimal($value, $scale); } /** * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part. */ public function stripTrailingZeros() : BigDecimal { if ($this->scale === 0) { return $this; } $trimmedValue = \rtrim($this->value, '0'); if ($trimmedValue === '') { return BigDecimal::zero(); } $trimmableZeros = \strlen($this->value) - \strlen($trimmedValue); if ($trimmableZeros === 0) { return $this; } if ($trimmableZeros > $this->scale) { $trimmableZeros = $this->scale; } $value = \substr($this->value, 0, -$trimmableZeros); $scale = $this->scale - $trimmableZeros; return new BigDecimal($value, $scale); } /** * Returns the absolute value of this number. */ public function abs() : BigDecimal { return $this->isNegative() ? $this->negated() : $this; } /** * Returns the negated value of this number. */ public function negated() : BigDecimal { return new BigDecimal(Calculator::get()->neg($this->value), $this->scale); } /** * @param \Brick\Math\BigNumber|int|float|string $that */ public function compareTo($that) : int { $that = BigNumber::of($that); if ($that instanceof BigInteger) { $that = $that->toBigDecimal(); } if ($that instanceof BigDecimal) { [$a, $b] = $this->scaleValues($this, $that); return Calculator::get()->cmp($a, $b); } return - $that->compareTo($this); } public function getSign() : int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } public function getUnscaledValue() : BigInteger { return self::newBigInteger($this->value); } public function getScale() : int { return $this->scale; } /** * Returns a string representing the integral part of this decimal number. * * Example: `-123.456` => `-123`. */ public function getIntegralPart() : string { if ($this->scale === 0) { return $this->value; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, 0, -$this->scale); } /** * Returns a string representing the fractional part of this decimal number. * * If the scale is zero, an empty string is returned. * * Examples: `-123.456` => '456', `123` => ''. */ public function getFractionalPart() : string { if ($this->scale === 0) { return ''; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, -$this->scale); } /** * Returns whether this decimal number has a non-zero fractional part. */ public function hasNonZeroFractionalPart() : bool { return $this->getFractionalPart() !== \str_repeat('0', $this->scale); } public function toBigInteger() : BigInteger { $zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0); return self::newBigInteger($zeroScaleDecimal->value); } public function toBigDecimal() : BigDecimal { return $this; } public function toBigRational() : BigRational { $numerator = self::newBigInteger($this->value); $denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale)); return self::newBigRational($numerator, $denominator, false); } /** * @param mixed $roundingMode */ public function toScale(int $scale, $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { if ($scale === $this->scale) { return $this; } return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode); } public function toInt() : int { return $this->toBigInteger()->toInt(); } public function toFloat() : float { return (float) (string) $this; } public function __toString() : string { if ($this->scale === 0) { return $this->value; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale); } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{value: string, scale: int} */ public function __serialize(): array { return ['value' => $this->value, 'scale' => $this->scale]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{value: string, scale: int} $data * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->value)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->value = $data['value']; $this->scale = $data['scale']; } /** * Puts the internal values of the given decimal numbers on the same scale. * * @return array{string, string} The scaled integer values of $x and $y. */ private function scaleValues(BigDecimal $x, BigDecimal $y) : array { $a = $x->value; $b = $y->value; if ($b !== '0' && $x->scale > $y->scale) { $b .= \str_repeat('0', $x->scale - $y->scale); } elseif ($a !== '0' && $x->scale < $y->scale) { $a .= \str_repeat('0', $y->scale - $x->scale); } return [$a, $b]; } private function valueWithMinScale(int $scale) : string { $value = $this->value; if ($this->value !== '0' && $scale > $this->scale) { $value .= \str_repeat('0', $scale - $this->scale); } return $value; } /** * Adds leading zeros if necessary to the unscaled value to represent the full decimal number. */ private function getUnscaledValueWithLeadingZeros() : string { $value = $this->value; $targetLength = $this->scale + 1; $negative = ($value[0] === '-'); $length = \strlen($value); if ($negative) { $length--; } if ($length >= $targetLength) { return $this->value; } if ($negative) { $value = \substr($value, 1); } $value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT); if ($negative) { $value = '-' . $value; } return $value; } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Exception/DivisionByZeroException.php 0000777 00000001340 15251156632 0026675 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Exception; /** * Exception thrown when a division by zero occurs. */ class DivisionByZeroException extends MathException { /** * @psalm-pure */ public static function divisionByZero() : DivisionByZeroException { return new self('Division by zero.'); } /** * @psalm-pure */ public static function modulusMustNotBeZero() : DivisionByZeroException { return new self('The modulus must not be zero.'); } /** * @psalm-pure */ public static function denominatorMustNotBeZero() : DivisionByZeroException { return new self('The denominator of a rational number cannot be zero.'); } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Exception/IntegerOverflowException.php 0000777 00000001016 15251156632 0027077 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Exception; use Brick\Math\BigInteger; /** * Exception thrown when an integer overflow occurs. */ class IntegerOverflowException extends MathException { /** * @psalm-pure */ public static function toIntOverflow(BigInteger $value) : IntegerOverflowException { $message = '%s is out of range %d to %d and cannot be represented as an integer.'; return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX)); } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Exception/RoundingNecessaryException.php 0000777 00000000713 15251156632 0027423 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Exception; /** * Exception thrown when a number cannot be represented at the requested scale without rounding. */ class RoundingNecessaryException extends MathException { /** * @psalm-pure */ public static function roundingNecessary() : RoundingNecessaryException { return new self('Rounding is necessary to represent the result of the operation at this scale.'); } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Exception/NumberFormatException.php 0000777 00000001646 15251156632 0026370 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Exception; /** * Exception thrown when attempting to create a number from a string with an invalid format. */ class NumberFormatException extends MathException { public static function invalidFormat(string $value) : self { return new self(\sprintf('The given value "%s" does not represent a valid number.', $value)); } /** * @param string $char The failing character. * * @psalm-pure */ public static function charNotInAlphabet(string $char) : self { $ord = \ord($char); if ($ord < 32 || $ord > 126) { $char = \strtoupper(\dechex($ord)); if ($ord < 10) { $char = '0' . $char; } } else { $char = '"' . $char . '"'; } return new self(\sprintf('Char %s is not a valid character in the given alphabet.', $char)); } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Exception/NegativeNumberException.php 0000777 00000000370 15251156632 0026673 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Exception; /** * Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number. */ class NegativeNumberException extends MathException { } php-toolkit/DataLiberation/vendor-patched/brick/math/src/Exception/MathException.php 0000777 00000000234 15251156632 0024650 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math\Exception; /** * Base class for all math exceptions. */ class MathException extends \Exception { } php-toolkit/DataLiberation/vendor-patched/brick/math/src/BigRational.php 0000777 00000027054 15251156632 0022346 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math; use Brick\Math\Exception\DivisionByZeroException; use Brick\Math\Exception\MathException; use Brick\Math\Exception\NumberFormatException; use Brick\Math\Exception\RoundingNecessaryException; /** * An arbitrarily large rational number. * * This class is immutable. * * @psalm-immutable */ final class BigRational extends BigNumber { /** * The numerator. * @readonly * @var \Brick\Math\BigInteger */ private $numerator; /** * The denominator. Always strictly positive. * @readonly * @var \Brick\Math\BigInteger */ private $denominator; /** * Protected constructor. Use a factory method to obtain an instance. * * @param BigInteger $numerator The numerator. * @param BigInteger $denominator The denominator. * @param bool $checkDenominator Whether to check the denominator for negative and zero. * * @throws DivisionByZeroException If the denominator is zero. */ protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) { if ($checkDenominator) { if ($denominator->isZero()) { throw DivisionByZeroException::denominatorMustNotBeZero(); } if ($denominator->isNegative()) { $numerator = $numerator->negated(); $denominator = $denominator->negated(); } } $this->numerator = $numerator; $this->denominator = $denominator; } /** * @psalm-pure * @return static */ protected static function from(BigNumber $number) { return $number->toBigRational(); } /** * Creates a BigRational out of a numerator and a denominator. * * If the denominator is negative, the signs of both the numerator and the denominator * will be inverted to ensure that the denominator is always positive. * * @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger. * @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger. * * @throws NumberFormatException If an argument does not represent a valid number. * @throws RoundingNecessaryException If an argument represents a non-integer number. * @throws DivisionByZeroException If the denominator is zero. * * @psalm-pure */ public static function nd($numerator, $denominator): BigRational { $numerator = BigInteger::of($numerator); $denominator = BigInteger::of($denominator); return new BigRational($numerator, $denominator, true); } /** * Returns a BigRational representing zero. * * @psalm-pure */ public static function zero() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $zero */ static $zero; if ($zero === null) { $zero = new BigRational(BigInteger::zero(), BigInteger::one(), false); } return $zero; } /** * Returns a BigRational representing one. * * @psalm-pure */ public static function one() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $one */ static $one; if ($one === null) { $one = new BigRational(BigInteger::one(), BigInteger::one(), false); } return $one; } /** * Returns a BigRational representing ten. * * @psalm-pure */ public static function ten() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $ten */ static $ten; if ($ten === null) { $ten = new BigRational(BigInteger::ten(), BigInteger::one(), false); } return $ten; } public function getNumerator() : BigInteger { return $this->numerator; } public function getDenominator() : BigInteger { return $this->denominator; } /** * Returns the quotient of the division of the numerator by the denominator. */ public function quotient() : BigInteger { return $this->numerator->quotient($this->denominator); } /** * Returns the remainder of the division of the numerator by the denominator. */ public function remainder() : BigInteger { return $this->numerator->remainder($this->denominator); } /** * Returns the quotient and remainder of the division of the numerator by the denominator. * * @return BigInteger[] * * @psalm-return array{BigInteger, BigInteger} */ public function quotientAndRemainder() : array { return $this->numerator->quotientAndRemainder($this->denominator); } /** * Returns the sum of this number and the given one. * * @param BigNumber|int|float|string $that The number to add. * * @throws MathException If the number is not valid. */ public function plus($that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator)); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the difference of this number and the given one. * * @param BigNumber|int|float|string $that The number to subtract. * * @throws MathException If the number is not valid. */ public function minus($that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator)); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the product of this number and the given one. * * @param BigNumber|int|float|string $that The multiplier. * * @throws MathException If the multiplier is not a valid number. */ public function multipliedBy($that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->numerator); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the result of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. * * @throws MathException If the divisor is not a valid number, or is zero. */ public function dividedBy($that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $denominator = $this->denominator->multipliedBy($that->numerator); return new BigRational($numerator, $denominator, true); } /** * Returns this number exponentiated to the given value. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigRational { if ($exponent === 0) { $one = BigInteger::one(); return new BigRational($one, $one, false); } if ($exponent === 1) { return $this; } return new BigRational( $this->numerator->power($exponent), $this->denominator->power($exponent), false ); } /** * Returns the reciprocal of this BigRational. * * The reciprocal has the numerator and denominator swapped. * * @throws DivisionByZeroException If the numerator is zero. */ public function reciprocal() : BigRational { return new BigRational($this->denominator, $this->numerator, true); } /** * Returns the absolute value of this BigRational. */ public function abs() : BigRational { return new BigRational($this->numerator->abs(), $this->denominator, false); } /** * Returns the negated value of this BigRational. */ public function negated() : BigRational { return new BigRational($this->numerator->negated(), $this->denominator, false); } /** * Returns the simplified value of this BigRational. */ public function simplified() : BigRational { $gcd = $this->numerator->gcd($this->denominator); $numerator = $this->numerator->quotient($gcd); $denominator = $this->denominator->quotient($gcd); return new BigRational($numerator, $denominator, false); } /** * @param \Brick\Math\BigNumber|int|float|string $that */ public function compareTo($that) : int { return $this->minus($that)->getSign(); } public function getSign() : int { return $this->numerator->getSign(); } public function toBigInteger() : BigInteger { $simplified = $this->simplified(); if (! $simplified->denominator->isEqualTo(1)) { throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.'); } return $simplified->numerator; } public function toBigDecimal() : BigDecimal { return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator); } public function toBigRational() : BigRational { return $this; } /** * @param mixed $roundingMode */ public function toScale(int $scale, $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode); } public function toInt() : int { return $this->toBigInteger()->toInt(); } public function toFloat() : float { $simplified = $this->simplified(); return $simplified->numerator->toFloat() / $simplified->denominator->toFloat(); } public function __toString() : string { $numerator = (string) $this->numerator; $denominator = (string) $this->denominator; if ($denominator === '1') { return $numerator; } return $this->numerator . '/' . $this->denominator; } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{numerator: BigInteger, denominator: BigInteger} */ public function __serialize(): array { return ['numerator' => $this->numerator, 'denominator' => $this->denominator]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{numerator: BigInteger, denominator: BigInteger} $data * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->numerator)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->numerator = $data['numerator']; $this->denominator = $data['denominator']; } } php-toolkit/DataLiberation/vendor-patched/brick/math/src/BigInteger.php 0000777 00000077446 15251156632 0022204 0 ustar 00 <?php declare(strict_types=1); namespace Brick\Math; use Brick\Math\Exception\DivisionByZeroException; use Brick\Math\Exception\IntegerOverflowException; use Brick\Math\Exception\MathException; use Brick\Math\Exception\NegativeNumberException; use Brick\Math\Exception\NumberFormatException; use Brick\Math\Internal\Calculator; /** * An arbitrary-size integer. * * All methods accepting a number as a parameter accept either a BigInteger instance, * an integer, or a string representing an arbitrary size integer. * * @psalm-immutable */ final class BigInteger extends BigNumber { /** * The value, as a string of digits with optional leading minus sign. * * No leading zeros must be present. * No leading minus sign must be present if the number is zero. * @readonly * @var string */ private $value; /** * Protected constructor. Use a factory method to obtain an instance. * * @param string $value A string of digits, with optional leading minus sign. */ protected function __construct(string $value) { $this->value = $value; } /** * @psalm-pure * @return static */ protected static function from(BigNumber $number) { return $number->toBigInteger(); } /** * Creates a number from a string in a given base. * * The string can optionally be prefixed with the `+` or `-` sign. * * Bases greater than 36 are not supported by this method, as there is no clear consensus on which of the lowercase * or uppercase characters should come first. Instead, this method accepts any base up to 36, and does not * differentiate lowercase and uppercase characters, which are considered equal. * * For bases greater than 36, and/or custom alphabets, use the fromArbitraryBase() method. * * @param string $number The number to convert, in the given base. * @param int $base The base of the number, between 2 and 36. * * @throws NumberFormatException If the number is empty, or contains invalid chars for the given base. * @throws \InvalidArgumentException If the base is out of range. * * @psalm-pure */ public static function fromBase(string $number, int $base) : BigInteger { if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } if ($base < 2 || $base > 36) { throw new \InvalidArgumentException(\sprintf('Base %d is not in range 2 to 36.', $base)); } if ($number[0] === '-') { $sign = '-'; $number = \substr($number, 1); } elseif ($number[0] === '+') { $sign = ''; $number = \substr($number, 1); } else { $sign = ''; } if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } $number = \ltrim($number, '0'); if ($number === '') { // The result will be the same in any base, avoid further calculation. return BigInteger::zero(); } if ($number === '1') { // The result will be the same in any base, avoid further calculation. return new BigInteger($sign . '1'); } $pattern = '/[^' . \substr(Calculator::ALPHABET, 0, $base) . ']/'; if (\preg_match($pattern, \strtolower($number), $matches) === 1) { throw new NumberFormatException(\sprintf('"%s" is not a valid character in base %d.', $matches[0], $base)); } if ($base === 10) { // The number is usable as is, avoid further calculation. return new BigInteger($sign . $number); } $result = Calculator::get()->fromBase($number, $base); return new BigInteger($sign . $result); } /** * Parses a string containing an integer in an arbitrary base, using a custom alphabet. * * Because this method accepts an alphabet with any character, including dash, it does not handle negative numbers. * * @param string $number The number to parse. * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * * @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet. * @throws \InvalidArgumentException If the alphabet does not contain at least 2 chars. * * @psalm-pure */ public static function fromArbitraryBase(string $number, string $alphabet) : BigInteger { if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } $base = \strlen($alphabet); if ($base < 2) { throw new \InvalidArgumentException('The alphabet must contain at least 2 chars.'); } $pattern = '/[^' . \preg_quote($alphabet, '/') . ']/'; if (\preg_match($pattern, $number, $matches) === 1) { throw NumberFormatException::charNotInAlphabet($matches[0]); } $number = Calculator::get()->fromArbitraryBase($number, $alphabet, $base); return new BigInteger($number); } /** * Translates a string of bytes containing the binary representation of a BigInteger into a BigInteger. * * The input string is assumed to be in big-endian byte-order: the most significant byte is in the zeroth element. * * If `$signed` is true, the input is assumed to be in two's-complement representation, and the leading bit is * interpreted as a sign bit. If `$signed` is false, the input is interpreted as an unsigned number, and the * resulting BigInteger will always be positive or zero. * * This method can be used to retrieve a number exported by `toBytes()`, as long as the `$signed` flags match. * * @param string $value The byte string. * @param bool $signed Whether to interpret as a signed number in two's-complement representation with a leading * sign bit. * * @throws NumberFormatException If the string is empty. */ public static function fromBytes(string $value, bool $signed = true) : BigInteger { if ($value === '') { throw new NumberFormatException('The byte string must not be empty.'); } $twosComplement = false; if ($signed) { $x = \ord($value[0]); if (($twosComplement = ($x >= 0x80))) { $value = ~$value; } } $number = self::fromBase(\bin2hex($value), 16); if ($twosComplement) { return $number->plus(1)->negated(); } return $number; } /** * Generates a pseudo-random number in the range 0 to 2^numBits - 1. * * Using the default random bytes generator, this method is suitable for cryptographic use. * * @psalm-param (callable(int): string)|null $randomBytesGenerator * * @param int $numBits The number of bits. * @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, and returns a * string of random bytes of the given length. Defaults to the * `random_bytes()` function. * * @throws \InvalidArgumentException If $numBits is negative. */ public static function randomBits(int $numBits, ?callable $randomBytesGenerator = null) : BigInteger { if ($numBits < 0) { throw new \InvalidArgumentException('The number of bits cannot be negative.'); } if ($numBits === 0) { return BigInteger::zero(); } if ($randomBytesGenerator === null) { $randomBytesGenerator = \Closure::fromCallable('random_bytes'); } /** @var int<1, max> $byteLength */ $byteLength = \intdiv($numBits - 1, 8) + 1; $extraBits = ($byteLength * 8 - $numBits); $bitmask = \chr(0xFF >> $extraBits); $randomBytes = $randomBytesGenerator($byteLength); $randomBytes[0] = $randomBytes[0] & $bitmask; return self::fromBytes($randomBytes, false); } /** * Generates a pseudo-random number between `$min` and `$max`. * * Using the default random bytes generator, this method is suitable for cryptographic use. * * @psalm-param (callable(int): string)|null $randomBytesGenerator * * @param BigNumber|int|float|string $min The lower bound. Must be convertible to a BigInteger. * @param BigNumber|int|float|string $max The upper bound. Must be convertible to a BigInteger. * @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, * and returns a string of random bytes of the given length. * Defaults to the `random_bytes()` function. * * @throws MathException If one of the parameters cannot be converted to a BigInteger, * or `$min` is greater than `$max`. */ public static function randomRange( $min, $max, ?callable $randomBytesGenerator = null ) : BigInteger { $min = BigInteger::of($min); $max = BigInteger::of($max); if ($min->isGreaterThan($max)) { throw new MathException('$min cannot be greater than $max.'); } if ($min->isEqualTo($max)) { return $min; } $diff = $max->minus($min); $bitLength = $diff->getBitLength(); // try until the number is in range (50% to 100% chance of success) do { $randomNumber = self::randomBits($bitLength, $randomBytesGenerator); } while ($randomNumber->isGreaterThan($diff)); return $randomNumber->plus($min); } /** * Returns a BigInteger representing zero. * * @psalm-pure */ public static function zero() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $zero */ static $zero; if ($zero === null) { $zero = new BigInteger('0'); } return $zero; } /** * Returns a BigInteger representing one. * * @psalm-pure */ public static function one() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $one */ static $one; if ($one === null) { $one = new BigInteger('1'); } return $one; } /** * Returns a BigInteger representing ten. * * @psalm-pure */ public static function ten() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $ten */ static $ten; if ($ten === null) { $ten = new BigInteger('10'); } return $ten; } public static function gcdMultiple(BigInteger $a, BigInteger ...$n): BigInteger { $result = $a; foreach ($n as $next) { $result = $result->gcd($next); if ($result->isEqualTo(1)) { return $result; } } return $result; } /** * Returns the sum of this number and the given one. * * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigInteger. * * @throws MathException If the number is not valid, or is not convertible to a BigInteger. */ public function plus($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { return $this; } if ($this->value === '0') { return $that; } $value = Calculator::get()->add($this->value, $that->value); return new BigInteger($value); } /** * Returns the difference of this number and the given one. * * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigInteger. * * @throws MathException If the number is not valid, or is not convertible to a BigInteger. */ public function minus($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { return $this; } $value = Calculator::get()->sub($this->value, $that->value); return new BigInteger($value); } /** * Returns the product of this number and the given one. * * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigInteger. * * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigInteger. */ public function multipliedBy($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($this->value === '1') { return $that; } $value = Calculator::get()->mul($this->value, $that->value); return new BigInteger($value); } /** * Returns the result of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * @param mixed $roundingMode An optional rounding mode, defaults to UNNECESSARY. * * @throws MathException If the divisor is not a valid number, is not convertible to a BigInteger, is zero, * or RoundingMode::UNNECESSARY is used and the remainder is not zero. * @param \Brick\Math\RoundingMode::* $roundingMode */ public function dividedBy($that, $roundingMode = RoundingMode::UNNECESSARY) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $result = Calculator::get()->divRound($this->value, $that->value, $roundingMode); return new BigInteger($result); } /** * Returns this number exponentiated to the given value. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigInteger { if ($exponent === 0) { return BigInteger::one(); } if ($exponent === 1) { return $this; } if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { throw new \InvalidArgumentException(\sprintf( 'The exponent %d is not in the range 0 to %d.', $exponent, Calculator::MAX_POWER )); } return new BigInteger(Calculator::get()->pow($this->value, $exponent)); } /** * Returns the quotient of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @throws DivisionByZeroException If the divisor is zero. */ public function quotient($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $quotient = Calculator::get()->divQ($this->value, $that->value); return new BigInteger($quotient); } /** * Returns the remainder of the division of this number by the given one. * * The remainder, when non-zero, has the same sign as the dividend. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @throws DivisionByZeroException If the divisor is zero. */ public function remainder($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return BigInteger::zero(); } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $remainder = Calculator::get()->divR($this->value, $that->value); return new BigInteger($remainder); } /** * Returns the quotient and remainder of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @return BigInteger[] An array containing the quotient and the remainder. * * @psalm-return array{BigInteger, BigInteger} * * @throws DivisionByZeroException If the divisor is zero. */ public function quotientAndRemainder($that) : array { $that = BigInteger::of($that); if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } [$quotient, $remainder] = Calculator::get()->divQR($this->value, $that->value); return [ new BigInteger($quotient), new BigInteger($remainder) ]; } /** * Returns the modulo of this number and the given one. * * The modulo operation yields the same result as the remainder operation when both operands are of the same sign, * and may differ when signs are different. * * The result of the modulo operation, when non-zero, has the same sign as the divisor. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @throws DivisionByZeroException If the divisor is zero. */ public function mod($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { throw DivisionByZeroException::modulusMustNotBeZero(); } $value = Calculator::get()->mod($this->value, $that->value); return new BigInteger($value); } /** * Returns the modular multiplicative inverse of this BigInteger modulo $m. * * @throws DivisionByZeroException If $m is zero. * @throws NegativeNumberException If $m is negative. * @throws MathException If this BigInteger has no multiplicative inverse mod m (that is, this BigInteger * is not relatively prime to m). */ public function modInverse(BigInteger $m) : BigInteger { if ($m->value === '0') { throw DivisionByZeroException::modulusMustNotBeZero(); } if ($m->isNegative()) { throw new NegativeNumberException('Modulus must not be negative.'); } if ($m->value === '1') { return BigInteger::zero(); } $value = Calculator::get()->modInverse($this->value, $m->value); if ($value === null) { throw new MathException('Unable to compute the modInverse for the given modulus.'); } return new BigInteger($value); } /** * Returns this number raised into power with modulo. * * This operation only works on positive numbers. * * @param BigNumber|int|float|string $exp The exponent. Must be positive or zero. * @param BigNumber|int|float|string $mod The modulus. Must be strictly positive. * * @throws NegativeNumberException If any of the operands is negative. * @throws DivisionByZeroException If the modulus is zero. */ public function modPow($exp, $mod) : BigInteger { $exp = BigInteger::of($exp); $mod = BigInteger::of($mod); if ($this->isNegative() || $exp->isNegative() || $mod->isNegative()) { throw new NegativeNumberException('The operands cannot be negative.'); } if ($mod->isZero()) { throw DivisionByZeroException::modulusMustNotBeZero(); } $result = Calculator::get()->modPow($this->value, $exp->value, $mod->value); return new BigInteger($result); } /** * Returns the greatest common divisor of this number and the given one. * * The GCD is always positive, unless both operands are zero, in which case it is zero. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. */ public function gcd($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0' && $this->value[0] !== '-') { return $this; } if ($this->value === '0' && $that->value[0] !== '-') { return $that; } $value = Calculator::get()->gcd($this->value, $that->value); return new BigInteger($value); } /** * Returns the integer square root number of this number, rounded down. * * The result is the largest x such that x² ≤ n. * * @throws NegativeNumberException If this number is negative. */ public function sqrt() : BigInteger { if ($this->value[0] === '-') { throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); } $value = Calculator::get()->sqrt($this->value); return new BigInteger($value); } /** * Returns the absolute value of this number. */ public function abs() : BigInteger { return $this->isNegative() ? $this->negated() : $this; } /** * Returns the inverse of this number. */ public function negated() : BigInteger { return new BigInteger(Calculator::get()->neg($this->value)); } /** * Returns the integer bitwise-and combined with another integer. * * This method returns a negative BigInteger if and only if both operands are negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. */ public function and($that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->and($this->value, $that->value)); } /** * Returns the integer bitwise-or combined with another integer. * * This method returns a negative BigInteger if and only if either of the operands is negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. */ public function or($that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->or($this->value, $that->value)); } /** * Returns the integer bitwise-xor combined with another integer. * * This method returns a negative BigInteger if and only if exactly one of the operands is negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. */ public function xor($that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->xor($this->value, $that->value)); } /** * Returns the bitwise-not of this BigInteger. */ public function not() : BigInteger { return $this->negated()->minus(1); } /** * Returns the integer left shifted by a given number of bits. */ public function shiftedLeft(int $distance) : BigInteger { if ($distance === 0) { return $this; } if ($distance < 0) { return $this->shiftedRight(- $distance); } return $this->multipliedBy(BigInteger::of(2)->power($distance)); } /** * Returns the integer right shifted by a given number of bits. */ public function shiftedRight(int $distance) : BigInteger { if ($distance === 0) { return $this; } if ($distance < 0) { return $this->shiftedLeft(- $distance); } $operand = BigInteger::of(2)->power($distance); if ($this->isPositiveOrZero()) { return $this->quotient($operand); } return $this->dividedBy($operand, RoundingMode::UP); } /** * Returns the number of bits in the minimal two's-complement representation of this BigInteger, excluding a sign bit. * * For positive BigIntegers, this is equivalent to the number of bits in the ordinary binary representation. * Computes (ceil(log2(this < 0 ? -this : this+1))). */ public function getBitLength() : int { if ($this->value === '0') { return 0; } if ($this->isNegative()) { return $this->abs()->minus(1)->getBitLength(); } return \strlen($this->toBase(2)); } /** * Returns the index of the rightmost (lowest-order) one bit in this BigInteger. * * Returns -1 if this BigInteger contains no one bits. */ public function getLowestSetBit() : int { $n = $this; $bitLength = $this->getBitLength(); for ($i = 0; $i <= $bitLength; $i++) { if ($n->isOdd()) { return $i; } $n = $n->shiftedRight(1); } return -1; } /** * Returns whether this number is even. */ public function isEven() : bool { return \in_array($this->value[-1], ['0', '2', '4', '6', '8'], true); } /** * Returns whether this number is odd. */ public function isOdd() : bool { return \in_array($this->value[-1], ['1', '3', '5', '7', '9'], true); } /** * Returns true if and only if the designated bit is set. * * Computes ((this & (1<<n)) != 0). * * @param int $n The bit to test, 0-based. * * @throws \InvalidArgumentException If the bit to test is negative. */ public function testBit(int $n) : bool { if ($n < 0) { throw new \InvalidArgumentException('The bit to test cannot be negative.'); } return $this->shiftedRight($n)->isOdd(); } /** * @param \Brick\Math\BigNumber|int|float|string $that */ public function compareTo($that) : int { $that = BigNumber::of($that); if ($that instanceof BigInteger) { return Calculator::get()->cmp($this->value, $that->value); } return - $that->compareTo($this); } public function getSign() : int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } public function toBigInteger() : BigInteger { return $this; } public function toBigDecimal() : BigDecimal { return self::newBigDecimal($this->value); } public function toBigRational() : BigRational { return self::newBigRational($this, BigInteger::one(), false); } /** * @param mixed $roundingMode */ public function toScale(int $scale, $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { return $this->toBigDecimal()->toScale($scale, $roundingMode); } public function toInt() : int { $intValue = (int) $this->value; if ($this->value !== (string) $intValue) { throw IntegerOverflowException::toIntOverflow($this); } return $intValue; } public function toFloat() : float { return (float) $this->value; } /** * Returns a string representation of this number in the given base. * * The output will always be lowercase for bases greater than 10. * * @throws \InvalidArgumentException If the base is out of range. */ public function toBase(int $base) : string { if ($base === 10) { return $this->value; } if ($base < 2 || $base > 36) { throw new \InvalidArgumentException(\sprintf('Base %d is out of range [2, 36]', $base)); } return Calculator::get()->toBase($this->value, $base); } /** * Returns a string representation of this number in an arbitrary base with a custom alphabet. * * Because this method accepts an alphabet with any character, including dash, it does not handle negative numbers; * a NegativeNumberException will be thrown when attempting to call this method on a negative number. * * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * * @throws NegativeNumberException If this number is negative. * @throws \InvalidArgumentException If the given alphabet does not contain at least 2 chars. */ public function toArbitraryBase(string $alphabet) : string { $base = \strlen($alphabet); if ($base < 2) { throw new \InvalidArgumentException('The alphabet must contain at least 2 chars.'); } if ($this->value[0] === '-') { throw new NegativeNumberException(__FUNCTION__ . '() does not support negative numbers.'); } return Calculator::get()->toArbitraryBase($this->value, $alphabet, $base); } /** * Returns a string of bytes containing the binary representation of this BigInteger. * * The string is in big-endian byte-order: the most significant byte is in the zeroth element. * * If `$signed` is true, the output will be in two's-complement representation, and a sign bit will be prepended to * the output. If `$signed` is false, no sign bit will be prepended, and this method will throw an exception if the * number is negative. * * The string will contain the minimum number of bytes required to represent this BigInteger, including a sign bit * if `$signed` is true. * * This representation is compatible with the `fromBytes()` factory method, as long as the `$signed` flags match. * * @param bool $signed Whether to output a signed number in two's-complement representation with a leading sign bit. * * @throws NegativeNumberException If $signed is false, and the number is negative. */ public function toBytes(bool $signed = true) : string { if (! $signed && $this->isNegative()) { throw new NegativeNumberException('Cannot convert a negative number to a byte string when $signed is false.'); } $hex = $this->abs()->toBase(16); if (\strlen($hex) % 2 !== 0) { $hex = '0' . $hex; } $baseHexLength = \strlen($hex); if ($signed) { if ($this->isNegative()) { $bin = \hex2bin($hex); assert($bin !== false); $hex = \bin2hex(~$bin); $hex = self::fromBase($hex, 16)->plus(1)->toBase(16); $hexLength = \strlen($hex); if ($hexLength < $baseHexLength) { $hex = \str_repeat('0', $baseHexLength - $hexLength) . $hex; } if ($hex[0] < '8') { $hex = 'FF' . $hex; } } else { if ($hex[0] >= '8') { $hex = '00' . $hex; } } } return \hex2bin($hex); } public function __toString() : string { return $this->value; } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{value: string} */ public function __serialize(): array { return ['value' => $this->value]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{value: string} $data * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->value)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->value = $data['value']; } } php-toolkit/DataLiberation/vendor-patched/brick/math/composer.json 0000777 00000001456 15251156632 0021373 0 ustar 00 { "name": "brick/math", "description": "Arbitrary-precision arithmetic library", "type": "library", "keywords": [ "Brick", "Math", "Mathematics", "Arbitrary-precision", "Arithmetic", "BigInteger", "BigDecimal", "BigRational", "BigNumber", "Bignum", "Decimal", "Rational", "Integer" ], "license": "MIT", "require": { "php": "^7.2" }, "require-dev": { "phpunit/phpunit": "^10.1", "php-coveralls/php-coveralls": "^2.2", "vimeo/psalm": "5.25.0" }, "autoload": { "psr-4": { "Brick\\Math\\": "src/" } }, "autoload-dev": { "psr-4": { "Brick\\Math\\Tests\\": "tests/" } } } php-toolkit/DataLiberation/vendor-patched/brick/math/LICENSE 0000777 00000002101 15251156632 0017642 0 ustar 00 The MIT License (MIT) Copyright (c) 2013-present Benjamin Morel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-toolkit/DataLiberation/URL/public-suffix-list.php 0000777 00000121234 15251156632 0016624 0 ustar 00 <?php /** * Public suffix list for detecting URLs with known domains within text. * This file is automatically generated by regenerate_public_suffix_list.php. * Do not edit it directly. * * @package WordPress\DataLiberation\URL */ return array( 'ac' => 1, 'ad' => 1, 'ae' => 1, 'aero' => 1, 'af' => 1, 'ag' => 1, 'ai' => 1, 'al' => 1, 'am' => 1, 'ao' => 1, 'aq' => 1, 'ar' => 1, 'arpa' => 1, 'as' => 1, 'asia' => 1, 'at' => 1, 'au' => 1, 'aw' => 1, 'ax' => 1, 'az' => 1, 'ba' => 1, 'bb' => 1, 'be' => 1, 'bf' => 1, 'bg' => 1, 'bh' => 1, 'bi' => 1, 'biz' => 1, 'bj' => 1, 'bm' => 1, 'bn' => 1, 'bo' => 1, 'br' => 1, 'bs' => 1, 'bt' => 1, 'bv' => 1, 'bw' => 1, 'by' => 1, 'bz' => 1, 'ca' => 1, 'cat' => 1, 'cc' => 1, 'cd' => 1, 'cf' => 1, 'cg' => 1, 'ch' => 1, 'ci' => 1, 'cl' => 1, 'cm' => 1, 'cn' => 1, 'co' => 1, 'com' => 1, 'coop' => 1, 'cr' => 1, 'cu' => 1, 'cv' => 1, 'cw' => 1, 'cx' => 1, 'cy' => 1, 'cz' => 1, 'de' => 1, 'dj' => 1, 'dk' => 1, 'dm' => 1, 'do' => 1, 'dz' => 1, 'ec' => 1, 'edu' => 1, 'ee' => 1, 'eg' => 1, 'es' => 1, 'et' => 1, 'eu' => 1, 'fi' => 1, 'fj' => 1, 'fm' => 1, 'fo' => 1, 'fr' => 1, 'ga' => 1, 'gb' => 1, 'gd' => 1, 'ge' => 1, 'gf' => 1, 'gg' => 1, 'gh' => 1, 'gi' => 1, 'gl' => 1, 'gm' => 1, 'gn' => 1, 'gov' => 1, 'gp' => 1, 'gq' => 1, 'gr' => 1, 'gs' => 1, 'gt' => 1, 'gu' => 1, 'gw' => 1, 'gy' => 1, 'hk' => 1, 'hm' => 1, 'hn' => 1, 'hr' => 1, 'ht' => 1, 'hu' => 1, 'id' => 1, 'ie' => 1, 'il' => 1, 'ישראל' => 1, 'im' => 1, 'in' => 1, 'info' => 1, 'int' => 1, 'io' => 1, 'iq' => 1, 'ir' => 1, 'is' => 1, 'it' => 1, 'je' => 1, 'jo' => 1, 'jobs' => 1, 'jp' => 1, 'ke' => 1, 'kg' => 1, 'ki' => 1, 'km' => 1, 'kn' => 1, 'kp' => 1, 'kr' => 1, 'kw' => 1, 'ky' => 1, 'kz' => 1, 'la' => 1, 'lb' => 1, 'lc' => 1, 'li' => 1, 'lk' => 1, 'lr' => 1, 'ls' => 1, 'lt' => 1, 'lu' => 1, 'lv' => 1, 'ly' => 1, 'ma' => 1, 'mc' => 1, 'md' => 1, 'me' => 1, 'mg' => 1, 'mh' => 1, 'mil' => 1, 'mk' => 1, 'ml' => 1, 'mn' => 1, 'mo' => 1, 'mobi' => 1, 'mp' => 1, 'mq' => 1, 'mr' => 1, 'ms' => 1, 'mt' => 1, 'mu' => 1, 'museum' => 1, 'mv' => 1, 'mw' => 1, 'mx' => 1, 'my' => 1, 'mz' => 1, 'na' => 1, 'name' => 1, 'nc' => 1, 'ne' => 1, 'net' => 1, 'nf' => 1, 'ng' => 1, 'ni' => 1, 'nl' => 1, 'no' => 1, 'nr' => 1, 'nu' => 1, 'nz' => 1, 'om' => 1, 'onion' => 1, 'org' => 1, 'pa' => 1, 'pe' => 1, 'pf' => 1, 'ph' => 1, 'pk' => 1, 'pl' => 1, 'pm' => 1, 'pn' => 1, 'post' => 1, 'pr' => 1, 'pro' => 1, 'ps' => 1, 'pt' => 1, 'pw' => 1, 'py' => 1, 'qa' => 1, 're' => 1, 'ro' => 1, 'rs' => 1, 'ru' => 1, 'rw' => 1, 'sa' => 1, 'sb' => 1, 'sc' => 1, 'sd' => 1, 'se' => 1, 'sg' => 1, 'sh' => 1, 'si' => 1, 'sj' => 1, 'sk' => 1, 'sl' => 1, 'sm' => 1, 'sn' => 1, 'so' => 1, 'sr' => 1, 'ss' => 1, 'st' => 1, 'su' => 1, 'sv' => 1, 'sx' => 1, 'sy' => 1, 'sz' => 1, 'tc' => 1, 'td' => 1, 'tel' => 1, 'tf' => 1, 'tg' => 1, 'th' => 1, 'tj' => 1, 'tk' => 1, 'tl' => 1, 'tm' => 1, 'tn' => 1, 'to' => 1, 'tr' => 1, 'tt' => 1, 'tv' => 1, 'tw' => 1, 'tz' => 1, 'ua' => 1, 'ug' => 1, 'uk' => 1, 'us' => 1, 'uy' => 1, 'uz' => 1, 'va' => 1, 'vc' => 1, 've' => 1, 'vg' => 1, 'vi' => 1, 'vn' => 1, 'vu' => 1, 'wf' => 1, 'ws' => 1, 'yt' => 1, 'امارات' => 1, 'հայ' => 1, 'বাংলা' => 1, 'бг' => 1, 'البحرين' => 1, 'бел' => 1, '中国' => 1, '中國' => 1, 'الجزائر' => 1, 'مصر' => 1, 'ею' => 1, 'ευ' => 1, 'موريتانيا' => 1, 'გე' => 1, 'ελ' => 1, '香港' => 1, 'ಭಾರತ' => 1, 'ଭାରତ' => 1, 'ভাৰত' => 1, 'भारतम्' => 1, 'भारोत' => 1, 'ڀارت' => 1, 'ഭാരതം' => 1, 'भारत' => 1, 'بارت' => 1, 'بھارت' => 1, 'భారత్' => 1, 'ભારત' => 1, 'ਭਾਰਤ' => 1, 'ভারত' => 1, 'இந்தியா' => 1, 'ایران' => 1, 'ايران' => 1, 'عراق' => 1, 'الاردن' => 1, '한국' => 1, 'қаз' => 1, 'ລາວ' => 1, 'ලංකා' => 1, 'இலங்கை' => 1, 'المغرب' => 1, 'мкд' => 1, 'мон' => 1, '澳門' => 1, '澳门' => 1, 'مليسيا' => 1, 'عمان' => 1, 'پاکستان' => 1, 'پاكستان' => 1, 'فلسطين' => 1, 'срб' => 1, 'рф' => 1, 'قطر' => 1, 'السعودية' => 1, 'السعودیة' => 1, 'السعودیۃ' => 1, 'السعوديه' => 1, 'سودان' => 1, '新加坡' => 1, 'சிங்கப்பூர்' => 1, 'سورية' => 1, 'سوريا' => 1, 'ไทย' => 1, 'تونس' => 1, '台灣' => 1, '台湾' => 1, '臺灣' => 1, 'укр' => 1, 'اليمن' => 1, 'xxx' => 1, 'ye' => 1, 'zm' => 1, 'zw' => 1, 'aaa' => 1, 'aarp' => 1, 'abb' => 1, 'abbott' => 1, 'abbvie' => 1, 'abc' => 1, 'able' => 1, 'abogado' => 1, 'abudhabi' => 1, 'academy' => 1, 'accenture' => 1, 'accountant' => 1, 'accountants' => 1, 'aco' => 1, 'actor' => 1, 'ads' => 1, 'adult' => 1, 'aeg' => 1, 'aetna' => 1, 'afl' => 1, 'africa' => 1, 'agakhan' => 1, 'agency' => 1, 'aig' => 1, 'airbus' => 1, 'airforce' => 1, 'airtel' => 1, 'akdn' => 1, 'alibaba' => 1, 'alipay' => 1, 'allfinanz' => 1, 'allstate' => 1, 'ally' => 1, 'alsace' => 1, 'alstom' => 1, 'amazon' => 1, 'americanexpress' => 1, 'americanfamily' => 1, 'amex' => 1, 'amfam' => 1, 'amica' => 1, 'amsterdam' => 1, 'analytics' => 1, 'android' => 1, 'anquan' => 1, 'anz' => 1, 'aol' => 1, 'apartments' => 1, 'app' => 1, 'apple' => 1, 'aquarelle' => 1, 'arab' => 1, 'aramco' => 1, 'archi' => 1, 'army' => 1, 'art' => 1, 'arte' => 1, 'asda' => 1, 'associates' => 1, 'athleta' => 1, 'attorney' => 1, 'auction' => 1, 'audi' => 1, 'audible' => 1, 'audio' => 1, 'auspost' => 1, 'author' => 1, 'auto' => 1, 'autos' => 1, 'aws' => 1, 'axa' => 1, 'azure' => 1, 'baby' => 1, 'baidu' => 1, 'banamex' => 1, 'band' => 1, 'bank' => 1, 'bar' => 1, 'barcelona' => 1, 'barclaycard' => 1, 'barclays' => 1, 'barefoot' => 1, 'bargains' => 1, 'baseball' => 1, 'basketball' => 1, 'bauhaus' => 1, 'bayern' => 1, 'bbc' => 1, 'bbt' => 1, 'bbva' => 1, 'bcg' => 1, 'bcn' => 1, 'beats' => 1, 'beauty' => 1, 'beer' => 1, 'bentley' => 1, 'berlin' => 1, 'best' => 1, 'bestbuy' => 1, 'bet' => 1, 'bharti' => 1, 'bible' => 1, 'bid' => 1, 'bike' => 1, 'bing' => 1, 'bingo' => 1, 'bio' => 1, 'black' => 1, 'blackfriday' => 1, 'blockbuster' => 1, 'blog' => 1, 'bloomberg' => 1, 'blue' => 1, 'bms' => 1, 'bmw' => 1, 'bnpparibas' => 1, 'boats' => 1, 'boehringer' => 1, 'bofa' => 1, 'bom' => 1, 'bond' => 1, 'boo' => 1, 'book' => 1, 'booking' => 1, 'bosch' => 1, 'bostik' => 1, 'boston' => 1, 'bot' => 1, 'boutique' => 1, 'box' => 1, 'bradesco' => 1, 'bridgestone' => 1, 'broadway' => 1, 'broker' => 1, 'brother' => 1, 'brussels' => 1, 'build' => 1, 'builders' => 1, 'business' => 1, 'buy' => 1, 'buzz' => 1, 'bzh' => 1, 'cab' => 1, 'cafe' => 1, 'cal' => 1, 'call' => 1, 'calvinklein' => 1, 'cam' => 1, 'camera' => 1, 'camp' => 1, 'canon' => 1, 'capetown' => 1, 'capital' => 1, 'capitalone' => 1, 'car' => 1, 'caravan' => 1, 'cards' => 1, 'care' => 1, 'career' => 1, 'careers' => 1, 'cars' => 1, 'casa' => 1, 'case' => 1, 'cash' => 1, 'casino' => 1, 'catering' => 1, 'catholic' => 1, 'cba' => 1, 'cbn' => 1, 'cbre' => 1, 'center' => 1, 'ceo' => 1, 'cern' => 1, 'cfa' => 1, 'cfd' => 1, 'chanel' => 1, 'channel' => 1, 'charity' => 1, 'chase' => 1, 'chat' => 1, 'cheap' => 1, 'chintai' => 1, 'christmas' => 1, 'chrome' => 1, 'church' => 1, 'cipriani' => 1, 'circle' => 1, 'cisco' => 1, 'citadel' => 1, 'citi' => 1, 'citic' => 1, 'city' => 1, 'claims' => 1, 'cleaning' => 1, 'click' => 1, 'clinic' => 1, 'clinique' => 1, 'clothing' => 1, 'cloud' => 1, 'club' => 1, 'clubmed' => 1, 'coach' => 1, 'codes' => 1, 'coffee' => 1, 'college' => 1, 'cologne' => 1, 'commbank' => 1, 'community' => 1, 'company' => 1, 'compare' => 1, 'computer' => 1, 'comsec' => 1, 'condos' => 1, 'construction' => 1, 'consulting' => 1, 'contact' => 1, 'contractors' => 1, 'cooking' => 1, 'cool' => 1, 'corsica' => 1, 'country' => 1, 'coupon' => 1, 'coupons' => 1, 'courses' => 1, 'cpa' => 1, 'credit' => 1, 'creditcard' => 1, 'creditunion' => 1, 'cricket' => 1, 'crown' => 1, 'crs' => 1, 'cruise' => 1, 'cruises' => 1, 'cuisinella' => 1, 'cymru' => 1, 'cyou' => 1, 'dabur' => 1, 'dad' => 1, 'dance' => 1, 'data' => 1, 'date' => 1, 'dating' => 1, 'datsun' => 1, 'day' => 1, 'dclk' => 1, 'dds' => 1, 'deal' => 1, 'dealer' => 1, 'deals' => 1, 'degree' => 1, 'delivery' => 1, 'dell' => 1, 'deloitte' => 1, 'delta' => 1, 'democrat' => 1, 'dental' => 1, 'dentist' => 1, 'desi' => 1, 'design' => 1, 'dev' => 1, 'dhl' => 1, 'diamonds' => 1, 'diet' => 1, 'digital' => 1, 'direct' => 1, 'directory' => 1, 'discount' => 1, 'discover' => 1, 'dish' => 1, 'diy' => 1, 'dnp' => 1, 'docs' => 1, 'doctor' => 1, 'dog' => 1, 'domains' => 1, 'dot' => 1, 'download' => 1, 'drive' => 1, 'dtv' => 1, 'dubai' => 1, 'dunlop' => 1, 'dupont' => 1, 'durban' => 1, 'dvag' => 1, 'dvr' => 1, 'earth' => 1, 'eat' => 1, 'eco' => 1, 'edeka' => 1, 'education' => 1, 'email' => 1, 'emerck' => 1, 'energy' => 1, 'engineer' => 1, 'engineering' => 1, 'enterprises' => 1, 'epson' => 1, 'equipment' => 1, 'ericsson' => 1, 'erni' => 1, 'esq' => 1, 'estate' => 1, 'eurovision' => 1, 'eus' => 1, 'events' => 1, 'exchange' => 1, 'expert' => 1, 'exposed' => 1, 'express' => 1, 'extraspace' => 1, 'fage' => 1, 'fail' => 1, 'fairwinds' => 1, 'faith' => 1, 'family' => 1, 'fan' => 1, 'fans' => 1, 'farm' => 1, 'farmers' => 1, 'fashion' => 1, 'fast' => 1, 'fedex' => 1, 'feedback' => 1, 'ferrari' => 1, 'ferrero' => 1, 'fidelity' => 1, 'fido' => 1, 'film' => 1, 'final' => 1, 'finance' => 1, 'financial' => 1, 'fire' => 1, 'firestone' => 1, 'firmdale' => 1, 'fish' => 1, 'fishing' => 1, 'fit' => 1, 'fitness' => 1, 'flickr' => 1, 'flights' => 1, 'flir' => 1, 'florist' => 1, 'flowers' => 1, 'fly' => 1, 'foo' => 1, 'food' => 1, 'football' => 1, 'ford' => 1, 'forex' => 1, 'forsale' => 1, 'forum' => 1, 'foundation' => 1, 'fox' => 1, 'free' => 1, 'fresenius' => 1, 'frl' => 1, 'frogans' => 1, 'frontier' => 1, 'ftr' => 1, 'fujitsu' => 1, 'fun' => 1, 'fund' => 1, 'furniture' => 1, 'futbol' => 1, 'fyi' => 1, 'gal' => 1, 'gallery' => 1, 'gallo' => 1, 'gallup' => 1, 'game' => 1, 'games' => 1, 'gap' => 1, 'garden' => 1, 'gay' => 1, 'gbiz' => 1, 'gdn' => 1, 'gea' => 1, 'gent' => 1, 'genting' => 1, 'george' => 1, 'ggee' => 1, 'gift' => 1, 'gifts' => 1, 'gives' => 1, 'giving' => 1, 'glass' => 1, 'gle' => 1, 'global' => 1, 'globo' => 1, 'gmail' => 1, 'gmbh' => 1, 'gmo' => 1, 'gmx' => 1, 'godaddy' => 1, 'gold' => 1, 'goldpoint' => 1, 'golf' => 1, 'goo' => 1, 'goodyear' => 1, 'goog' => 1, 'google' => 1, 'gop' => 1, 'got' => 1, 'grainger' => 1, 'graphics' => 1, 'gratis' => 1, 'green' => 1, 'gripe' => 1, 'grocery' => 1, 'group' => 1, 'gucci' => 1, 'guge' => 1, 'guide' => 1, 'guitars' => 1, 'guru' => 1, 'hair' => 1, 'hamburg' => 1, 'hangout' => 1, 'haus' => 1, 'hbo' => 1, 'hdfc' => 1, 'hdfcbank' => 1, 'health' => 1, 'healthcare' => 1, 'help' => 1, 'helsinki' => 1, 'here' => 1, 'hermes' => 1, 'hiphop' => 1, 'hisamitsu' => 1, 'hitachi' => 1, 'hiv' => 1, 'hkt' => 1, 'hockey' => 1, 'holdings' => 1, 'holiday' => 1, 'homedepot' => 1, 'homegoods' => 1, 'homes' => 1, 'homesense' => 1, 'honda' => 1, 'horse' => 1, 'hospital' => 1, 'host' => 1, 'hosting' => 1, 'hot' => 1, 'hotels' => 1, 'hotmail' => 1, 'house' => 1, 'how' => 1, 'hsbc' => 1, 'hughes' => 1, 'hyatt' => 1, 'hyundai' => 1, 'ibm' => 1, 'icbc' => 1, 'ice' => 1, 'icu' => 1, 'ieee' => 1, 'ifm' => 1, 'ikano' => 1, 'imamat' => 1, 'imdb' => 1, 'immo' => 1, 'immobilien' => 1, 'inc' => 1, 'industries' => 1, 'infiniti' => 1, 'ing' => 1, 'ink' => 1, 'institute' => 1, 'insurance' => 1, 'insure' => 1, 'international' => 1, 'intuit' => 1, 'investments' => 1, 'ipiranga' => 1, 'irish' => 1, 'ismaili' => 1, 'ist' => 1, 'istanbul' => 1, 'itau' => 1, 'itv' => 1, 'jaguar' => 1, 'java' => 1, 'jcb' => 1, 'jeep' => 1, 'jetzt' => 1, 'jewelry' => 1, 'jio' => 1, 'jll' => 1, 'jmp' => 1, 'jnj' => 1, 'joburg' => 1, 'jot' => 1, 'joy' => 1, 'jpmorgan' => 1, 'jprs' => 1, 'juegos' => 1, 'juniper' => 1, 'kaufen' => 1, 'kddi' => 1, 'kerryhotels' => 1, 'kerrylogistics' => 1, 'kerryproperties' => 1, 'kfh' => 1, 'kia' => 1, 'kids' => 1, 'kim' => 1, 'kindle' => 1, 'kitchen' => 1, 'kiwi' => 1, 'koeln' => 1, 'komatsu' => 1, 'kosher' => 1, 'kpmg' => 1, 'kpn' => 1, 'krd' => 1, 'kred' => 1, 'kuokgroup' => 1, 'kyoto' => 1, 'lacaixa' => 1, 'lamborghini' => 1, 'lamer' => 1, 'lancaster' => 1, 'land' => 1, 'landrover' => 1, 'lanxess' => 1, 'lasalle' => 1, 'lat' => 1, 'latino' => 1, 'latrobe' => 1, 'law' => 1, 'lawyer' => 1, 'lds' => 1, 'lease' => 1, 'leclerc' => 1, 'lefrak' => 1, 'legal' => 1, 'lego' => 1, 'lexus' => 1, 'lgbt' => 1, 'lidl' => 1, 'life' => 1, 'lifeinsurance' => 1, 'lifestyle' => 1, 'lighting' => 1, 'like' => 1, 'lilly' => 1, 'limited' => 1, 'limo' => 1, 'lincoln' => 1, 'link' => 1, 'lipsy' => 1, 'live' => 1, 'living' => 1, 'llc' => 1, 'llp' => 1, 'loan' => 1, 'loans' => 1, 'locker' => 1, 'locus' => 1, 'lol' => 1, 'london' => 1, 'lotte' => 1, 'lotto' => 1, 'love' => 1, 'lpl' => 1, 'lplfinancial' => 1, 'ltd' => 1, 'ltda' => 1, 'lundbeck' => 1, 'luxe' => 1, 'luxury' => 1, 'madrid' => 1, 'maif' => 1, 'maison' => 1, 'makeup' => 1, 'man' => 1, 'management' => 1, 'mango' => 1, 'map' => 1, 'market' => 1, 'marketing' => 1, 'markets' => 1, 'marriott' => 1, 'marshalls' => 1, 'mattel' => 1, 'mba' => 1, 'mckinsey' => 1, 'med' => 1, 'media' => 1, 'meet' => 1, 'melbourne' => 1, 'meme' => 1, 'memorial' => 1, 'men' => 1, 'menu' => 1, 'merckmsd' => 1, 'miami' => 1, 'microsoft' => 1, 'mini' => 1, 'mint' => 1, 'mit' => 1, 'mitsubishi' => 1, 'mlb' => 1, 'mls' => 1, 'mma' => 1, 'mobile' => 1, 'moda' => 1, 'moe' => 1, 'moi' => 1, 'mom' => 1, 'monash' => 1, 'money' => 1, 'monster' => 1, 'mormon' => 1, 'mortgage' => 1, 'moscow' => 1, 'moto' => 1, 'motorcycles' => 1, 'mov' => 1, 'movie' => 1, 'msd' => 1, 'mtn' => 1, 'mtr' => 1, 'music' => 1, 'nab' => 1, 'nagoya' => 1, 'navy' => 1, 'nba' => 1, 'nec' => 1, 'netbank' => 1, 'netflix' => 1, 'network' => 1, 'neustar' => 1, 'new' => 1, 'news' => 1, 'next' => 1, 'nextdirect' => 1, 'nexus' => 1, 'nfl' => 1, 'ngo' => 1, 'nhk' => 1, 'nico' => 1, 'nike' => 1, 'nikon' => 1, 'ninja' => 1, 'nissan' => 1, 'nissay' => 1, 'nokia' => 1, 'norton' => 1, 'now' => 1, 'nowruz' => 1, 'nowtv' => 1, 'nra' => 1, 'nrw' => 1, 'ntt' => 1, 'nyc' => 1, 'obi' => 1, 'observer' => 1, 'office' => 1, 'okinawa' => 1, 'olayan' => 1, 'olayangroup' => 1, 'ollo' => 1, 'omega' => 1, 'one' => 1, 'ong' => 1, 'onl' => 1, 'online' => 1, 'ooo' => 1, 'open' => 1, 'oracle' => 1, 'orange' => 1, 'organic' => 1, 'origins' => 1, 'osaka' => 1, 'otsuka' => 1, 'ott' => 1, 'ovh' => 1, 'page' => 1, 'panasonic' => 1, 'paris' => 1, 'pars' => 1, 'partners' => 1, 'parts' => 1, 'party' => 1, 'pay' => 1, 'pccw' => 1, 'pet' => 1, 'pfizer' => 1, 'pharmacy' => 1, 'phd' => 1, 'philips' => 1, 'phone' => 1, 'photo' => 1, 'photography' => 1, 'photos' => 1, 'physio' => 1, 'pics' => 1, 'pictet' => 1, 'pictures' => 1, 'pid' => 1, 'pin' => 1, 'ping' => 1, 'pink' => 1, 'pioneer' => 1, 'pizza' => 1, 'place' => 1, 'play' => 1, 'playstation' => 1, 'plumbing' => 1, 'plus' => 1, 'pnc' => 1, 'pohl' => 1, 'poker' => 1, 'politie' => 1, 'porn' => 1, 'pramerica' => 1, 'praxi' => 1, 'press' => 1, 'prime' => 1, 'prod' => 1, 'productions' => 1, 'prof' => 1, 'progressive' => 1, 'promo' => 1, 'properties' => 1, 'property' => 1, 'protection' => 1, 'pru' => 1, 'prudential' => 1, 'pub' => 1, 'pwc' => 1, 'qpon' => 1, 'quebec' => 1, 'quest' => 1, 'racing' => 1, 'radio' => 1, 'read' => 1, 'realestate' => 1, 'realtor' => 1, 'realty' => 1, 'recipes' => 1, 'red' => 1, 'redstone' => 1, 'redumbrella' => 1, 'rehab' => 1, 'reise' => 1, 'reisen' => 1, 'reit' => 1, 'reliance' => 1, 'ren' => 1, 'rent' => 1, 'rentals' => 1, 'repair' => 1, 'report' => 1, 'republican' => 1, 'rest' => 1, 'restaurant' => 1, 'review' => 1, 'reviews' => 1, 'rexroth' => 1, 'rich' => 1, 'richardli' => 1, 'ricoh' => 1, 'ril' => 1, 'rio' => 1, 'rip' => 1, 'rocks' => 1, 'rodeo' => 1, 'rogers' => 1, 'room' => 1, 'rsvp' => 1, 'rugby' => 1, 'ruhr' => 1, 'run' => 1, 'rwe' => 1, 'ryukyu' => 1, 'saarland' => 1, 'safe' => 1, 'safety' => 1, 'sakura' => 1, 'sale' => 1, 'salon' => 1, 'samsclub' => 1, 'samsung' => 1, 'sandvik' => 1, 'sandvikcoromant' => 1, 'sanofi' => 1, 'sap' => 1, 'sarl' => 1, 'sas' => 1, 'save' => 1, 'saxo' => 1, 'sbi' => 1, 'sbs' => 1, 'scb' => 1, 'schaeffler' => 1, 'schmidt' => 1, 'scholarships' => 1, 'school' => 1, 'schule' => 1, 'schwarz' => 1, 'science' => 1, 'scot' => 1, 'search' => 1, 'seat' => 1, 'secure' => 1, 'security' => 1, 'seek' => 1, 'select' => 1, 'sener' => 1, 'services' => 1, 'seven' => 1, 'sew' => 1, 'sex' => 1, 'sexy' => 1, 'sfr' => 1, 'shangrila' => 1, 'sharp' => 1, 'shaw' => 1, 'shell' => 1, 'shia' => 1, 'shiksha' => 1, 'shoes' => 1, 'shop' => 1, 'shopping' => 1, 'shouji' => 1, 'show' => 1, 'silk' => 1, 'sina' => 1, 'singles' => 1, 'site' => 1, 'ski' => 1, 'skin' => 1, 'sky' => 1, 'skype' => 1, 'sling' => 1, 'smart' => 1, 'smile' => 1, 'sncf' => 1, 'soccer' => 1, 'social' => 1, 'softbank' => 1, 'software' => 1, 'sohu' => 1, 'solar' => 1, 'solutions' => 1, 'song' => 1, 'sony' => 1, 'soy' => 1, 'spa' => 1, 'space' => 1, 'sport' => 1, 'spot' => 1, 'srl' => 1, 'stada' => 1, 'staples' => 1, 'star' => 1, 'statebank' => 1, 'statefarm' => 1, 'stc' => 1, 'stcgroup' => 1, 'stockholm' => 1, 'storage' => 1, 'store' => 1, 'stream' => 1, 'studio' => 1, 'study' => 1, 'style' => 1, 'sucks' => 1, 'supplies' => 1, 'supply' => 1, 'support' => 1, 'surf' => 1, 'surgery' => 1, 'suzuki' => 1, 'swatch' => 1, 'swiss' => 1, 'sydney' => 1, 'systems' => 1, 'tab' => 1, 'taipei' => 1, 'talk' => 1, 'taobao' => 1, 'target' => 1, 'tatamotors' => 1, 'tatar' => 1, 'tattoo' => 1, 'tax' => 1, 'taxi' => 1, 'tci' => 1, 'tdk' => 1, 'team' => 1, 'tech' => 1, 'technology' => 1, 'temasek' => 1, 'tennis' => 1, 'teva' => 1, 'thd' => 1, 'theater' => 1, 'theatre' => 1, 'tiaa' => 1, 'tickets' => 1, 'tienda' => 1, 'tips' => 1, 'tires' => 1, 'tirol' => 1, 'tjmaxx' => 1, 'tjx' => 1, 'tkmaxx' => 1, 'tmall' => 1, 'today' => 1, 'tokyo' => 1, 'tools' => 1, 'top' => 1, 'toray' => 1, 'toshiba' => 1, 'total' => 1, 'tours' => 1, 'town' => 1, 'toyota' => 1, 'toys' => 1, 'trade' => 1, 'trading' => 1, 'training' => 1, 'travel' => 1, 'travelers' => 1, 'travelersinsurance' => 1, 'trust' => 1, 'trv' => 1, 'tube' => 1, 'tui' => 1, 'tunes' => 1, 'tushu' => 1, 'tvs' => 1, 'ubank' => 1, 'ubs' => 1, 'unicom' => 1, 'university' => 1, 'uno' => 1, 'uol' => 1, 'ups' => 1, 'vacations' => 1, 'vana' => 1, 'vanguard' => 1, 'vegas' => 1, 'ventures' => 1, 'verisign' => 1, 'versicherung' => 1, 'vet' => 1, 'viajes' => 1, 'video' => 1, 'vig' => 1, 'viking' => 1, 'villas' => 1, 'vin' => 1, 'vip' => 1, 'virgin' => 1, 'visa' => 1, 'vision' => 1, 'viva' => 1, 'vivo' => 1, 'vlaanderen' => 1, 'vodka' => 1, 'volvo' => 1, 'vote' => 1, 'voting' => 1, 'voto' => 1, 'voyage' => 1, 'wales' => 1, 'walmart' => 1, 'walter' => 1, 'wang' => 1, 'wanggou' => 1, 'watch' => 1, 'watches' => 1, 'weather' => 1, 'weatherchannel' => 1, 'webcam' => 1, 'weber' => 1, 'website' => 1, 'wed' => 1, 'wedding' => 1, 'weibo' => 1, 'weir' => 1, 'whoswho' => 1, 'wien' => 1, 'wiki' => 1, 'williamhill' => 1, 'win' => 1, 'windows' => 1, 'wine' => 1, 'winners' => 1, 'wme' => 1, 'wolterskluwer' => 1, 'woodside' => 1, 'work' => 1, 'works' => 1, 'world' => 1, 'wow' => 1, 'wtc' => 1, 'wtf' => 1, 'xbox' => 1, 'xerox' => 1, 'xihuan' => 1, 'xin' => 1, 'कॉम' => 1, 'セール' => 1, '佛山' => 1, '慈善' => 1, '集团' => 1, '在线' => 1, '点看' => 1, 'คอม' => 1, '八卦' => 1, 'موقع' => 1, '公益' => 1, '公司' => 1, '香格里拉' => 1, '网站' => 1, '移动' => 1, '我爱你' => 1, 'москва' => 1, 'католик' => 1, 'онлайн' => 1, 'сайт' => 1, '联通' => 1, 'קום' => 1, '时尚' => 1, '微博' => 1, '淡马锡' => 1, 'ファッション' => 1, 'орг' => 1, 'नेट' => 1, 'ストア' => 1, 'アマゾン' => 1, '삼성' => 1, '商标' => 1, '商店' => 1, '商城' => 1, 'дети' => 1, 'ポイント' => 1, '新闻' => 1, '家電' => 1, 'كوم' => 1, '中文网' => 1, '中信' => 1, '娱乐' => 1, '谷歌' => 1, '電訊盈科' => 1, '购物' => 1, 'クラウド' => 1, '通販' => 1, '网店' => 1, 'संगठन' => 1, '餐厅' => 1, '网络' => 1, 'ком' => 1, '亚马逊' => 1, '食品' => 1, '飞利浦' => 1, '手机' => 1, 'ارامكو' => 1, 'العليان' => 1, 'بازار' => 1, 'ابوظبي' => 1, 'كاثوليك' => 1, 'همراه' => 1, '닷컴' => 1, '政府' => 1, 'شبكة' => 1, 'بيتك' => 1, 'عرب' => 1, '机构' => 1, '组织机构' => 1, '健康' => 1, '招聘' => 1, 'рус' => 1, '大拿' => 1, 'みんな' => 1, 'グーグル' => 1, '世界' => 1, '書籍' => 1, '网址' => 1, '닷넷' => 1, 'コム' => 1, '天主教' => 1, '游戏' => 1, 'vermögensberater' => 1, 'vermögensberatung' => 1, '企业' => 1, '信息' => 1, '嘉里大酒店' => 1, '嘉里' => 1, '广东' => 1, '政务' => 1, 'xyz' => 1, 'yachts' => 1, 'yahoo' => 1, 'yamaxun' => 1, 'yandex' => 1, 'yodobashi' => 1, 'yoga' => 1, 'yokohama' => 1, 'you' => 1, 'youtube' => 1, 'yun' => 1, 'zappos' => 1, 'zara' => 1, 'zero' => 1, 'zip' => 1, 'zone' => 1, 'zuerich' => 1, ); php-toolkit/DataLiberation/URL/class-urlintextprocessor.php 0000777 00000034243 15251156632 0020177 0 ustar 00 <?php namespace WordPress\DataLiberation\URL; use WordPress\DataLiberation\BlockMarkup\URL; use WP_HTML_Text_Replacement; /** * Finds string fragments that look like URLs and allows replacing them. * * This class implements two stages of detection: * * 1. **A "thick" sieve** * 2. **A "fine" sieve** * * The thick sieve uses a regular expression to match URL-like substrings. It matches too * much and may yield false positives. * * The fine sieve filters out invalid candidates using a WHATWG-compliant parser so only * real URLs are returned. * * ## URL Detection * * The thick sieve looks for URLs: * * * Starting with http://, https://, or //, e.g. //wp.org. * * With no protocol, e.g. www.wp.org or wp.org/path * * Here's a list of matching-related rules, limitations, and assumptions: * * ### Protocols * * As a site migration tool, this processor only considers URLs with HTTP * and HTTPS protocols. * * ### Domain names * * UTF-8 characters in the domain names are supported even if they're * not encoded as punycode. For example, scanning the text: * * > Więcej na łąka.pl * * Would yield `łąka.pl` * * ### Paths * * The path is limited to ASCII characters, as per the URL specification. * For example, scanning the text: * * > Visit the WordPress plugins directory https://w.org/plugins?łąka=1 * * Would yield `https://w.org/plugins?`, not `https://w.org/plugins?łąka=1`. * However, scanning this text: * * > Visit the WordPress plugins directory https://w.org/plugins?%C5%82%C4%85ka=1 * * Would yield `https://w.org/plugins?%C5%82%C4%85ka=1`. * * ### Parenthesis treatment * * This scanner captures parentheses as a part of the path, query, or fragment, except * when they're seen as the last character in the URL. For example, scanning the text: * * > Visit the WordPress plugins directory (https://w.org/plugins) * * Would yield `https://w.org/plugins`, but scanning the text: * * > Visit the WordPress plugins directory (https://w.org/plug(in)s * * Would yield `https://w.org/plug(in)s`. * * ### Rejecting URLs with embedded credentials * * `https://user:pass@wp.org` is not matched. Rewriting URLs that presume transferable * credentials is hazardous and rarely correct for migrations. * * ### Reject non-HTTP(S) schemes * * Out of scope for site moves; all of these are rejected: * `gopher://site.com`, `blob:afgh2-48189d`, `ahttp://site.com`, `mailto:user@site.com`, `file://asset.zip`. * If we need additional schemes later, we can add them intentionally. * * ### Reject non‑absolute‑looking references * * While we do rely on a base URL, inputs like `::`, `/index.html`, `?query` are still ignored. * Bare-domain forms like `mysite.org/?query` are still matched. * * ### Handle trailing punctuation sensibly * * `https://mysite.com/path/..` is interpreted as `https://mysite.com/path/` rather than * collapsing to the origin. A final period is far more likely sentence punctuation than `../`. * If a user truly writes `https://mysite.com/path/../`, we parse it as expected. * * ### Fuzzy matching for malformed ports * * * **WHATWG**: `"http://w.org:100000 plugins are in the directory" → failure`. * * **Inline detection**: `"http://w.org:100000 plugins are in the directory" → "http://w.org/"` * (truncate at the invalid port). * * This is a best‑effort extraction of the valid prefix rather than an all‑or‑nothing rejection. * * ### Whitespace handling * * * **WHATWG**: `"http://example\t.\norg" → "http://example.org/"`. * * **Inline detection**: stops at the first whitespace, yielding `"http://example/"`. * * This reflects how URLs actually appear in text blocks where whitespace often terminates a link. */ class URLInTextProcessor { private $text; private $url_starts_at; private $url_length; private $bytes_already_parsed = 0; /** * @var string */ private $matched_url; /** * @var URL */ private $parsed_url; private $did_prepend_protocol; /** * The base URL for the parsing algorithm. * See https://url.spec.whatwg.org/. * * @var mixed|null */ private $base_url; private $base_protocol; /** * The regular expression pattern used for the matchin URL candidates * from the text. * * @var string */ private $regex; /** * @see \WP_HTML_Tag_Processor * @var WP_HTML_Text_Replacement[] */ private $lexical_updates = array(); /** * @var bool * A flag to indicate whether the URL matching should be strict or not. * If set to true, the matching will be strict, meaning it will only match URLs that strictly adhere to the pattern. * If set to false, the matching will be more lenient, allowing for potential false positives. */ private $strict = false; public function __construct( $text, $base_url = null ) { $this->text = $text; $this->base_url = $base_url; $this->base_protocol = $base_url ? parse_url( $base_url, PHP_URL_SCHEME ) : null; $prefix = $this->strict ? '^' : ''; $suffix = $this->strict ? '$' : ''; // Source: https://github.com/vstelmakh/url-highlight/blob/master/src/Matcher/Matcher.php. $this->regex = '/' . $prefix . ' (?: # scheme (?<scheme>[a-z0-9\+]+?:)? # (?:\/*) # The protocol may optionally be followed by one or more slashes )? (?: # userinfo (?: (?<=\/{2}) # prefixed with \/\/ | # or (?=[^\p{Sm}\p{Sc}\p{Sk}\p{P}]) # start with not: mathematical, currency, modifier symbol, punctuation ) (?<userinfo>[^\s<>@\/]+) # not: whitespace, < > @ \/ @ # at )? (?=%|[^\p{Z}\p{Sm}\p{Sc}\p{Sk}\p{C}\p{P}]) # followed by valid host char (?| # host (?<host> # host prefixed by scheme or userinfo (less strict) (?<=\/\/|@) # prefixed with \/\/ or @ (?=[^\-]) # label start, not: - (?:%|[^\p{Z}\p{Sm}\p{Sc}\p{Sk}\p{C}\p{P}]|-){1,63} # label not: whitespace, mathematical, currency, modifier symbol, control point, punctuation | except - (?<=[^\-]) # label end, not: - (?: # more label parts \. (?=[^\-]) # label start, not: - (?<tld>(?:[^\p{Z}\p{Sm}\p{Sc}\p{Sk}\p{C}\p{P}]|-){1,63}) # label not: whitespace, mathematical, currency, modifier symbol, control point, punctuation | except - (?<=[^\-]) # label end, not: - )* ) | # or (?<host> # host with tld (no scheme or userinfo) (?=[^\-]) # label start, not: - (?:%|[^\p{Z}\p{Sm}\p{Sc}\p{Sk}\p{C}\p{P}]|-){1,63} # label not: whitespace, mathematical, currency, modifier symbol, control point, punctuation | except - (?<=[^\-]) # label end, not: - (?: # more label parts \. (?=[^\-]) # label start, not: - (?:%|[^\p{Z}\p{Sm}\p{Sc}\p{Sk}\p{C}\p{P}]|-){1,63} # label not: whitespace, mathematical, currency, modifier symbol, control point, punctuation | except - (?<=[^\-]) # label end, not: - )* \.(?<tld>\w{2,63}) # tld ) ) (?:\:(?<port>\d{1,5}(?!\d)))? # port (?<path> # path, query, fragment [\/?#] # prefixed with \/ or ? or # [^\s<>]* # any chars except whitespace and <> (?<=[^\s<>({\[`!;:\'".,?«»“”‘’]) # end with not a space or some punctuation chars )? ' . $suffix . '/ixuJ'; } /** * @return string */ public function next_url() { while ( true ) { $this->matched_url = null; $this->parsed_url = null; $this->url_starts_at = null; $this->url_length = null; $this->did_prepend_protocol = false; /** * Thick sieve – eagerly match things that look like URLs but turn out to not be URLs in the end. */ $matches = array(); $found = preg_match( $this->regex, $this->text, $matches, PREG_OFFSET_CAPTURE, $this->bytes_already_parsed ); if ( 1 !== $found ) { return false; } $this->matched_url = $matches[0][0]; // Do not consider just :: as a URL. if ( '::' === $this->matched_url ) { continue; } if ( ')' === $this->matched_url[ strlen( $this->matched_url ) - 1 ] || '.' === $this->matched_url[ strlen( $this->matched_url ) - 1 ] ) { $this->matched_url = substr( $this->matched_url, 0, - 1 ); } $url_starts_at = $matches[0][1]; $this->bytes_already_parsed = $url_starts_at + strlen( $this->matched_url ); $had_protocol = WPURL::has_http_https_protocol( $this->matched_url ); $preprocessed_url = $this->matched_url; if ( $this->base_url && $this->base_protocol && ! $had_protocol ) { $preprocessed_url = WPURL::ensure_protocol( $preprocessed_url, $this->base_protocol ); $this->did_prepend_protocol = true; } /* * Extra fine sieve – parse the candidates using a WHATWG-compliant parser to rule out false positives. */ $parsed_url = WPURL::parse( $preprocessed_url, $this->base_url ); if ( false === $parsed_url ) { continue; } // Only consider HTTP and HTTPS URLs. if ( $parsed_url->protocol && ! in_array( $parsed_url->protocol, array( 'http:', 'https:' ), true ) ) { continue; } // Disregard URLs with auth details. if ( $parsed_url->username || $parsed_url->password ) { continue; } // Additional rigor for URLs that are not explicitly preceded by a double slash. if ( ! $had_protocol ) { /* * Skip TLDs that are not in the public suffix. * This reduces false positives like `index.html` or `plugins.php`. * * See https://publicsuffix.org/. */ $last_dot_position = strrpos( $parsed_url->hostname, '.' ); if ( false === $last_dot_position ) { /* * Oh, there was no dot in the hostname AND no double slash at * the beginning! Let's assume this isn't a valid URL and move on. * @TODO: Explore updating the regular expression above to avoid matching * URLs without a dot in the hostname when they're not preceeded * by a protocol. */ continue; } $tld = substr( $parsed_url->hostname, $last_dot_position + 1 ); if ( ! WPURL::is_known_public_domain( $tld ) ) { // This TLD is not in the public suffix list. It's not a valid domain name. continue; } } $this->parsed_url = $parsed_url; $this->url_starts_at = $url_starts_at; $this->url_length = strlen( $matches[0][0] ); return true; } } public function get_raw_url() { return $this->matched_url ?? false; } public function get_parsed_url() { if ( null === $this->parsed_url ) { return false; } return $this->parsed_url; } public function set_raw_url( $new_url ) { if ( null === $this->matched_url ) { return false; } if ( $this->did_prepend_protocol ) { $new_url = substr( $new_url, strpos( $new_url, '://' ) + 3 ); } $this->matched_url = $new_url; $this->lexical_updates[ $this->url_starts_at ] = new WP_HTML_Text_Replacement( $this->url_starts_at, $this->url_length, $new_url ); return true; } private function apply_lexical_updates() { if ( ! count( $this->lexical_updates ) ) { return 0; } /* * Attribute updates can be enqueued in any order but updates * to the document must occur in lexical order; that is, each * replacement must be made before all others which follow it * at later string indices in the input document. * * Sorting avoid making out-of-order replacements which * can lead to mangled output, partially-duplicated * attributes, and overwritten attributes. */ ksort( $this->lexical_updates ); $bytes_already_copied = 0; $output_buffer = ''; foreach ( $this->lexical_updates as $diff ) { $shift = strlen( $diff->text ) - $diff->length; // Adjust the cursor position by however much an update affects it. if ( $diff->start < $this->bytes_already_parsed ) { $this->bytes_already_parsed += $shift; } $output_buffer .= substr( $this->text, $bytes_already_copied, $diff->start - $bytes_already_copied ); if ( $diff->start === $this->url_starts_at ) { $this->url_starts_at = strlen( $output_buffer ); $this->url_length = strlen( $diff->text ); } $output_buffer .= $diff->text; $bytes_already_copied = $diff->start + $diff->length; } $this->text = $output_buffer . substr( $this->text, $bytes_already_copied ); $this->lexical_updates = array(); } public function get_updated_text() { $this->apply_lexical_updates(); return $this->text; } } php-toolkit/DataLiberation/URL/class-wpurl.php 0000777 00000022720 15251156632 0015347 0 ustar 00 <?php namespace WordPress\DataLiberation\URL; use Rowbot\URL\URL; /** * An abstraction to make swapping the URL parser easier later on. * We do not need it in the long run. It adds extra overhead of the * function call – let's remove it once the URL parser is decided. */ class WPURL { public static function parse( $url, $base = null ) { if ( is_string( $url ) ) { return URL::parse( $url, $base ) ?? false; } elseif ( is_a( $url, 'Rowbot\URL\URL' ) ) { return $url; } return false; } public static function can_parse( $url, $base = null ) { return URL::canParse( $url, $base ); } /** * Replaces the "base" of a URL — scheme, host (and port), and the portion of the path that * belongs to the old base — with a new base while keeping the remainder of the URL intact. * * This is intended for content migrations, where URLs embedded in block markup, HTML attributes, * or inline text must be moved from one site root to another without losing the rest of the path, * query, or fragment. It handles simple domain swaps, ports, and deep path bases. When the old * base includes path segments, only that matched prefix is substituted and the unmatched tail is * carried over to the target base. * * For example: * * URL: https://example.com/a/b/c/d/e/f/g/h/i/j/page/ * * Old base: https://example.com/a/b/c/d/e/f/ * * New base: https://example.org/docs/ * * Result: https://example.org/docs/g/h/i/j/page/ * * ## Trailing slash handling * * Trailing slash style is preserved from the original URL. If it has no trailing slash, the * result will also omit the trailing slash and vice versa. * * For example, here the final result has no trailing slash: * * URL: https://example.com/uploads/file.txt * * Old base: https://example.com/uploads/ * * New base: https://example.org/docs/ * * Result: https://example.org/docs/file.txt * * And here it does: * * URL: https://example.com/uploads/2018/ * * Old base: https://example.com/uploads/ * * New base: https://example.org/docs/ * * Result: https://example.org/docs/2018/ * * ## URL-encoded path segments * * URL-encoded path segments are respected and not inadvertently decoded or re-encoded. Only the * matched base prefix is considered for alignment, so inputs that contain percent-encoded content * keep that content exactly as-is in the output. This prevents data corruption in tricky cases such * as "/~jappleseed/1997.10.1/%2561-reasons-to-migrate-data/" where the "%2561" must remain * double-escaped after the move. * * ## Relative URLs * * This method can preserve the relative nature of the original URL. Say you are processing a markup * that contains `<a href="/uploads/file.txt">`. The original URL string is "/uploads/file.txt", * and the URL actually resolves to "https://example.com/uploads/file.txt". If you want to replace * the base URL from "https://example.com/uploads/" to "https://newsite.com/files/" but keep the * URL relative, you can pass the raw URL string via the "raw_url" option. * * For example: * * URL: https://example.com/uploads/file.txt * * Raw URL: /uploads/file.txt * * Old base: https://example.com/uploads/ * * New base: https://example.org/files/ * * Result: /files/file.txt * * The method also supports relative inputs commonly found in markup. If you pass the raw URL * string via the "raw_url" option, the method can infer whether the author originally wrote a * relative URL like "docs/page.html" or an absolute one. You may also explicitly * assert relativity with "is_relative" to avoid inference. * * @param string|URL $url The URL to replace the base of. * @param array $options Associative options: old_base_url, new_base_url; optional raw_url. * @return ConvertedUrl|false Returns a ConvertedUrl value object on success, or false when parsing * or replacement cannot be performed. */ public static function replace_base_url( $url, $options ) { if ( ! is_array( $options ) ) { return false; } foreach ( array( 'old_base_url', 'new_base_url' ) as $required ) { if ( ! array_key_exists( $required, $options ) || null === $options[ $required ] ) { return false; } } $old_base_url = self::parse( $options['old_base_url'] ); $new_base_url = self::parse( $options['new_base_url'] ); $url = self::parse( $url, $old_base_url ? $old_base_url->toString() : null ); if ( false === $old_base_url || false === $new_base_url || false === $url ) { return false; } $updated_url = clone $url; $updated_url->hostname = $new_base_url->hostname; $updated_url->protocol = $new_base_url->protocol; $updated_url->port = $new_base_url->port; $from_pathname = $url->pathname; $to_pathname = $new_base_url->pathname; $base_pathname = $old_base_url->pathname; if ( $base_pathname !== $to_pathname ) { $base_pathname_with_trailing_slash = rtrim( $base_pathname, '/' ) . '/'; $decoded_matched_pathname = urldecode_n( $from_pathname, strlen( $base_pathname_with_trailing_slash ) ); $to_pathname_with_trailing_slash = rtrim( $to_pathname, '/' ) . '/'; $remaining_pathname = substr( $decoded_matched_pathname, strlen( $base_pathname_with_trailing_slash ) ); $updated_url->pathname = $to_pathname_with_trailing_slash . $remaining_pathname; } /* * Stylistic choice – if the updated URL has no trailing slash, * do not add it to the new URL. The WHATWG URL parser will * add one automatically if the path is empty, so we have to * explicitly remove it. */ $new_raw_url = $updated_url->toString(); $should_trim_trailing_slash = ( '' !== $from_pathname && '/' !== substr( $from_pathname, -1 ) && '/' !== $from_pathname && '' === $url->search && '' === $url->hash ); if ( $should_trim_trailing_slash ) { $new_raw_url = rtrim( $new_raw_url, '/' ); } if ( ! $new_raw_url ) { // This may technically happen, but does it happen in practice? return false; } $converted_url = new ConvertedUrl(); $converted_url->new_url = $updated_url; $converted_url->new_raw_url = $new_raw_url; // Preserve the relative nature of the original URL. if ( array_key_exists( 'raw_url', $options ) && is_string( $options['raw_url'] ) ) { if ( ! array_key_exists( 'is_relative', $options ) ) { $options['is_relative'] = self::can_parse( $options['raw_url'] ); } if ( $options['is_relative'] ) { $relative_url = $updated_url->pathname; // Remove the trailing slash if it's not the root path. if ( strlen( $relative_url ) > 1 && $should_trim_trailing_slash ) { $relative_url = rtrim( $relative_url, '/' ); } if ( '' !== $updated_url->search ) { $relative_url .= $updated_url->search; } if ( '' !== $updated_url->hash ) { $relative_url .= $updated_url->hash; } $converted_url->was_relative = true; $converted_url->new_raw_relative_url = $relative_url; } } return $converted_url; } /** * Prepends a protocol to any matched URL without the double slash. * * Imagine we have a base URL of `https://example.com` and a text like `Visit myblog.com`. * This Processor would match `myblog.com` as a URL candidate and then the parser * would parse it as `https://example.com/myblog.com`, which is not what a user would expect. * * To get `https://myblog.com`, we need to prepend a protocol and turn that candidate into * `https://myblog.com` before parsing. */ public static function ensure_protocol( $raw_url, $protocol = 'https' ) { if ( ! self::has_http_https_protocol( $raw_url ) ) { $raw_url = $protocol . '://' . $raw_url; } return $raw_url; } /** * This method only considers http and https protocols. */ public static function has_http_https_protocol( $raw_url ) { return ( ( // Protocol-relative URLs. strlen( $raw_url ) > 2 && '/' === $raw_url[0] && '/' === $raw_url[1] ) || ( strlen( $raw_url ) > 5 && ( 'h' === $raw_url[0] || 'H' === $raw_url[0] ) && ( 't' === $raw_url[1] || 'T' === $raw_url[1] ) && ( 't' === $raw_url[2] || 'T' === $raw_url[2] ) && ( 'p' === $raw_url[3] || 'P' === $raw_url[3] ) && ':' === $raw_url[4] ) || ( strlen( $raw_url ) > 6 && ( 'h' === $raw_url[0] || 'H' === $raw_url[0] ) && ( 't' === $raw_url[1] || 'T' === $raw_url[1] ) && ( 't' === $raw_url[2] || 'T' === $raw_url[2] ) && ( 'p' === $raw_url[3] || 'P' === $raw_url[3] ) && ( 's' === $raw_url[4] || 'S' === $raw_url[4] ) && ':' === $raw_url[5] ) ); } public static function append_path( $base_url, $path ) { $base_url = self::parse( $base_url ); $base_url->pathname = rtrim( $base_url->pathname, '/' ) . '/' . ltrim( $path, '/' ); return $base_url->toString(); } /** * Checks if a TLD is in the known public domain suffix list. * This reduces false positives like `index.html` or `plugins.php`. * * @see https://publicsuffix.org/ * * @param string $tld The top-level domain to check. * @return bool True if the TLD is a known public domain, false otherwise. */ public static function is_known_public_domain( $tld ) { static $public_suffix_list = null; if ( null === $public_suffix_list ) { $public_suffix_list = require_once __DIR__ . '/public-suffix-list.php'; } // @TODO: Parse wildcards and exceptions from the public suffix list. $tld = strtolower( $tld ); return ! empty( $public_suffix_list[ $tld ] ) || 'internal' === $tld; } } php-toolkit/DataLiberation/URL/class-convertedurl.php 0000777 00000001360 15251156632 0016707 0 ustar 00 <?php namespace WordPress\DataLiberation\URL; use Rowbot\URL\URL; /** * Value object returned by WPURL::replace_base_url(). * * - Cast to string to get the updated URL as a string. * - When the original URL was relative, casting returns a relative string against * the new base. */ class ConvertedUrl { /** @var URL */ public $new_url; /** @var string */ public $new_raw_url; /** @var string|null */ public $new_raw_relative_url; /** @var bool */ public $was_relative = false; /** * Returns the updated URL string. If the original was relative, returns a relative string. */ public function __toString(): string { if ( $this->was_relative ) { return $this->new_raw_relative_url; } return $this->new_raw_url; } } php-toolkit/DataLiberation/URL/functions.php 0000777 00000011062 15251156632 0015100 0 ustar 00 <?php namespace WordPress\DataLiberation\URL; use Rowbot\URL\URL; use WordPress\DataLiberation\BlockMarkup\BlockMarkupUrlProcessor; /** * Migrate URLs in post content. See WPRewriteUrlsTests for * specific examples. TODO: A better description. * * Example: * * ```php * php > wp_rewrite_urls([ * 'block_markup' => '<!-- wp:image {"src": "http://legacy-blog.com/image.jpg"} -->', * 'url-mapping' => [ * 'http://legacy-blog.com' => 'https://modern-webstore.org' * ] * ]) * <!-- wp:image {"src":"https:\/\/modern-webstore.org\/image.jpg"} --> * ``` * * @TODO Use a proper JSON parser and encoder to: * * Support UTF-16 characters * * Gracefully handle recoverable encoding issues * * Avoid changing the whitespace in the same manner as * we do in WP_HTML_Tag_Processor. e.g. if we start with: * * ```html * <!-- wp:block {"url":"https://w.org"}` --> * ^ no space here * ``` * * then it would be nice to re-encode that block markup also without the space character. This is similar * to how the tag processor avoids changing parts of the tag it doesn't need to change. */ function wp_rewrite_urls( $options ) { if ( empty( $options['base_url'] ) ) { // Use first from-url as base_url if not specified. $from_urls = array_keys( $options['url-mapping'] ); $options['base_url'] = $from_urls[0]; } $url_mapping = array(); foreach ( $options['url-mapping'] as $from_url_string => $to_url_string ) { $url_mapping[] = array( 'from_url' => WPURL::parse( $from_url_string ), 'to_url' => WPURL::parse( $to_url_string ), ); } $p = new BlockMarkupUrlProcessor( $options['block_markup'], $options['base_url'] ); while ( $p->next_url() ) { $parsed_url = $p->get_parsed_url(); foreach ( $url_mapping as $mapping ) { if ( is_child_url_of( $parsed_url, $mapping['from_url'] ) ) { $p->replace_base_url( $mapping['to_url'] ); break; } } } return $p->get_updated_html(); } /** * Check if a given URL matches the current site URL. * * @param URL $child The URL to check. * @param string $parent_url The current site URL to compare against. * * @return bool Whether the URL matches the current site URL. */ function is_child_url_of( $child, $parent_url ) { $parent_url = is_string( $parent_url ) ? WPURL::parse( $parent_url ) : $parent_url; $child = is_string( $child ) ? WPURL::parse( $child ) : $child; $child_pathname_no_trailing_slash = rtrim( urldecode( $child->pathname ), '/' ); if ( false === $child || false === $parent_url ) { return false; } if ( $parent_url->hostname !== $child->hostname ) { return false; } if ( $parent_url->protocol !== $child->protocol ) { return false; } $parent_pathname = urldecode( $parent_url->pathname ); return ( // Direct match. $parent_pathname === $child_pathname_no_trailing_slash || $parent_pathname === $child_pathname_no_trailing_slash . '/' || // Path prefix. 0 === strncmp( $child_pathname_no_trailing_slash . '/', $parent_pathname, strlen( $parent_pathname ) ) ); } /** * Decodes the first n **encoded bytes** a URL-encoded string. * * For example, `urldecode_n( '%22is 6 %3C 6?%22 – asked Achilles', 1 )` returns * '"is 6 %3C 6?%22 – asked Achilles' because only the first encoded byte is decoded. * * @param string $input The string to decode. * @param int $decode_n The number of bytes to decode in $input * * @return string The decoded string. */ function urldecode_n( $input, $decode_n ) { // Fast paths: nothing to do. if ( $decode_n <= 0 || false === strpos( $input, '%' ) ) { return $input; } $result = ''; $at = 0; while ( true ) { if ( $at + 3 > strlen( $input ) ) { break; } $last_at = $at; $at += strcspn( $input, '%', $at ); // Consume bytes except for the percent sign. $result .= substr( $input, $last_at, $at - $last_at ); // If we've already decoded the requested number of bytes, stop. if ( strlen( $result ) >= $decode_n ) { break; } ++$at; if ( $at > strlen( $input ) ) { break; } $decodable_length = strspn( $input, '0123456789ABCDEFabcdef', $at, 2 ); if ( 2 === $decodable_length ) { // Decodes the urlencoded hex sequence from URL. // Note: This decodes bytes, not characters. It will recover the original byte sequence, // not necessarily any valid UTF-8 characters. $result .= chr( hexdec( $input[ $at ] . $input[ $at + 1 ] ) ); $at += 2; } else { // Consume the next byte and move on. $result .= '%'; } } $result .= substr( $input, $at ); return $result; } php-toolkit/DataLiberation/URL/class-cssurlprocessor.php 0000777 00000004260 15251156632 0017450 0 ustar 00 <?php namespace WordPress\DataLiberation\URL; use WordPress\DataLiberation\CSS\CSSProcessor; /** * Provides URL specific helpers on top of the CSSProcessor tokenizer. */ class CSSURLProcessor { /** * @var CSSProcessor */ private $processor; /** * @param string $css CSS source without wrapping braces. */ public function __construct( string $css ) { $this->processor = CSSProcessor::create( $css ); } /** * Moves the cursor to the next URL token, if available. * * @return bool */ public function next_url(): bool { while ( $this->processor->next_token() ) { $type = $this->processor->get_token_type(); // Direct URL token. if ( CSSProcessor::TOKEN_URL === $type ) { return true; } // url() function with STRING token. if ( CSSProcessor::TOKEN_FUNCTION === $type && 0 === strcasecmp( $this->processor->get_token_value(), 'url' ) ) { // Look ahead for STRING token, skipping whitespace. while ( $this->processor->next_token() ) { $inner_type = $this->processor->get_token_type(); if ( CSSProcessor::TOKEN_WHITESPACE === $inner_type ) { continue; // Skip whitespace. } if ( CSSProcessor::TOKEN_STRING === $inner_type ) { return true; // Found the URL string. } // Hit something else (like RIGHT_PAREN or another token). break; } } } return false; } /** * Returns the raw (decoded) URL for the current match. * * @return string|false */ public function get_raw_url() { $value = $this->processor->get_token_value(); return false !== $value ? $value : false; } /** * Replaces the currently matched URL with a new value. * * @param string $new_url Replacement URL without quoting. * @return bool */ public function set_raw_url( string $new_url ): bool { return $this->processor->set_token_value( $new_url ); } /** * Returns the updated CSS with all replacements applied. * * @return string */ public function get_updated_css(): string { return $this->processor->get_updated_css(); } /** * Determines whether the current URL is a data URI. * * @return bool */ public function is_data_uri(): bool { return $this->processor->is_data_uri(); } } parsers.php 0000777 00000001202 15251156632 0006744 0 ustar 00 <?php /** * WordPress eXtended RSS file parser implementations * * @package WordPress * @subpackage Importer */ _deprecated_file( basename( __FILE__ ), '0.7.0' ); /** WXR_Parser class */ require_once __DIR__ . '/parsers/class-wxr-parser.php'; /** WXR_Parser_SimpleXML class */ require_once __DIR__ . '/parsers/class-wxr-parser-simplexml.php'; /** WXR_Parser_XML class */ require_once __DIR__ . '/parsers/class-wxr-parser-xml.php'; /** WXR_Parser_Regex class */ require_once __DIR__ . '/parsers/class-wxr-parser-regex.php'; /** WXR_Parser_XML_Processor class */ require_once __DIR__ . '/parsers/class-wxr-parser-xml-processor.php'; compat.php 0000777 00000001540 15251156632 0006555 0 ustar 00 <?php /** * Implementation for WordPress functions missing in older WordPress versions. * * @package WordPress * @subpackage Importer */ if ( ! function_exists( 'wp_slash_strings_only' ) ) { /** * Adds slashes to only string values in an array of values. * * Compat for WordPress < 5.3.0. * * @since 0.7.0 * * @param mixed $value Scalar or array of scalars. * @return mixed Slashes $value */ function wp_slash_strings_only( $value ) { return map_deep( $value, 'addslashes_strings_only' ); } } if ( ! function_exists( 'addslashes_strings_only' ) ) { /** * Adds slashes only if the provided value is a string. * * Compat for WordPress < 5.3.0. * * @since 0.7.0 * * @param mixed $value * @return mixed */ function addslashes_strings_only( $value ) { return is_string( $value ) ? addslashes( $value ) : $value; } } wordpress-importer.php 0000777 00000005167 15251156632 0011172 0 ustar 00 <?php /* * @wordpress-plugin * Plugin Name: WordPress Importer * Plugin URI: https://wordpress.org/plugins/wordpress-importer/ * Description: Import posts, pages, comments, custom fields, categories, tags and more from a WordPress export file. * Author: wordpressdotorg * Author URI: https://wordpress.org/ * Version: 0.9.5 * Requires at least: 5.2 * Requires PHP: 7.2 * Text Domain: wordpress-importer * License: GPLv2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html */ if ( ! defined( 'WP_LOAD_IMPORTERS' ) ) { return; } /** Display verbose errors */ if ( ! defined( 'IMPORT_DEBUG' ) ) { define( 'IMPORT_DEBUG', WP_DEBUG ); } /** WordPress Import Administration API */ require_once ABSPATH . 'wp-admin/includes/import.php'; if ( ! class_exists( 'WP_Importer' ) ) { $class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php'; if ( file_exists( $class_wp_importer ) ) { require $class_wp_importer; } } /** Functions missing in older WordPress versions. */ require_once __DIR__ . '/compat.php'; if ( ! class_exists( 'WordPress\XML\XMLProcessor' ) ) { require_once __DIR__ . '/php-toolkit/load.php'; } /** WXR_Parser class */ require_once __DIR__ . '/parsers/class-wxr-parser.php'; /** WXR_Parser_SimpleXML class */ require_once __DIR__ . '/parsers/class-wxr-parser-simplexml.php'; /** WXR_Parser_XML class */ require_once __DIR__ . '/parsers/class-wxr-parser-xml.php'; /** * WXR_Parser_Regex class * @deprecated 0.9.0 Use WXR_Parser_XML_Processor instead. The WXR_Parser_Regex class * is no longer used by the importer or maintained with bug fixes. The only * reason it is still included in the codebase is for backwards compatibility * with plugins that directly reference it. */ require_once __DIR__ . '/parsers/class-wxr-parser-regex.php'; /** WXR_Parser_XML_Processor class */ require_once __DIR__ . '/parsers/class-wxr-parser-xml-processor.php'; /** WP_Import class */ require_once __DIR__ . '/class-wp-import.php'; function wordpress_importer_init() { load_plugin_textdomain( 'wordpress-importer' ); /** * WordPress Importer object for registering the import callback * @global WP_Import $wp_import */ $GLOBALS['wp_import'] = new WP_Import(); // phpcs:ignore WordPress.WP.CapitalPDangit register_importer( 'wordpress', 'WordPress', __( 'Import <strong>posts, pages, comments, custom fields, categories, and tags</strong> from a WordPress export file.', 'wordpress-importer' ), array( $GLOBALS['wp_import'], 'dispatch' ) ); } add_action( 'admin_init', 'wordpress_importer_init' ); readme.txt 0000777 00000015733 15251156632 0006570 0 ustar 00 === WordPress Importer === Contributors: wordpressdotorg Donate link: https://wordpressfoundation.org/donate/ Tags: importer, wordpress Requires at least: 5.2 Tested up to: 6.8 Requires PHP: 7.2 Stable tag: 0.9.5 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Import posts, pages, comments, custom fields, categories, tags and more from a WordPress export file. == Description == The WordPress Importer will import the following content from a WordPress export file: * Posts, pages and other custom post types * Comments and comment meta * Custom fields and post meta * Categories, tags and terms from custom taxonomies and term meta * Authors For further information and instructions please see the [documention on Importing Content](https://wordpress.org/support/article/importing-content/#wordpress). == Installation == The quickest method for installing the importer is: 1. Visit Tools -> Import in the WordPress dashboard 1. Click on the WordPress link in the list of importers 1. Click "Install Now" 1. Finally click "Activate Plugin & Run Importer" If you would prefer to do things manually then follow these instructions: 1. Upload the `wordpress-importer` folder to the `/wp-content/plugins/` directory 1. Activate the plugin through the 'Plugins' menu in WordPress 1. Go to the Tools -> Import screen, click on WordPress == Changelog == = 0.9.5 = * Rewrite CSS URLs in block markup (e.g., cover blocks with background images). * Code refactoring: Extract import processing logic into separate methods to prepare for future streaming support. * Update Playwright and @playwright/test dependencies from 1.55.0 to 1.56.1. * Clean up vendor-patched dependencies by removing dotfiles and GitHub workflow files. = 0.9.4 = * Fix a bug that caused self-closing blocks to be incorrectly serialized during URL rewriting. = 0.9.3 = * Rewrite attachment URLs to the new URL structure = 0.9.2 = * Rewrite site URLs in block attributes. = 0.9.1 = * Add support for rewriting site URLs in post content and excerpts. = 0.9.0 = * Introduce a new XML parser class `WXR_Parser_XML_Processor` that replaces the deprecated `WXR_Parser_Regex` class. = 0.8.4 = * Fix a bug on deserialization of untrusted input. * Update compatibility tested-up-to to WordPress 6.7.2. = 0.8.3 = * Update compatibility tested-up-to to WordPress 6.7. * Update call to `post_exists` to include `post_type` in the query * PHP 8.4 compatibility fixes. = 0.8.2 = * Update compatibility tested-up-to to WordPress 6.4.2. * Update doc URL references. * Adjust workflow triggers. = 0.8.1 = * Update compatibility tested-up-to to WordPress 6.2. * Update paths to build status badges. = 0.8 = * Update minimum WordPress requirement to 5.2. * Update minimum PHP requirement to 5.6. * Update compatibility tested-up-to to WordPress 6.1. * PHP 8.0, 8.1, and 8.2 compatibility fixes. * Fix a bug causing blank lines in content to be ignored when using the Regex Parser. * Fix a bug resulting in a PHP fatal error when IMPORT_DEBUG is enabled and a category creation error occurs. * Improved Unit testing & automated testing. = 0.7 = * Update minimum WordPress requirement to 3.7 and ensure compatibility with PHP 7.4. * Fix bug that caused not importing term meta. * Fix bug that caused slashes to be stripped from imported meta data. * Fix bug that prevented import of serialized meta data. * Fix file size check after download of remote files with HTTP compression enabled. * Improve accessibility of form fields by adding missing labels. * Improve imports for remote file URLs without name and/or extension. * Add support for `wp:base_blog_url` field to allow importing multiple files with WP-CLI. * Add support for term meta parsing when using the regular expressions or XML parser. * Developers: All PHP classes have been moved into their own files. * Developers: Allow to change `IMPORT_DEBUG` via `wp-config.php` and change default value to the value of `WP_DEBUG`. = 0.6.4 = * Improve PHP7 compatibility. * Fix bug that caused slashes to be stripped from imported comments. * Fix for various deprecation notices including `wp_get_http()` and `screen_icon()`. * Fix for importing export files with multiline term meta data. = 0.6.3 = * Add support for import term metadata. * Fix bug that caused slashes to be stripped from imported content. * Fix bug that caused characters to be stripped inside of CDATA in some cases. * Fix PHP notices. = 0.6.2 = * Add `wp_import_existing_post` filter, see [Trac ticket #33721](https://core.trac.wordpress.org/ticket/33721). = 0.6 = * Support for WXR 1.2 and multiple CDATA sections * Post aren't duplicates if their post_type's are different = 0.5.2 = * Double check that the uploaded export file exists before processing it. This prevents incorrect error messages when an export file is uploaded to a server with bad permissions and WordPress 3.3 or 3.3.1 is being used. = 0.5 = * Import comment meta (requires export from WordPress 3.2) * Minor bugfixes and enhancements = 0.4 = * Map comment user_id where possible * Import attachments from `wp:attachment_url` * Upload attachments to correct directory * Remap resized image URLs correctly = 0.3 = * Use an XML Parser if possible * Proper import support for nav menus * ... and much more, see [Trac ticket #15197](https://core.trac.wordpress.org/ticket/15197) = 0.1 = * Initial release == Frequently Asked Questions == = Help! I'm getting out of memory errors or a blank screen. = If your exported file is very large, the import script may run into your host's configured memory limit for PHP. A message like "Fatal error: Allowed memory size of 8388608 bytes exhausted" indicates that the script can't successfully import your XML file under the current PHP memory limit. If you have access to the php.ini file, you can manually increase the limit; if you do not (your WordPress installation is hosted on a shared server, for instance), you might have to break your exported XML file into several smaller pieces and run the import script one at a time. For those with shared hosting, the best alternative may be to consult hosting support to determine the safest approach for running the import. A host may be willing to temporarily lift the memory limit and/or run the process directly from their end. -- [Support Article: Importing Content](https://wordpress.org/support/article/importing-content/#before-importing) == Filters == The importer has a couple of filters to allow you to completely enable/block certain features: * `import_allow_create_users`: return false if you only want to allow mapping to existing users * `import_allow_fetch_attachments`: return false if you do not wish to allow importing and downloading of attachments * `import_attachment_size_limit`: return an integer value for the maximum file size in bytes to save (default is 0, which is unlimited) There are also a few actions available to hook into: * `import_start`: occurs after the export file has been uploaded and author import settings have been chosen * `import_end`: called after the last output from the importer class-wp-import.php 0000777 00000171343 15251156632 0010344 0 ustar 00 <?php /** * WordPress Importer class for managing the import process of a WXR file * * @package WordPress * @subpackage Importer */ use WordPress\DataLiberation\URL\WPURL; use function WordPress\DataLiberation\URL\wp_rewrite_urls; /** * WordPress importer class. */ class WP_Import extends WP_Importer { public $max_wxr_version = 1.2; // max. supported WXR version public $id; // WXR attachment ID // information to import from WXR file public $version; public $authors = array(); public $posts = array(); public $terms = array(); public $categories = array(); public $tags = array(); public $base_url = ''; public $base_url_parsed = null; public $site_url_parsed = null; // mappings from old information to new public $processed_authors = array(); public $author_mapping = array(); public $processed_terms = array(); public $processed_posts = array(); public $post_orphans = array(); public $processed_menu_items = array(); public $menu_item_orphans = array(); public $missing_menu_items = array(); public $fetch_attachments = false; public $url_remap = array(); public $featured_images = array(); /** * Import options. * * @since 0.9.1 * @var array */ public $options = array(); /** * Registered callback function for the WordPress Importer * * Manages the three separate stages of the WXR import process */ public function dispatch() { $this->header(); $step = empty( $_GET['step'] ) ? 0 : (int) $_GET['step']; switch ( $step ) { case 0: $this->greet(); break; case 1: check_admin_referer( 'import-upload' ); if ( $this->handle_upload() ) { $this->import_options(); } break; case 2: check_admin_referer( 'import-wordpress' ); $this->fetch_attachments = ( ! empty( $_POST['fetch_attachments'] ) && $this->allow_fetch_attachments() ); $this->id = (int) $_POST['import_id']; $file = get_attached_file( $this->id ); set_time_limit( 0 ); $this->import( $file, array( 'rewrite_urls' => '1' === $_POST['rewrite_urls'] ) ); break; } $this->footer(); } /** * The main controller for the actual import stage. * * @param string $file Path to the WXR file for importing * @param array $options Options to control import behavior. Supported: * - 'rewrite_urls' (bool) Enable rewriting URLs in post content/excerpt. */ public function import( $file, $options = array() ) { $options = wp_parse_args( $options, array( 'rewrite_urls' => false, ) ); $this->options = apply_filters( 'wp_import_options', $options ); add_filter( 'import_post_meta_key', array( $this, 'is_valid_meta_key' ) ); add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) ); $this->import_start( $file ); /** * If URL rewriting was requested but the WP version is too old, report * an error and disable it. * * More context: * WordPress 6.7 introduced WP_HTML_Tag_Processor::set_modifiable_text * required for wp_rewrite_urls to work. We could also offer a graceful * downgrade and support versions down to WordPress 6.5 where the required * WP_HTML_Tag_Processor::get_token_type() method was introduced. * * Alternatively, it might be possible to just rely on the HTML Processor * polyfill shipped with this plugin and make URL rewriting work in any * WordPress version. */ if ( $this->options['rewrite_urls'] && version_compare( get_bloginfo( 'version' ), '6.7', '<' ) ) { echo '<div class="error"><p><strong>' . __( 'URL rewriting requires WordPress 6.7 or newer. The import will continue without rewriting URLs.', 'wordpress-importer' ) . '</strong></p></div>'; $this->options['rewrite_urls'] = false; } // URL rewriting is only possible when we have the previous site base URL if ( $this->options['rewrite_urls'] && ! $this->base_url_parsed ) { $this->options['rewrite_urls'] = false; } $this->get_author_mapping(); wp_suspend_cache_invalidation( true ); $this->process_categories(); $this->process_tags(); $this->process_terms(); $this->process_posts(); wp_suspend_cache_invalidation( false ); // update incorrect/missing information in the DB $this->backfill_parents(); $this->backfill_attachment_urls(); $this->remap_featured_images(); $this->import_end(); } /** * Parses the WXR file and prepares us for the task of processing parsed data * * @param string $file Path to the WXR file for importing */ public function import_start( $file ) { if ( ! is_file( $file ) ) { echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong><br />'; echo __( 'The file does not exist, please try again.', 'wordpress-importer' ) . '</p>'; $this->footer(); die(); } $import_data = $this->parse( $file ); if ( is_wp_error( $import_data ) ) { /** @var WP_Error $import_error */ $import_error = $import_data; echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong><br />'; echo esc_html( $import_error->get_error_message() ) . '</p>'; $this->footer(); die(); } $this->version = $import_data['version']; $this->get_authors_from_import( $import_data ); $this->posts = $import_data['posts']; $this->terms = $import_data['terms']; $this->categories = $import_data['categories']; $this->tags = $import_data['tags']; $this->base_url = esc_url( $import_data['base_url'] ); /** * Add trailing slash to base URL and site URL. Without the trailing slashes, * the WHATWG URL spec tells us compare the parent pathname. For example: * * > is_child_url_of("https://example.com/path", "https://example.com/path-2") * true * * The example above actually ignores the `/path` and `/path-2` parts and only * compares the `example.com` parts. * * With the trailing slashes, the result is false: * * > is_child_url_of("https://example.com/path/", "https://example.com/path-2/") * false * * In this scenario, `/path/` and `/path-2/` are considered in the comparison. */ $base_url_with_trailing_slash = rtrim( $import_data['base_url'], '/' ) . '/'; $this->base_url_parsed = WPURL::parse( $base_url_with_trailing_slash ); $site_url_with_trailing_slash = rtrim( get_site_url(), '/' ) . '/'; $this->site_url_parsed = WPURL::parse( $site_url_with_trailing_slash ); wp_defer_term_counting( true ); wp_defer_comment_counting( true ); do_action( 'import_start' ); } /** * Performs post-import cleanup of files and the cache */ public function import_end() { wp_import_cleanup( $this->id ); wp_cache_flush(); foreach ( get_taxonomies() as $tax ) { delete_option( "{$tax}_children" ); _get_term_hierarchy( $tax ); } wp_defer_term_counting( false ); wp_defer_comment_counting( false ); echo '<p>' . __( 'All done.', 'wordpress-importer' ) . ' <a href="' . admin_url() . '">' . __( 'Have fun!', 'wordpress-importer' ) . '</a>' . '</p>'; echo '<p>' . __( 'Remember to update the passwords and roles of imported users.', 'wordpress-importer' ) . '</p>'; do_action( 'import_end' ); } /** * Handles the WXR upload and initial parsing of the file to prepare for * displaying author import options * * @return bool False if error uploading or invalid file, true otherwise */ public function handle_upload() { $file = wp_import_handle_upload(); if ( isset( $file['error'] ) ) { echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong><br />'; echo esc_html( $file['error'] ) . '</p>'; return false; } elseif ( ! file_exists( $file['file'] ) ) { echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong><br />'; printf( __( 'The export file could not be found at <code>%s</code>. It is likely that this was caused by a permissions problem.', 'wordpress-importer' ), esc_html( $file['file'] ) ); echo '</p>'; return false; } $this->id = (int) $file['id']; $import_data = $this->parse( $file['file'] ); if ( is_wp_error( $import_data ) ) { /** @var WP_Error $import_error */ $import_error = $import_data; echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong><br />'; echo esc_html( $import_error->get_error_message() ) . '</p>'; return false; } $this->version = $import_data['version']; if ( $this->version > $this->max_wxr_version ) { echo '<div class="error"><p><strong>'; printf( __( 'This WXR file (version %s) may not be supported by this version of the importer. Please consider updating.', 'wordpress-importer' ), esc_html( $import_data['version'] ) ); echo '</strong></p></div>'; } $this->get_authors_from_import( $import_data ); return true; } /** * Retrieve authors from parsed WXR data * * Uses the provided author information from WXR 1.1 files * or extracts info from each post for WXR 1.0 files * * @param array $import_data Data returned by a WXR parser */ public function get_authors_from_import( $import_data ) { if ( ! empty( $import_data['authors'] ) ) { $this->authors = $import_data['authors']; // no author information, grab it from the posts } else { foreach ( $import_data['posts'] as $post ) { $login = sanitize_user( $post['post_author'], true ); if ( empty( $login ) ) { printf( __( 'Failed to import author %s. Their posts will be attributed to the current user.', 'wordpress-importer' ), esc_html( $post['post_author'] ) ); echo '<br />'; continue; } if ( ! isset( $this->authors[ $login ] ) ) { $this->authors[ $login ] = array( 'author_login' => $login, 'author_display_name' => $post['post_author'], ); } } } } /** * Display pre-import options, author importing/mapping and option to * fetch attachments */ public function import_options() { $j = 0; // phpcs:disable Generic.WhiteSpace.ScopeIndent.Incorrect ?> <form action="<?php echo admin_url( 'admin.php?import=wordpress&step=2' ); ?>" method="post"> <?php wp_nonce_field( 'import-wordpress' ); ?> <input type="hidden" name="import_id" value="<?php echo $this->id; ?>" /> <?php if ( ! empty( $this->authors ) ) : ?> <h3><?php _e( 'Assign Authors', 'wordpress-importer' ); ?></h3> <p><?php _e( 'To make it simpler for you to edit and save the imported content, you may want to reassign the author of the imported item to an existing user of this site, such as your primary administrator account.', 'wordpress-importer' ); ?></p> <?php if ( $this->allow_create_users() ) : ?> <p><?php printf( __( 'If a new user is created by WordPress, a new password will be randomly generated and the new user’s role will be set as %s. Manually changing the new user’s details will be necessary.', 'wordpress-importer' ), esc_html( get_option( 'default_role' ) ) ); ?></p> <?php endif; ?> <ol id="authors"> <?php foreach ( $this->authors as $author ) : ?> <li><?php $this->author_select( $j++, $author ); ?></li> <?php endforeach; ?> </ol> <?php endif; ?> <?php if ( $this->allow_fetch_attachments() ) : ?> <h3><?php _e( 'Import Attachments', 'wordpress-importer' ); ?></h3> <p> <input type="checkbox" value="1" name="fetch_attachments" id="import-attachments" /> <label for="import-attachments"><?php _e( 'Download and import file attachments', 'wordpress-importer' ); ?></label> </p> <?php endif; ?> <h3><?php _e( 'Content Options', 'wordpress-importer' ); ?></h3> <p> <input type="checkbox" value="1" name="rewrite_urls" id="rewrite-urls" checked="checked" /> <label for="rewrite-urls"><?php _e( 'Change all imported URLs that currently link to the previous site so that they now link to this site', 'wordpress-importer' ); ?></label> </p> <p class="submit"><input type="submit" class="button" value="<?php esc_attr_e( 'Submit', 'wordpress-importer' ); ?>" /></p> </form> <?php // phpcs:enable Generic.WhiteSpace.ScopeIndent.Incorrect } /** * Display import options for an individual author. That is, either create * a new user based on import info or map to an existing user * * @param int $n Index for each author in the form * @param array $author Author information, e.g. login, display name, email */ public function author_select( $n, $author ) { _e( 'Import author:', 'wordpress-importer' ); echo ' <strong>' . esc_html( $author['author_display_name'] ); if ( '1.0' != $this->version ) { echo ' (' . esc_html( $author['author_login'] ) . ')'; } echo '</strong><br />'; if ( '1.0' != $this->version ) { echo '<div style="margin-left:18px">'; } $create_users = $this->allow_create_users(); if ( $create_users ) { echo '<label for="user_new_' . $n . '">'; if ( '1.0' != $this->version ) { _e( 'or create new user with login name:', 'wordpress-importer' ); $value = ''; } else { _e( 'as a new user:', 'wordpress-importer' ); $value = esc_attr( sanitize_user( $author['author_login'], true ) ); } echo '</label>'; echo ' <input type="text" id="user_new_' . $n . '" name="user_new[' . $n . ']" value="' . $value . '" /><br />'; } echo '<label for="imported_authors_' . $n . '">'; if ( ! $create_users && '1.0' == $this->version ) { _e( 'assign posts to an existing user:', 'wordpress-importer' ); } else { _e( 'or assign posts to an existing user:', 'wordpress-importer' ); } echo '</label>'; echo ' ' . wp_dropdown_users( array( 'name' => "user_map[$n]", 'id' => 'imported_authors_' . $n, 'multi' => true, 'show_option_all' => __( '- Select -', 'wordpress-importer' ), 'show' => 'display_name_with_login', 'echo' => 0, ) ); echo '<input type="hidden" name="imported_authors[' . $n . ']" value="' . esc_attr( $author['author_login'] ) . '" />'; if ( '1.0' != $this->version ) { echo '</div>'; } } /** * Map old author logins to local user IDs based on decisions made * in import options form. Can map to an existing user, create a new user * or falls back to the current user in case of error with either of the previous */ public function get_author_mapping() { if ( ! isset( $_POST['imported_authors'] ) ) { return; } $create_users = $this->allow_create_users(); foreach ( (array) $_POST['imported_authors'] as $i => $old_login ) { // Multisite adds strtolower to sanitize_user. Need to sanitize here to stop breakage in process_posts. $santized_old_login = sanitize_user( $old_login, true ); $old_id = isset( $this->authors[ $old_login ]['author_id'] ) ? intval( $this->authors[ $old_login ]['author_id'] ) : false; if ( ! empty( $_POST['user_map'][ $i ] ) ) { $user = get_userdata( intval( $_POST['user_map'][ $i ] ) ); if ( isset( $user->ID ) ) { if ( $old_id ) { $this->processed_authors[ $old_id ] = $user->ID; } $this->author_mapping[ $santized_old_login ] = $user->ID; } } elseif ( $create_users ) { if ( ! empty( $_POST['user_new'][ $i ] ) ) { $user_id = wp_create_user( $_POST['user_new'][ $i ], wp_generate_password() ); } elseif ( '1.0' != $this->version ) { $user_data = array( 'user_login' => $old_login, 'user_pass' => wp_generate_password(), 'user_email' => isset( $this->authors[ $old_login ]['author_email'] ) ? $this->authors[ $old_login ]['author_email'] : '', 'display_name' => $this->authors[ $old_login ]['author_display_name'], 'first_name' => isset( $this->authors[ $old_login ]['author_first_name'] ) ? $this->authors[ $old_login ]['author_first_name'] : '', 'last_name' => isset( $this->authors[ $old_login ]['author_last_name'] ) ? $this->authors[ $old_login ]['author_last_name'] : '', ); $user_id = wp_insert_user( $user_data ); } if ( ! is_wp_error( $user_id ) ) { if ( $old_id ) { $this->processed_authors[ $old_id ] = $user_id; } $this->author_mapping[ $santized_old_login ] = $user_id; } else { printf( __( 'Failed to create new user for %s. Their posts will be attributed to the current user.', 'wordpress-importer' ), esc_html( $this->authors[ $old_login ]['author_display_name'] ) ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { echo ' ' . $user_id->get_error_message(); } echo '<br />'; } } // failsafe: if the user_id was invalid, default to the current user if ( ! isset( $this->author_mapping[ $santized_old_login ] ) ) { if ( $old_id ) { $this->processed_authors[ $old_id ] = (int) get_current_user_id(); } $this->author_mapping[ $santized_old_login ] = (int) get_current_user_id(); } } } /** * Create new categories based on import information * * Doesn't create a new category if its slug already exists */ public function process_categories() { $this->categories = apply_filters( 'wp_import_categories', $this->categories ); if ( empty( $this->categories ) ) { return; } foreach ( $this->categories as $cat ) { $processed_category = $this->process_category( $cat ); if ( false === $processed_category ) { continue; } $this->processed_terms[ intval( $cat['term_id'] ) ] = $processed_category['term_id']; if ( $processed_category['created'] ) { $this->process_termmeta( $cat, $processed_category['term_id'] ); } } unset( $this->categories ); } protected function process_category( $category ) { $term_id = term_exists( $category['category_nicename'], 'category' ); if ( $term_id ) { if ( is_array( $term_id ) ) { $term_id = $term_id['term_id']; } return array( 'created' => false, 'term_id' => $term_id, ); } $parent = empty( $category['category_parent'] ) ? 0 : category_exists( $category['category_parent'] ); $description = isset( $category['category_description'] ) ? $category['category_description'] : ''; $data = array( 'category_nicename' => $category['category_nicename'], 'category_parent' => $parent, 'cat_name' => wp_slash( $category['cat_name'] ), 'category_description' => wp_slash( $description ), ); $id = wp_insert_category( $data, true ); if ( is_wp_error( $id ) || $id <= 0 ) { printf( __( 'Failed to import category %s', 'wordpress-importer' ), esc_html( $category['category_nicename'] ) ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { echo ': ' . $id->get_error_message(); } echo '<br />'; return false; } if ( isset( $category['term_id'] ) ) { $this->processed_terms[ intval( $category['term_id'] ) ] = $id; } return array( 'created' => true, 'term_id' => $id, ); } /** * Create new post tags based on import information * * Doesn't create a tag if its slug already exists */ public function process_tags() { $this->tags = apply_filters( 'wp_import_tags', $this->tags ); if ( empty( $this->tags ) ) { return; } foreach ( $this->tags as $tag ) { $processed_tag = $this->process_tag( $tag ); if ( false === $processed_tag ) { continue; } if ( isset( $tag['term_id'] ) ) { $this->processed_terms[ intval( $tag['term_id'] ) ] = $processed_tag['term_id']; } if ( $processed_tag['created'] ) { $this->process_termmeta( $tag, $processed_tag['term_id'] ); } } unset( $this->tags ); } protected function process_tag( $tag ) { $term_id = term_exists( $tag['tag_slug'], 'post_tag' ); if ( $term_id ) { if ( is_array( $term_id ) ) { $term_id = $term_id['term_id']; } if ( isset( $tag['term_id'] ) ) { $this->processed_terms[ intval( $tag['term_id'] ) ] = (int) $term_id; } return array( 'created' => false, 'term_id' => (int) $term_id, ); } $description = isset( $tag['tag_description'] ) ? $tag['tag_description'] : ''; $args = array( 'slug' => $tag['tag_slug'], 'description' => wp_slash( $description ), ); $id = wp_insert_term( wp_slash( $tag['tag_name'] ), 'post_tag', $args ); if ( is_wp_error( $id ) ) { printf( __( 'Failed to import post tag %s', 'wordpress-importer' ), esc_html( $tag['tag_name'] ) ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { echo ': ' . $id->get_error_message(); } echo '<br />'; return false; } if ( isset( $tag['term_id'] ) ) { $this->processed_terms[ intval( $tag['term_id'] ) ] = (int) $id['term_id']; } return array( 'created' => true, 'term_id' => (int) $id['term_id'], ); } /** * Create new terms based on import information * * Doesn't create a term its slug already exists */ public function process_terms() { $this->terms = apply_filters( 'wp_import_terms', $this->terms ); if ( empty( $this->terms ) ) { return; } foreach ( $this->terms as $term ) { $processed_term = $this->process_term( $term ); if ( false === $processed_term ) { continue; } if ( isset( $term['term_id'] ) ) { $this->processed_terms[ intval( $term['term_id'] ) ] = $processed_term['term_id']; } if ( $processed_term['created'] ) { $this->process_termmeta( $term, $processed_term['term_id'] ); } } unset( $this->terms ); } protected function process_term( $term ) { $term_id = term_exists( $term['slug'], $term['term_taxonomy'] ); if ( $term_id ) { if ( is_array( $term_id ) ) { $term_id = $term_id['term_id']; } return array( 'created' => false, 'term_id' => (int) $term_id, ); } if ( empty( $term['term_parent'] ) ) { $parent = 0; } else { $parent = term_exists( $term['term_parent'], $term['term_taxonomy'] ); if ( is_array( $parent ) ) { $parent = $parent['term_id']; } } $description = isset( $term['term_description'] ) ? $term['term_description'] : ''; $args = array( 'slug' => $term['slug'], 'description' => wp_slash( $description ), 'parent' => (int) $parent, ); $id = wp_insert_term( wp_slash( $term['term_name'] ), $term['term_taxonomy'], $args ); if ( is_wp_error( $id ) ) { printf( __( 'Failed to import %1$s %2$s', 'wordpress-importer' ), esc_html( $term['term_taxonomy'] ), esc_html( $term['term_name'] ) ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { echo ': ' . $id->get_error_message(); } echo '<br />'; return false; } return array( 'created' => true, 'term_id' => (int) $id['term_id'], ); } /** * Add metadata to imported term. * * @since 0.6.2 * * @param array $term Term data from WXR import. * @param int $term_id ID of the newly created term. */ protected function process_termmeta( $term, $term_id ) { if ( ! isset( $term['termmeta'] ) ) { $term['termmeta'] = array(); } /** * Filters the metadata attached to an imported term. * * @since 0.6.2 * * @param array $termmeta Array of term meta. * @param int $term_id ID of the newly created term. * @param array $term Term data from the WXR import. */ $term['termmeta'] = apply_filters( 'wp_import_term_meta', $term['termmeta'], $term_id, $term ); if ( empty( $term['termmeta'] ) ) { return; } foreach ( $term['termmeta'] as $meta ) { /** * Filters the meta key for an imported piece of term meta. * * @since 0.6.2 * * @param string $meta_key Meta key. * @param int $term_id ID of the newly created term. * @param array $term Term data from the WXR import. */ $key = apply_filters( 'import_term_meta_key', $meta['key'], $term_id, $term ); if ( ! $key ) { continue; } // Export gets meta straight from the DB so could have a serialized string $value = $this->maybe_unserialize( $meta['value'] ); add_term_meta( $term_id, wp_slash( $key ), wp_slash_strings_only( $value ) ); /** * Fires after term meta is imported. * * @since 0.6.2 * * @param int $term_id ID of the newly created term. * @param string $key Meta key. * @param mixed $value Meta value. */ do_action( 'import_term_meta', $term_id, $key, $value ); } } /** * Create new posts based on import information * * Posts marked as having a parent which doesn't exist will become top level items. * Doesn't create a new post if: the post type doesn't exist, the given post ID * is already noted as imported or a post with the same title and date already exists. * Note that new/updated terms, comments and meta are imported for the last of the above. */ public function process_posts() { $this->posts = apply_filters( 'wp_import_posts', $this->posts ); foreach ( $this->posts as $post ) { $post = apply_filters( 'wp_import_post_data_raw', $post ); if ( ! post_type_exists( $post['post_type'] ) ) { printf( __( 'Failed to import “%1$s”: Invalid post type %2$s', 'wordpress-importer' ), esc_html( $post['post_title'] ), esc_html( $post['post_type'] ) ); echo '<br />'; do_action( 'wp_import_post_exists', $post ); continue; } if ( isset( $this->processed_posts[ $post['post_id'] ] ) && ! empty( $post['post_id'] ) ) { continue; } if ( 'auto-draft' == $post['status'] ) { continue; } if ( 'nav_menu_item' == $post['post_type'] ) { $this->process_menu_item( $post ); continue; } $post_type_object = get_post_type_object( $post['post_type'] ); $post_exists = post_exists( $post['post_title'], '', $post['post_date'], $post['post_type'] ); /** * Filter ID of the existing post corresponding to post currently importing. * * Return 0 to force the post to be imported. Filter the ID to be something else * to override which existing post is mapped to the imported post. * * @see post_exists() * @since 0.6.2 * * @param int $post_exists Post ID, or 0 if post did not exist. * @param array $post The post array to be inserted. */ $post_exists = apply_filters( 'wp_import_existing_post', $post_exists, $post ); if ( $post_exists && get_post_type( $post_exists ) == $post['post_type'] ) { printf( __( '%1$s “%2$s” already exists.', 'wordpress-importer' ), $post_type_object->labels->singular_name, esc_html( $post['post_title'] ) ); echo '<br />'; $comment_post_id = $post_exists; $post_id = $post_exists; $this->processed_posts[ intval( $post['post_id'] ) ] = intval( $post_exists ); } else { $post_parent = (int) $post['post_parent']; if ( $post_parent ) { // if we already know the parent, map it to the new local ID if ( isset( $this->processed_posts[ $post_parent ] ) ) { $post_parent = $this->processed_posts[ $post_parent ]; // otherwise record the parent for later } else { $this->post_orphans[ intval( $post['post_id'] ) ] = $post_parent; $post_parent = 0; } } // map the post author $author = sanitize_user( $post['post_author'], true ); if ( isset( $this->author_mapping[ $author ] ) ) { $author = $this->author_mapping[ $author ]; } else { $author = (int) get_current_user_id(); } $postdata = array( 'import_id' => $post['post_id'], 'post_author' => $author, 'post_date' => $post['post_date'], 'post_date_gmt' => $post['post_date_gmt'], 'post_content' => $post['post_content'], 'post_excerpt' => $post['post_excerpt'], 'post_title' => $post['post_title'], 'post_status' => $post['status'], 'post_name' => $post['post_name'], 'comment_status' => $post['comment_status'], 'ping_status' => $post['ping_status'], 'guid' => $post['guid'], 'post_parent' => $post_parent, 'menu_order' => $post['menu_order'], 'post_type' => $post['post_type'], 'post_password' => $post['post_password'], ); if ( $this->options['rewrite_urls'] ) { $url_mapping = array( $this->base_url_parsed->toString() => $this->site_url_parsed, ); $postdata['post_content'] = wp_rewrite_urls( array( 'block_markup' => $postdata['post_content'], 'url-mapping' => $url_mapping, ) ); $postdata['post_excerpt'] = wp_rewrite_urls( array( 'block_markup' => $postdata['post_excerpt'], 'url-mapping' => $url_mapping, ) ); } $original_post_id = $post['post_id']; $postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $post ); $postdata = wp_slash( $postdata ); if ( 'attachment' == $postdata['post_type'] ) { $remote_url = ! empty( $post['attachment_url'] ) ? $post['attachment_url'] : $post['guid']; // try to use _wp_attached file for upload folder placement to ensure the same location as the export site // e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload() $postdata['upload_date'] = $post['post_date']; if ( isset( $post['postmeta'] ) ) { foreach ( $post['postmeta'] as $meta ) { if ( '_wp_attached_file' == $meta['key'] ) { if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta['value'], $matches ) ) { $postdata['upload_date'] = $matches[0]; } break; } } } $comment_post_id = $this->process_attachment( $postdata, $remote_url ); $post_id = $comment_post_id; } else { $comment_post_id = wp_insert_post( $postdata, true ); $post_id = $comment_post_id; do_action( 'wp_import_insert_post', $post_id, $original_post_id, $postdata, $post ); } if ( is_wp_error( $post_id ) ) { printf( __( 'Failed to import %1$s “%2$s”', 'wordpress-importer' ), $post_type_object->labels->singular_name, esc_html( $post['post_title'] ) ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { echo ': ' . $post_id->get_error_message(); } echo '<br />'; continue; } if ( 1 == $post['is_sticky'] ) { stick_post( $post_id ); } } // map pre-import ID to local ID $this->processed_posts[ intval( $post['post_id'] ) ] = (int) $post_id; if ( ! isset( $post['terms'] ) ) { $post['terms'] = array(); } $post['terms'] = apply_filters( 'wp_import_post_terms', $post['terms'], $post_id, $post ); // add categories, tags and other terms if ( ! empty( $post['terms'] ) ) { $this->process_post_terms( $post['terms'], $post_id, $post ); unset( $post['terms'] ); } if ( ! isset( $post['comments'] ) ) { $post['comments'] = array(); } $post['comments'] = apply_filters( 'wp_import_post_comments', $post['comments'], $post_id, $post ); // add/update comments if ( ! empty( $post['comments'] ) ) { $this->process_post_comments( $post['comments'], (bool) $post_exists, $comment_post_id, $post ); unset( $post['comments'] ); } if ( ! isset( $post['postmeta'] ) ) { $post['postmeta'] = array(); } $post['postmeta'] = apply_filters( 'wp_import_post_meta', $post['postmeta'], $post_id, $post ); $this->process_post_metas( $post['postmeta'], $post_id, $post ); } unset( $this->posts ); } /** * Add or update post meta for an imported post. * * @param array $post_metas Array of post meta entries. * @param int $post_id ID of the just imported post. * @param array $post Raw post data from the WXR file. */ protected function process_post_metas( $post_metas, $post_id, $post ) { if ( empty( $post_metas ) ) { return; } foreach ( $post_metas as $meta ) { $this->process_post_meta( $meta, $post_id, $post ); } } /** * Process a single post meta entry. * * @param array $meta Post meta data. * @param int $post_id ID of the just imported post. * @param array $post Raw post data from the WXR file. */ protected function process_post_meta( $meta, $post_id, $post ) { $key = apply_filters( 'import_post_meta_key', $meta['key'], $post_id, $post ); $value = false; if ( '_edit_last' == $key ) { if ( isset( $this->processed_authors[ intval( $meta['value'] ) ] ) ) { $value = $this->processed_authors[ intval( $meta['value'] ) ]; } else { $key = false; } } if ( ! $key ) { return; } // export gets meta straight from the DB so could have a serialized string if ( ! $value ) { $value = $this->maybe_unserialize( $meta['value'] ); } add_post_meta( $post_id, wp_slash( $key ), wp_slash_strings_only( $value ) ); do_action( 'import_post_meta', $post_id, $key, $value ); // if the post has a featured image, take note of this in case of remap if ( '_thumbnail_id' == $key ) { $this->featured_images[ $post_id ] = (int) $value; } } /** * Process comments for a post being imported. * * @param array $comments Comment data from the WXR file. * @param bool $post_exists Whether the post already exists. * @param int $comment_post_id Local post ID for the imported comments. * @param array $post Original post array from the WXR file. */ protected function process_post_comments( $comments, $post_exists, $comment_post_id, $post ) { $num_comments = 0; $newcomments = array(); $inserted_comments = array(); foreach ( $comments as $comment ) { $comment_id = $comment['comment_id']; $newcomments[ $comment_id ] = array( 'comment_post_ID' => $comment_post_id, 'comment_author' => $comment['comment_author'], 'comment_author_email' => $comment['comment_author_email'], 'comment_author_IP' => $comment['comment_author_IP'], 'comment_author_url' => $comment['comment_author_url'], 'comment_date' => $comment['comment_date'], 'comment_date_gmt' => $comment['comment_date_gmt'], 'comment_content' => $comment['comment_content'], 'comment_approved' => $comment['comment_approved'], 'comment_type' => $comment['comment_type'], 'comment_parent' => $comment['comment_parent'], 'commentmeta' => isset( $comment['commentmeta'] ) ? $comment['commentmeta'] : array(), ); if ( isset( $this->processed_authors[ $comment['comment_user_id'] ] ) ) { $newcomments[ $comment_id ]['user_id'] = $this->processed_authors[ $comment['comment_user_id'] ]; } } if ( empty( $newcomments ) ) { return; } ksort( $newcomments ); foreach ( $newcomments as $key => $comment ) { if ( isset( $inserted_comments[ $comment['comment_parent'] ] ) ) { $comment['comment_parent'] = $inserted_comments[ $comment['comment_parent'] ]; } $inserted_comment_id = $this->process_post_comment( $comment, $post_exists, $comment_post_id ); if ( $inserted_comment_id ) { do_action( 'wp_import_insert_comment', $inserted_comment_id, $comment, $comment_post_id, $post ); $this->process_post_comment_metas( $inserted_comment_id, $comment['commentmeta'] ); $inserted_comments[ $key ] = $inserted_comment_id; ++$num_comments; } } } /** * Insert an individual comment for the post during import. * * @param array $comment Comment data to insert. * @param bool $post_exists Whether the post already exists. * @param int $comment_post_id Local post ID for the imported comment. * @param array $post Original post array from the WXR file. * @return int|false Inserted comment ID on success, false otherwise. */ protected function process_post_comment( $comment, $post_exists, $comment_post_id ) { if ( $post_exists && comment_exists( $comment['comment_author'], $comment['comment_date'] ) ) { return false; } $comment['comment_post_ID'] = $comment_post_id; $comment_data = wp_slash( $comment ); unset( $comment_data['commentmeta'] ); // Handled separately, wp_insert_comment() also expects `comment_meta`. $comment_data = wp_filter_comment( $comment_data ); return wp_insert_comment( $comment_data ); } /** * Process comment meta for an imported comment. * * @param int $comment_id ID of the comment being imported. * @param array $commentmeta Comment meta data for the inserted comment. */ protected function process_post_comment_metas( $comment_id, $commentmeta ) { if ( empty( $commentmeta ) ) { return; } foreach ( $commentmeta as $meta ) { $this->process_post_comment_meta( $comment_id, $meta ); } } /** * Process a single comment meta entry for an imported comment. * * @param int $comment_id ID of the comment being imported. * @param array $meta Single meta entry (key/value) for the comment. */ protected function process_post_comment_meta( $comment_id, $meta ) { if ( ! isset( $meta['key'], $meta['value'] ) ) { return; } $value = $this->maybe_unserialize( $meta['value'] ); add_comment_meta( $comment_id, wp_slash( $meta['key'] ), wp_slash_strings_only( $value ) ); } /** * Add categories, tags, and other taxonomies to a post. * * @param array $terms Terms to be added to the post. * @param int $post_id The ID of the post being processed. * @param array $post The raw post data from the import file. */ protected function process_post_terms( $terms, $post_id, $post ) { if ( empty( $terms ) ) { return; } $terms_to_set = array(); foreach ( $terms as $term ) { $processed_term = $this->process_post_term( $term, $post_id, $post ); if ( $processed_term ) { $taxonomy = $processed_term['taxonomy']; $terms_to_set[ $taxonomy ][] = $processed_term['term_id']; } } foreach ( $terms_to_set as $tax => $ids ) { $tt_ids = wp_set_post_terms( $post_id, $ids, $tax ); do_action( 'wp_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $post ); } } /** * Ensure a single term exists and return its taxonomy mapping for a post. * * @param array $term Term data from the import file. * @param int $post_id The ID of the post being processed. * @param array $post The raw post data from the import file. * @return array|false { * Mapping of taxonomy to term ID or false on failure. * * @type string $taxonomy Taxonomy slug. * @type int $term_id Term ID. * } */ protected function process_post_term( $term, $post_id, $post ) { // Back compat with WXR 1.0 map 'tag' to 'post_tag'. $taxonomy = ( 'tag' == $term['domain'] ) ? 'post_tag' : $term['domain']; $term_exists = term_exists( $term['slug'], $taxonomy ); $term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists; if ( ! $term_id ) { $t = wp_insert_term( $term['name'], $taxonomy, array( 'slug' => $term['slug'] ) ); if ( is_wp_error( $t ) ) { printf( __( 'Failed to import %1$s %2$s', 'wordpress-importer' ), esc_html( $taxonomy ), esc_html( $term['name'] ) ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { echo ': ' . $t->get_error_message(); } echo '<br />'; do_action( 'wp_import_insert_term_failed', $t, $term, $post_id, $post ); return false; } $term_id = $t['term_id']; do_action( 'wp_import_insert_term', $t, $term, $post_id, $post ); } return array( 'taxonomy' => $taxonomy, 'term_id' => intval( $term_id ), ); } /** * Attempt to create a new menu item from import data * * Fails for draft, orphaned menu items and those without an associated nav_menu * or an invalid nav_menu term. If the post type or term object which the menu item * represents doesn't exist then the menu item will not be imported (waits until the * end of the import to retry again before discarding). * * @param array $item Menu item details from WXR file */ public function process_menu_item( $item ) { // skip draft, orphaned menu items if ( 'draft' == $item['status'] ) { return; } $menu_slug = false; if ( isset( $item['terms'] ) ) { // loop through terms, assume first nav_menu term is correct menu foreach ( $item['terms'] as $term ) { if ( 'nav_menu' == $term['domain'] ) { $menu_slug = $term['slug']; break; } } } // no nav_menu term associated with this menu item if ( ! $menu_slug ) { _e( 'Menu item skipped due to missing menu slug', 'wordpress-importer' ); echo '<br />'; return; } $menu_id = term_exists( $menu_slug, 'nav_menu' ); if ( ! $menu_id ) { printf( __( 'Menu item skipped due to invalid menu slug: %s', 'wordpress-importer' ), esc_html( $menu_slug ) ); echo '<br />'; return; } else { $menu_id = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id; } foreach ( $item['postmeta'] as $meta ) { ${$meta['key']} = $meta['value']; } if ( 'taxonomy' == $_menu_item_type && isset( $this->processed_terms[ intval( $_menu_item_object_id ) ] ) ) { $_menu_item_object_id = $this->processed_terms[ intval( $_menu_item_object_id ) ]; } elseif ( 'post_type' == $_menu_item_type && isset( $this->processed_posts[ intval( $_menu_item_object_id ) ] ) ) { $_menu_item_object_id = $this->processed_posts[ intval( $_menu_item_object_id ) ]; } elseif ( 'custom' != $_menu_item_type ) { // associated object is missing or not imported yet, we'll retry later $this->missing_menu_items[] = $item; return; } if ( isset( $this->processed_menu_items[ intval( $_menu_item_menu_item_parent ) ] ) ) { $_menu_item_menu_item_parent = $this->processed_menu_items[ intval( $_menu_item_menu_item_parent ) ]; } elseif ( $_menu_item_menu_item_parent ) { $this->menu_item_orphans[ intval( $item['post_id'] ) ] = (int) $_menu_item_menu_item_parent; $_menu_item_menu_item_parent = 0; } // wp_update_nav_menu_item expects CSS classes as a space separated string $_menu_item_classes = $this->maybe_unserialize( $_menu_item_classes ); if ( is_array( $_menu_item_classes ) ) { $_menu_item_classes = implode( ' ', $_menu_item_classes ); } $args = array( 'menu-item-object-id' => $_menu_item_object_id, 'menu-item-object' => $_menu_item_object, 'menu-item-parent-id' => $_menu_item_menu_item_parent, 'menu-item-position' => intval( $item['menu_order'] ), 'menu-item-type' => $_menu_item_type, 'menu-item-title' => $item['post_title'], 'menu-item-url' => $_menu_item_url, 'menu-item-description' => $item['post_content'], 'menu-item-attr-title' => $item['post_excerpt'], 'menu-item-target' => $_menu_item_target, 'menu-item-classes' => $_menu_item_classes, 'menu-item-xfn' => $_menu_item_xfn, 'menu-item-status' => $item['status'], ); $id = wp_update_nav_menu_item( $menu_id, 0, $args ); if ( $id && ! is_wp_error( $id ) ) { $this->processed_menu_items[ intval( $item['post_id'] ) ] = (int) $id; } } /** * If fetching attachments is enabled then attempt to create a new attachment * * @param array $post Attachment post details from WXR * @param string $url URL to fetch attachment from * @return int|WP_Error Post ID on success, WP_Error otherwise */ public function process_attachment( $post, $url ) { if ( ! $this->fetch_attachments ) { return new WP_Error( 'attachment_processing_error', __( 'Fetching attachments is not enabled', 'wordpress-importer' ) ); } // if the URL is absolute, but does not contain address, then upload it assuming base_site_url if ( preg_match( '|^/[\w\W]+$|', $url ) ) { $url = rtrim( $this->base_url, '/' ) . $url; } $upload = $this->fetch_remote_file( $url, $post ); if ( is_wp_error( $upload ) ) { return $upload; } $info = wp_check_filetype( $upload['file'] ); if ( $info ) { $post['post_mime_type'] = $info['type']; } else { return new WP_Error( 'attachment_processing_error', __( 'Invalid file type', 'wordpress-importer' ) ); } $post['guid'] = $upload['url']; // as per wp-admin/includes/upload.php $post_id = wp_insert_attachment( $post, $upload['file'] ); wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) ); // remap resized image URLs, works by stripping the extension and remapping the URL stub. if ( preg_match( '!^image/!', $info['type'] ) ) { $parts = pathinfo( $url ); $name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2 $parts_new = pathinfo( $upload['url'] ); $name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" ); $this->url_remap[ $parts['dirname'] . '/' . $name ] = $parts_new['dirname'] . '/' . $name_new; } return $post_id; } /** * Attempt to download a remote file attachment * * @param string $url URL of item to fetch * @param array $post Attachment details * @return array|WP_Error Local file location details on success, WP_Error otherwise */ public function fetch_remote_file( $url, $post ) { // Extract the file name from the URL. $path = parse_url( $url, PHP_URL_PATH ); $file_name = ''; if ( is_string( $path ) ) { $file_name = basename( $path ); } if ( ! $file_name ) { $file_name = md5( $url ); } $tmp_file_name = wp_tempnam( $file_name ); if ( ! $tmp_file_name ) { return new WP_Error( 'import_no_file', __( 'Could not create temporary file.', 'wordpress-importer' ) ); } // Fetch the remote URL and write it to the placeholder file. $remote_response = wp_safe_remote_get( $url, array( 'timeout' => 300, 'stream' => true, 'filename' => $tmp_file_name, 'headers' => array( 'Accept-Encoding' => 'identity', ), ) ); if ( is_wp_error( $remote_response ) ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', sprintf( /* translators: 1: The WordPress error message. 2: The WordPress error code. */ __( 'Request failed due to an error: %1$s (%2$s)', 'wordpress-importer' ), esc_html( $remote_response->get_error_message() ), esc_html( $remote_response->get_error_code() ) ) ); } $remote_response_code = (int) wp_remote_retrieve_response_code( $remote_response ); // Make sure the fetch was successful. if ( 200 !== $remote_response_code ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', sprintf( /* translators: 1: The HTTP error message. 2: The HTTP error code. */ __( 'Remote server returned the following unexpected result: %1$s (%2$s)', 'wordpress-importer' ), get_status_header_desc( $remote_response_code ), esc_html( $remote_response_code ) ) ); } $headers = wp_remote_retrieve_headers( $remote_response ); // Request failed. if ( ! $headers ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', __( 'Remote server did not respond', 'wordpress-importer' ) ); } $filesize = (int) filesize( $tmp_file_name ); if ( 0 === $filesize ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', __( 'Zero size file downloaded', 'wordpress-importer' ) ); } if ( ! isset( $headers['content-encoding'] ) && isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', __( 'Downloaded file has incorrect size', 'wordpress-importer' ) ); } $max_size = (int) $this->max_attachment_size(); if ( ! empty( $max_size ) && $filesize > $max_size ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', sprintf( __( 'Remote file is too large, limit is %s', 'wordpress-importer' ), size_format( $max_size ) ) ); } // Override file name with Content-Disposition header value. if ( ! empty( $headers['content-disposition'] ) ) { $file_name_from_disposition = self::get_filename_from_disposition( (array) $headers['content-disposition'] ); if ( $file_name_from_disposition ) { $file_name = $file_name_from_disposition; } } // Set file extension if missing. $file_ext = pathinfo( $file_name, PATHINFO_EXTENSION ); if ( ! $file_ext && ! empty( $headers['content-type'] ) ) { $extension = self::get_file_extension_by_mime_type( $headers['content-type'] ); if ( $extension ) { $file_name = "{$file_name}.{$extension}"; } } // Handle the upload like _wp_handle_upload() does. $wp_filetype = wp_check_filetype_and_ext( $tmp_file_name, $file_name ); $ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext']; $type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type']; $proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename']; // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect. if ( $proper_filename ) { $file_name = $proper_filename; } if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) { return new WP_Error( 'import_file_error', __( 'Sorry, this file type is not permitted for security reasons.', 'wordpress-importer' ) ); } $uploads = wp_upload_dir( $post['upload_date'] ); if ( ! ( $uploads && false === $uploads['error'] ) ) { return new WP_Error( 'upload_dir_error', $uploads['error'] ); } // Move the file to the uploads dir. $file_name = wp_unique_filename( $uploads['path'], $file_name ); $new_file = $uploads['path'] . "/$file_name"; $move_new_file = copy( $tmp_file_name, $new_file ); if ( ! $move_new_file ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', __( 'The uploaded file could not be moved', 'wordpress-importer' ) ); } // Set correct file permissions. $stat = stat( dirname( $new_file ) ); $perms = $stat['mode'] & 0000666; chmod( $new_file, $perms ); $upload = array( 'file' => $new_file, 'url' => $uploads['url'] . "/$file_name", 'type' => $wp_filetype['type'], 'error' => false, ); /** * When URL rewriting is enabled, posts such as this one: * * <img src="https://example.com/subpath/wp-content/uploads/2008/06/canola2.jpg" /> * * Are already stored as: * * <img src="https://example.org/wp-content/uploads/2008/06/canola2.jpg" /> * * Therefore, we can't just remap the old URL to the new URL here. This substring * is no longer present in the post: * * https://example.com/subpath/wp-content/uploads/2008/06/canola2.jpg * * We need to replace the base URL in the media file URL the same way as we did * in the post content: * * https://example.org/wp-content/uploads/2008/06/canola2.jpg * * Only from there we can remap that URL to the new media files URL: * * https://example.org/wp-content/uploads/canola2.jpg" * ^ there may be no 2008/06 on the target site. */ if ( $this->options['rewrite_urls'] ) { $url_candidate = WPURL::replace_base_url( $url, array( 'old_base_url' => $this->base_url_parsed, 'new_base_url' => $this->site_url_parsed, ) ); if ( false !== $url_candidate ) { $url = (string) $url_candidate; } $guid_candidate = WPURL::replace_base_url( $post['guid'], array( 'old_base_url' => $this->base_url_parsed, 'new_base_url' => $this->site_url_parsed, ) ); if ( false !== $guid_candidate ) { $post['guid'] = (string) $guid_candidate; } if ( isset( $headers['x-final-location'] ) ) { $final_location_candidate = WPURL::replace_base_url( $headers['x-final-location'], array( 'old_base_url' => $this->base_url_parsed, 'new_base_url' => $this->site_url_parsed, ) ); if ( false !== $final_location_candidate ) { $headers['x-final-location'] = (string) $final_location_candidate; } } } $this->url_remap[ $url ] = $upload['url']; $this->url_remap[ $post['guid'] ] = $upload['url']; // r13735, really needed? // keep track of the destination if the remote url is redirected somewhere else if ( isset( $headers['x-final-location'] ) && $headers['x-final-location'] != $url ) { $this->url_remap[ $headers['x-final-location'] ] = $upload['url']; } return $upload; } /** * Attempt to associate posts and menu items with previously missing parents * * An imported post's parent may not have been imported when it was first created * so try again. Similarly for child menu items and menu items which were missing * the object (e.g. post) they represent in the menu */ public function backfill_parents() { global $wpdb; // find parents for post orphans foreach ( $this->post_orphans as $child_id => $parent_id ) { $local_child_id = false; $local_parent_id = false; if ( isset( $this->processed_posts[ $child_id ] ) ) { $local_child_id = $this->processed_posts[ $child_id ]; } if ( isset( $this->processed_posts[ $parent_id ] ) ) { $local_parent_id = $this->processed_posts[ $parent_id ]; } if ( $local_child_id && $local_parent_id ) { $wpdb->update( $wpdb->posts, array( 'post_parent' => $local_parent_id ), array( 'ID' => $local_child_id ), '%d', '%d' ); clean_post_cache( $local_child_id ); } } // all other posts/terms are imported, retry menu items with missing associated object $missing_menu_items = $this->missing_menu_items; foreach ( $missing_menu_items as $item ) { $this->process_menu_item( $item ); } // find parents for menu item orphans foreach ( $this->menu_item_orphans as $child_id => $parent_id ) { $local_child_id = 0; $local_parent_id = 0; if ( isset( $this->processed_menu_items[ $child_id ] ) ) { $local_child_id = $this->processed_menu_items[ $child_id ]; } if ( isset( $this->processed_menu_items[ $parent_id ] ) ) { $local_parent_id = $this->processed_menu_items[ $parent_id ]; } if ( $local_child_id && $local_parent_id ) { update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id ); } } } /** * Use stored mapping information to update old attachment URLs */ public function backfill_attachment_urls() { global $wpdb; // make sure we do the longest urls first, in case one is a substring of another uksort( $this->url_remap, array( &$this, 'cmpr_strlen' ) ); foreach ( $this->url_remap as $from_url => $to_url ) { // remap urls in post_content $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url ) ); // remap enclosure urls $result = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url ) ); } } /** * Update _thumbnail_id meta to new, imported attachment IDs */ public function remap_featured_images() { // cycle through posts that have a featured image foreach ( $this->featured_images as $post_id => $value ) { if ( isset( $this->processed_posts[ $value ] ) ) { $new_id = $this->processed_posts[ $value ]; // only update if there's a difference if ( $new_id != $value ) { update_post_meta( $post_id, '_thumbnail_id', $new_id ); } } } } /** * Parse a WXR file * * @param string $file Path to WXR file for parsing * @return array Information gathered from the WXR file */ public function parse( $file ) { $parser = new WXR_Parser(); return $parser->parse( $file ); } // Display import page title public function header() { echo '<div class="wrap">'; echo '<h2>' . __( 'Import WordPress', 'wordpress-importer' ) . '</h2>'; $updates = get_plugin_updates(); $basename = plugin_basename( __FILE__ ); if ( isset( $updates[ $basename ] ) ) { $update = $updates[ $basename ]; echo '<div class="error"><p><strong>'; printf( __( 'A new version of this importer is available. Please update to version %s to ensure compatibility with newer export files.', 'wordpress-importer' ), $update->update->new_version ); echo '</strong></p></div>'; } } // Close div.wrap public function footer() { echo '</div>'; } /** * Display introductory text and file upload form */ public function greet() { echo '<div class="narrow">'; echo '<p>' . __( 'Howdy! Upload your WordPress eXtended RSS (WXR) file and we’ll import the posts, pages, comments, custom fields, categories, and tags into this site.', 'wordpress-importer' ) . '</p>'; echo '<p>' . __( 'Choose a WXR (.xml) file to upload, then click Upload file and import.', 'wordpress-importer' ) . '</p>'; wp_import_upload_form( 'admin.php?import=wordpress&step=1' ); echo '</div>'; } /** * Decide if the given meta key maps to information we will want to import * * @param string $key The meta key to check * @return string|bool The key if we do want to import, false if not */ public function is_valid_meta_key( $key ) { // skip attachment metadata since we'll regenerate it from scratch // skip _edit_lock as not relevant for import if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ), true ) ) { return false; } return $key; } /** * Decide whether or not the importer is allowed to create users. * Default is true, can be filtered via import_allow_create_users * * @return bool True if creating users is allowed */ public function allow_create_users() { return apply_filters( 'import_allow_create_users', true ); } /** * Decide whether or not the importer should attempt to download attachment files. * Default is true, can be filtered via import_allow_fetch_attachments. The choice * made at the import options screen must also be true, false here hides that checkbox. * * @return bool True if downloading attachments is allowed */ public function allow_fetch_attachments() { return apply_filters( 'import_allow_fetch_attachments', true ); } /** * Decide what the maximum file size for downloaded attachments is. * Default is 0 (unlimited), can be filtered via import_attachment_size_limit * * @return int Maximum attachment file size to import */ public function max_attachment_size() { return apply_filters( 'import_attachment_size_limit', 0 ); } /** * Added to http_request_timeout filter to force timeout at 60 seconds during import * @return int 60 */ public function bump_request_timeout( $val ) { return 60; } // return the difference in length between two strings public function cmpr_strlen( $a, $b ) { return strlen( $b ) - strlen( $a ); } /** * Parses filename from a Content-Disposition header value. * * As per RFC6266: * * content-disposition = "Content-Disposition" ":" * disposition-type *( ";" disposition-parm ) * * disposition-type = "inline" | "attachment" | disp-ext-type * ; case-insensitive * disp-ext-type = token * * disposition-parm = filename-parm | disp-ext-parm * * filename-parm = "filename" "=" value * | "filename*" "=" ext-value * * disp-ext-parm = token "=" value * | ext-token "=" ext-value * ext-token = <the characters in token, followed by "*"> * * @since 0.7.0 * * @see WP_REST_Attachments_Controller::get_filename_from_disposition() * * @link http://tools.ietf.org/html/rfc2388 * @link http://tools.ietf.org/html/rfc6266 * * @param string[] $disposition_header List of Content-Disposition header values. * @return string|null Filename if available, or null if not found. */ protected static function get_filename_from_disposition( $disposition_header ) { // Get the filename. $filename = null; foreach ( $disposition_header as $value ) { $value = trim( $value ); if ( strpos( $value, ';' ) === false ) { continue; } list( $type, $attr_parts ) = explode( ';', $value, 2 ); $attr_parts = explode( ';', $attr_parts ); $attributes = array(); foreach ( $attr_parts as $part ) { if ( strpos( $part, '=' ) === false ) { continue; } list( $key, $value ) = explode( '=', $part, 2 ); $attributes[ trim( $key ) ] = trim( $value ); } if ( empty( $attributes['filename'] ) ) { continue; } $filename = trim( $attributes['filename'] ); // Unquote quoted filename, but after trimming. if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) { $filename = substr( $filename, 1, -1 ); } } return $filename; } /** * Retrieves file extension by mime type. * * @since 0.7.0 * * @param string $mime_type Mime type to search extension for. * @return string|null File extension if available, or null if not found. */ protected static function get_file_extension_by_mime_type( $mime_type ) { static $map = null; if ( is_array( $map ) ) { return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null; } $mime_types = wp_get_mime_types(); $map = array_flip( $mime_types ); // Some types have multiple extensions, use only the first one. foreach ( $map as $type => $extensions ) { $map[ $type ] = strtok( $extensions, '|' ); } return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null; } /** * Unserializes data only if it was serialized. * * @since 0.8.4 * * @param string $data Data that might be unserialized. * @return mixed Unserialized data can be any type. */ protected function maybe_unserialize( $data ) { // Don't attempt to unserialize data that wasn't serialized going in. if ( is_serialized( $data ) ) { // Transform the serialized objects to a stdClass object. $data = preg_replace( '/O:\d+:"[^"]+":/', 'O:8:"stdClass":', $data ); return maybe_unserialize( $data ); } return $data; } }
| ver. 1.6 |
Github
|
.
| PHP 8.1.34 | Генерация страницы: 0.05 |
proxy
|
phpinfo
|
Настройка