No test URLs found in config, if you supply one we\'ll give you a link to test how Full-Text RSS will extract it
';
+ }
+ } else {
+ echo '
Could not load/parse config file
';
+ }
+ } else {
+ echo 'Failed to save '.$savepath.'. Make sure the directory is writable.';
+ }
+ }
+ exit;
+ }
+}
+
+/////////////////////////////////
+// Show list of site config files
+/////////////////////////////////
+if (!isset($_REQUEST['url']) || trim($_REQUEST['url']) == '') {
+ $sc_files = array_merge(scandir('../site_config/standard/'), scandir('../site_config/custom/'));
+ $sc_files = array_unique(array_filter($sc_files, 'filter_only_text'));
+ ?>
+
Note: This feature is for advanced users familiar with XPath. It allows you to override automatic article extraction and specify what Full-Text RSS should extract from specific domains. If you're uncomfortable writing your own, you can request one from us.
diff --git a/admin/update.php b/admin/update.php
index c1a7535..f3eaf00 100644
--- a/admin/update.php
+++ b/admin/update.php
@@ -33,8 +33,8 @@ ini_set("display_errors", 1);
////////////////////////////////
// Load config file
////////////////////////////////
-require_once('../config.php');
$admin_page = 'update';
+require_once('../config.php');
require_once('require_login.php');
require_once('template.php');
tpl_header('Update site patterns');
diff --git a/cache/index.php b/cache/index.php
new file mode 100644
index 0000000..a3d5f73
--- /dev/null
+++ b/cache/index.php
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/changelog.txt b/changelog.txt
index cd597ba..81458c6 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -2,6 +2,33 @@ FiveFilters.org: Full-Text RSS
http://fivefilters.org/content-only/
CHANGELOG
------------------------------------
+3.0 (2012-09-04)
+ - Multi-page support - next_page_link now supported in site config (enable/disable with $options->multipage)
+ - HTML5 parser available - use parser: html5lib in site config, also see $options->allowed_parsers
+ - Updated site patterns for better extraction
+ - New global site config to be applied to all sites (global.txt)
+ - APC caching of site config files to improve performance, if APC available - see $options->apc
+ - Site config editor in admin/ - easily find, edit, test, and test site config files, or add new ones
+ - Debug mode to see what's happening behind the scenes - see $options->debug
+ - Removed deprecated config options: restrict, message_to_prepend_with_key, message_to_append_with_key, error_message_with_key
+ - Removed extraction with CSS via querystring
+ - Removed config option: $options->alternative_url
+ - Bug fix: allow extraction of a single element
+ - Bug fix: redirect handling improved
+ - Strip 'http://' prefix when API key is supplied
+ - Site config merging (custom + standard + fingerprint + global)
+ - Site config command replace_string(find): replace can now be split over two lines: find_string: find, replace_string: replace
+ - YouTube and Vimeo URLs now return iframe embed code
+ - We now look for OpenGraph title and date elements
+ - Improved extraction from AJAX pages - we now look for AJAX triggers embedded in HTML, per Google spec
+ - JSONP support - use &format=json&callback=functionName in querystring
+ - New config option to enable Cross-Origin Resource Sharing (CORS): $option->cors
+ - New config option to enable XSS filtering, if required: $option->xss_filter
+ - Zend_Cache updated
+ - Smart caching - experimental feature to store cache IDs in APC first, and write output to disk on subsequent request (see $options->smart_cache)
+ - Easier cloud deploy - manifest.yml added for AppFog
+ - Override most config options with environment variables, e.g. ftr_max_entries: 3
+
2.9.5 (2012-04-29)
- Language detection using Text_LanguageDetect or PHP-CLD (dc:language field in output and $options->detect_language in config)
- New site patterns added and old ones updated
diff --git a/cleancache.php b/cleancache.php
index a3b57e0..182e2e5 100644
--- a/cleancache.php
+++ b/cleancache.php
@@ -22,7 +22,7 @@ along with this program. If not, see .
// Usage
// -----
// Set up your scheduler (e.g. cron) to request this file periodically.
-// Note: this file must not be named cleancache.php so please rename it.
+// Note: this file must _not_ be named cleancache.php so please rename it.
// We ask you to do this to prevent others from initiating
// the cache cleanup process. It will not run if it's called cleancache.php.
@@ -50,31 +50,18 @@ function __autoload($class_name) {
return false;
}
}
-require_once(dirname(__FILE__).'/config.php');
+require_once dirname(__FILE__).'/config.php';
if (!$options->caching) die('Caching is disabled');
-/*
-// clean http response cache
-$frontendOptions = array(
- 'lifetime' => 30*60, // cache lifetime of 30 minutes
- 'automatic_serialization' => true,
- 'write_control' => false,
- 'automatic_cleaning_factor' => 0,
- 'ignore_user_abort' => false
-);
-$backendOptions = array(
- 'cache_dir' => $options->cache_dir.'/http-responses/',
- 'file_locking' => false,
- 'read_control' => true,
- 'read_control_type' => 'strlen',
- 'hashed_directory_level' => $options->cache_directory_level,
- 'hashed_directory_umask' => 0777,
- 'cache_file_umask' => 0664,
- 'file_name_prefix' => 'ff'
-);
-$cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
-$cache->clean(Zend_Cache::CLEANING_MODE_OLD);
-*/
+// clean APC cache
+if ($options->apc && function_exists('apc_delete')) {
+ $_apc_data = apc_cache_info('user');
+ foreach ($_apc_data['cache_list'] as $_apc_item) {
+ if ($_apc_item['ttl'] > 0 && ($_apc_item['ttl'] + $_apc_item['creation_time'] < time())) {
+ apc_delete($_apc_item['info']);
+ }
+ }
+}
// clean rss (non-key) cache
$frontendOptions = array(
@@ -90,8 +77,8 @@ $backendOptions = array(
'read_control' => true,
'read_control_type' => 'strlen',
'hashed_directory_level' => $options->cache_directory_level,
- 'hashed_directory_umask' => 0777,
- 'cache_file_umask' => 0664,
+ 'hashed_directory_perm' => 0777,
+ 'cache_file_perm' => 0664,
'file_name_prefix' => 'ff'
);
$cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
@@ -111,8 +98,8 @@ $backendOptions = array(
'read_control' => true,
'read_control_type' => 'strlen',
'hashed_directory_level' => $options->cache_directory_level,
- 'hashed_directory_umask' => 0777,
- 'cache_file_umask' => 0664,
+ 'hashed_directory_perm' => 0777,
+ 'cache_file_perm' => 0664,
'file_name_prefix' => 'ff'
);
$cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
diff --git a/config.php b/config.php
index 632dcb1..8332cc8 100644
--- a/config.php
+++ b/config.php
@@ -21,6 +21,16 @@ if (!isset($options)) $options = new stdClass();
// be told that the service is disabled.
$options->enabled = true;
+// Debug mode
+// ----------------------
+// Enable or disable debugging. When enabled debugging works by passing
+// &debug to the makefulltextfeed.php querystring.
+// Valid values:
+// true or 'user' (default) - let user decide
+// 'admin' - debug works only for logged in admin users
+// false - disabled
+$options->debug = true;
+
// Default entries (without access key)
// ----------------------
// The number of feed items to process when no API key is supplied
@@ -52,12 +62,21 @@ $options->rewrite_relative_urls = true;
// User decides: 'user' (this option will appear on the form)
$options->exclude_items_on_fail = 'user';
+// Enable multi-page support
+// -------------------------
+// If enabled, we will try to follow next page links on multi-page articles.
+// Currently this only happens for sites where next_page_link has been defined
+// in a site config file.
+$options->multipage = true;
+
// Enable caching
// ----------------------
// Enable this if you'd like to cache results
-// for 10 minutes. Initially it's best
-// to keep this disabled to make sure everything works
-// as expected.
+// for 10 minutes. Cache files are written to disk (in cache/ subfolders
+// - which must be writable).
+// Initially it's best to keep this disabled to make sure everything works
+// as expected. If you have APC enabled, please also see smart_cache in the
+// advanced section.
$options->caching = false;
// Cache directory
@@ -125,6 +144,8 @@ $options->registration_key = '';
// To use these pages, enter a password here and you'll be prompted for it when you try to access those pages.
// If no password or username is set, pages requiring admin privelages will be inaccessible.
// The default username is 'admin'.
+// If overriding with an environment variable, separate username and password with a colon, e.g.:
+// ftr_admin_credentials: admin:my-secret-password
// Example: $options->admin_credentials = array('username'=>'admin', 'password'=>'my-secret-password');
$options->admin_credentials = array('username'=>'admin', 'password'=>'');
@@ -184,6 +205,72 @@ $options->max_entries_with_key = 10;
/// ADVANCED OPTIONS ////////////////////////////
/////////////////////////////////////////////////
+// Enable XSS filter?
+// ----------------------
+// We have not enabled this by default because we assume the majority of
+// our users do not display the HTML retrieved by Full-Text RSS
+// in a web page without further processing. If you subscribe to our generated
+// feeds in your news reader application, it should, if it's good software, already
+// filter the resulting HTML for XSS attacks, making it redundant for
+// Full-Text RSS do the same. Similarly with frameworks/CMS which display
+// feed content - the content should be treated like any other user-submitted content.
+//
+// If you are writing an application yourself which is processing feeds generated by
+// Full-Text RSS, you can either filter the HTML yourself to remove potential XSS attacks
+// or enable this option. This might be useful if you are processing our generated
+// feeds with JavaScript on the client side - although there's client side xss
+// filtering available too, e.g. https://code.google.com/p/google-caja/wiki/JsHtmlSanitizer
+//
+// If enabled, we'll pass retrieved HTML content through htmLawed with
+// safe flag on and style attributes denied, see
+// http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/htmLawed_README.htm#s3.6
+// Note: if enabled this will also remove certain elements you may want to preserve, such as iframes.
+//
+// Valid values:
+// true - enabled, all content will be filtered
+// 'user' (default) - user must pass &xss in makefulltextfeed.php querystring to enable
+// false - disabled
+$options->xss_filter = 'user';
+
+// Allowed parsers
+// ----------------------
+// Full-Text RSS attempts to use PHP's libxml extension to process HTML.
+// While fast, on some sites it may not always produce good results.
+// For these sites, you can specify an alternative HTML parser:
+// parser: html5lib
+// The html5lib parser is bundled with Full-Text RSS.
+// see http://code.google.com/p/html5lib/
+//
+// To disable HTML parsing with html5lib, you can remove it from this list.
+// By default we allow both: libxml and html5lib.
+$options->allowed_parsers = array('libxml', 'html5lib');
+//$options->allowed_parsers = array('libxml'); //disable html5lib - forcing libxml in all cases
+
+// Enable Cross-Origin Resource Sharing (CORS)
+// ----------------------
+// If enabled we'll send the following HTTP header
+// Access-Control-Allow-Origin: *
+// see http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+$options->cors = false;
+
+// Use APC user cache?
+// ----------------------
+// If enabled we will store site config files (when requested
+// for the first time) in APC's user cache. Keys prefixed with 'sc.'
+// This improves performance by reducing disk access.
+// Note: this has no effect if APC is unavailable on your server.
+$options->apc = true;
+
+// Smart cache (experimental)
+// ----------------------
+// With this option enabled we will not cache to disk immediately.
+// We will store the cache key in APC and if it's requested again
+// we will cache results to disk. Keys prefixed with 'cache.'
+// This improves performance by reducing disk access.
+// Note: this has no effect if APC is disabled or unavailable on your server,
+// or if you have caching disabled.
+$options->smart_cache = true;
+
// Fingerprints
// ----------------------
// key is fingerprint (fragment to find in HTML)
@@ -211,7 +298,8 @@ $options->user_agents = array( 'lifehacker.com' => 'PHP/5.2',
'io9.com' => 'PHP/5.2',
'jalopnik.com' => 'PHP/5.2',
'gizmodo.com' => 'PHP/5.2',
- '.wikipedia.org' => 'Mozilla/5.2'
+ '.wikipedia.org' => 'Mozilla/5.2',
+ '.fok.nl' => 'Googlebot/2.1'
);
// URL Rewriting
@@ -232,9 +320,8 @@ $options->rewrite_url = array(
// Content-Type exceptions
// -----------------------
-// We currently treat everything as HTML.
-// Here you can define different actions if
-// Content-Type returned by server matches.
+// Here you can define different actions based
+// on the Content-Type header returned by server.
// MIME type as key, action as value.
// Valid actions:
// * 'exclude' - exclude this item from the result
@@ -246,16 +333,6 @@ $options->content_type_exc = array(
'video' => array('action'=>'link', 'name'=>'Video')
);
-// Alternative Full-Text RSS service URL
-// ----------------------
-// This option is to offer very simple load distribution for the service.
-// If you've set up another instance of the Full-Text RSS service on a different
-// server, you can enter its full URL here.
-// E.g. 'http://my-other-server.org/full-text-rss/makefulltextfeed.php'
-// If you specify a URL here, 50% of the requests to makefulltextfeed.php on
-// this server will be redirected to the URL specified here.
-$options->alternative_url = '';
-
// Cache directory level
// ----------------------
// Spread cache files over different directories (only used if caching is enabled).
@@ -274,53 +351,37 @@ $options->cache_directory_level = 0;
// ...you get the idea :)
$options->cache_cleanup = 100;
-/////////////////////////////////////////////////
-/// DEPRECATED OPTIONS
-/// THESE OPTIONS MIGHT CHANGE IN VERSION 3.0
-/// WE RECOMMEND YOU DO NOT USE THEM
-/////////////////////////////////////////////////
-
-// Extraction pattern (deprecated)
-// Site configuration files offer a better,
-// more flexible solution - please use those instead.
-// ---------------------------------
-// Specify what should get extracted
-// Possible values:
-// Auto detect: 'auto'
-// Custom: css string (e.g. 'div#content')
-// Element within auto-detected block: 'auto ' + css string (e.g. 'auto p')
-// User decides: 'user' (same as 'auto' but CSS selector can be passed in query, e.g. &what=.content)
-$options->extraction_pattern = 'user';
-
-// Restrict service (deprecated)
-// -----------------------------
-// Set this to true if you'd like certain features
-// to be available only to key holders.
-// Affected features:
-// * Link handling (disabled for non-key holders if set to true)
-// * Cache time (20 minutes for non-key holders if set to true)
-$options->restrict = false;
-
-// Message to prepend (with API key) (deprecated)
-// ----------------------
-// HTML to insert at the beginning of each feed item when a valid API key is supplied.
-$options->message_to_prepend_with_key = '';
-
-// Message to append (with API key) (deprecated)
-// ----------------------
-// HTML to insert at the end of each feed item when a valid API key is supplied.
-$options->message_to_append_with_key = '';
-
-// Error message when content extraction fails (with API key) (deprecated)
-// ----------------------
-$options->error_message_with_key = '[unable to retrieve full-text content]';
-
/////////////////////////////////////////////////
/// DO NOT CHANGE ANYTHING BELOW THIS ///////////
/////////////////////////////////////////////////
-if (!defined('_FF_FTR_VERSION')) define('_FF_FTR_VERSION', '2.9.5');
+if (!defined('_FF_FTR_VERSION')) define('_FF_FTR_VERSION', '3.0');
-if ((basename(__FILE__) == 'config.php') && (file_exists(dirname(__FILE__).'/custom_config.php'))) {
- require_once(dirname(__FILE__).'/custom_config.php');
+if (basename(__FILE__) == 'config.php') {
+ if (file_exists(dirname(__FILE__).'/custom_config.php')) {
+ require_once dirname(__FILE__).'/custom_config.php';
+ }
+
+ // check for environment variables - often used on cloud platforms
+ // environment variables should be prefixed with 'ftr_', e.g.
+ // ftr_max_entries: 1
+ // will set the max_entries value to 1.
+ foreach ($options as $_key=>&$_val) {
+ $_key = "ftr_$_key";
+ if (($_env = getenv($_key)) !== false) {
+ if (is_array($_val)) {
+ if ($_key === 'ftr_admin_credentials') {
+ $_val = array_combine(array('username', 'password'), array_map('trim', explode(':', $_env, 2)));
+ if ($_val === false) $_val = array('username'=>'admin', 'password'=>'');
+ }
+ } elseif ($_env === 'true' || $_env === 'false') {
+ $_val = ($_env === 'true');
+ } elseif (is_numeric($_env)) {
+ $_val = (int)$_env;
+ } else { // string
+ $_val = $_env;
+ }
+ }
+ }
+ unset($_key, $_val, $_env);
}
\ No newline at end of file
diff --git a/ftr_compatibility_test.php b/ftr_compatibility_test.php
index 2a6496c..4ae42bc 100644
--- a/ftr_compatibility_test.php
+++ b/ftr_compatibility_test.php
@@ -1,6 +1,6 @@
='));
$pcre_ok = extension_loaded('pcre');
@@ -84,6 +84,14 @@ em {
padding: 0.1em 0;
}
+.success {
+ background-color: lightgreen;
+}
+
+.highlight {
+ background-color: #ffc;
+}
+
ul, ol {
margin:10px 0 10px 20px;
padding:0 0 0 15px;
@@ -114,7 +122,6 @@ h4 {
code {
font-size:1.1em;
- background-color:#f3f3ff;
color:#000;
}
@@ -251,71 +258,71 @@ div.chunk {
What does this mean?
-
You have everything you need to run properly! Congratulations!
+
You have everything you need to run properly! Congratulations!
-
PHP: You are running a supported version of PHP. No problems here.
+
PHP: You are running a supported version of PHP. No problems here.
-
XML: You have XMLReader support or a version of XML support that isn't broken installed. No problems here.
+
XML: You have XMLReader support or a version of XML support that isn't broken installed. No problems here.
-
PCRE: You have PCRE support installed. No problems here.
+
PCRE: You have PCRE support installed. No problems here.
-
allow_url_fopen: You have allow_url_fopen enabled. No problems here.
+
allow_url_fopen: You have allow_url_fopen enabled. No problems here.
-
Data filtering: You have the PHP filter extension enabled. No problems here.
+
Data filtering: You have the PHP filter extension enabled. No problems here.
-
Zlib: You have Zlib enabled. This allows SimplePie to support GZIP-encoded feeds. No problems here.
+
Zlib: You have Zlib enabled. This allows SimplePie to support GZIP-encoded feeds. No problems here.
-
Zlib: The Zlib extension is not available. SimplePie will ignore any GZIP-encoding, and instead handle feeds as uncompressed text.
+
Zlib: The Zlib extension is not available. SimplePie will ignore any GZIP-encoding, and instead handle feeds as uncompressed text.
-
mbstring and iconv: You have both mbstring and iconv installed! This will allow to handle the greatest number of languages. No problems here.
+
mbstring and iconv: You have both mbstring and iconv installed! This will allow to handle the greatest number of languages. No problems here.
-
mbstring:mbstring is installed, but iconv is not.
+
mbstring:mbstring is installed, but iconv is not.
-
iconv:iconv is installed, but mbstring is not.
+
iconv:iconv is installed, but mbstring is not.
-
mbstring and iconv:You do not have either of the extensions installed. This will significantly impair your ability to read non-English feeds, as well as even some English ones.
+
mbstring and iconv:You do not have either of the extensions installed. This will significantly impair your ability to read non-English feeds, as well as even some English ones.
-
Tidy: You have Tidy support installed. No problems here.
+
Tidy: You have Tidy support installed. No problems here.
-
Tidy: The Tidy extension is not available. should still work with most feeds, but you may experience problems with some.
+
Tidy: The Tidy extension is not available. should still work with most feeds/articles, but you may experience problems with some. If you do, we suggest you specify parsing with html5lib.
-
cURL: You have cURL support installed. No problems here.
+
cURL: You have cURL support installed. No problems here.
-
cURL: The cURL extension is not available. SimplePie will use fsockopen() instead.
+
cURL: The cURL extension is not available. SimplePie will use fsockopen() instead.
-
Parallel URL fetching: You have HttpRequestPool or curl_multi support installed. No problems here.
+
Parallel URL fetching: You have HttpRequestPool or curl_multi support installed. No problems here.
-
Parallel URL fetching:HttpRequestPool or curl_multi support is not available. will use file_get_contents() instead to fetch URLs sequentially rather than in parallel.
+
Parallel URL fetching:HttpRequestPool or curl_multi support is not available. will use file_get_contents() instead to fetch URLs sequentially rather than in parallel.
-
Data filtering: Your PHP configuration has the filter extension disabled. will not work here.
+
Data filtering: Your PHP configuration has the filter extension disabled. will not work here.
-
allow_url_fopen: Your PHP configuration has allow_url_fopen disabled. will not work here.
+
allow_url_fopen: Your PHP configuration has allow_url_fopen disabled. will not work here.
-
PCRE: Your PHP installation doesn't support Perl-Compatible Regular Expressions. will not work here.
+
PCRE: Your PHP installation doesn't support Perl-Compatible Regular Expressions. will not work here.
-
XML: Your PHP installation doesn't support XML parsing. will not work here.
+
XML: Your PHP installation doesn't support XML parsing. will not work here.
-
PHP: You are running an unsupported version of PHP. will not work here.
+
PHP: You are running an unsupported version of PHP. will not work here.
@@ -324,7 +331,7 @@ div.chunk {
Bottom Line: Yes, you can!
-
Your webhost has its act together!
+
Your webhost has its act together!
You can download the latest version of from FiveFilters.org.
Note: Passing this test does not guarantee that will run on your webhost — it only ensures that the basic requirements have been addressed. If you experience any problems, please let us know.
Thanks for downloading and setting up Full-Text RSS from FiveFilters.org. The software runs on most web hosting environments, but to make sure everything works as it should, please follow the steps below.
Enter a feed or article URL in the form above and click 'Create Feed' ?
@@ -176,6 +169,10 @@ if (!defined('_FF_FTR_INDEX')) {
To change the configuration, save a copy of config.php as custom_config.php and make any changes you like to it.To change the configuration, edit custom_config.php and make any changes you like.
+
Manage and update site config files
+
For best results, we suggest you update the site config files bundled with Full-Text RSS. If you've purchased Full-Text RSS from us, you'll receive an email when these are updated.
+
The easiest way to update these is via the admin area. (For advanced users, you'll also be able to edit and test the extraction rules contained in the site config files from the admin area.)
+
Customise this page
If everything works fine, feel free to modify this page by following the steps below:
@@ -187,8 +184,11 @@ if (!defined('_FF_FTR_INDEX')) {
Thanks for downloading and setting up Full-Text RSS. This software is developed and maintained by FiveFilters.org. If you find it useful, but have not purchased this from us, please consider supporting us by purchasing from FiveFilters.org.
+
-
+
@@ -233,9 +233,10 @@ if (!defined('_FF_FTR_INDEX')) {
Depending on your configuration, these secondary components may also be used:
If you've purchased this from FiveFilters.org, you'll receive notification when we release a new version or update the site patterns.
-
Full-Text RSS is licensed under the AGPL version 3 — which basically means if you use the code to offer the same or similar service for your users, you are also required to share the code with your users so they can examine the code and run it for themselves. (More on why this is important.)
-
The software components used by the application are licensed as follows...
+
Full-Text RSS is licensed under the AGPL version 3 — more information about why we use this license can be found on FiveFilters.org
+
The software components in this application are licensed as follows...
';
+ }
+ elseif($do < 3 or isset($ok['#pcdata'])){echo $x;}
+ elseif(strpos($x, "\x02\x04")){
+ foreach(preg_split('`(\x01\x02[^\x01\x02]+\x02\x01)`', $x, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) as $v){
+ echo (substr($v, 0, 2) == "\x01\x02" ? $v : ($do > 4 ? preg_replace('`\S`', '', $v) : ''));
+ }
+ }elseif($do > 4){echo preg_replace('`\S`', '', $x);}
+}
+while(!empty($q) && ($e = array_pop($q))){echo '', $e, '>';}
+$o = ob_get_contents();
+ob_end_clean();
+return $o;
+// eof
+}
+
+public static function hl_cmtcd($t){
+// comment/CDATA sec handler
+$t = $t[0];
+global $C;
+if(!($v = $C[$n = $t[3] == '-' ? 'comment' : 'cdata'])){return $t;}
+if($v == 1){return '';}
+if($n == 'comment'){
+ if(substr(($t = preg_replace('`--+`', '-', substr($t, 4, -3))), -1) != ' '){$t .= ' ';}
+}
+else{$t = substr($t, 1, -1);}
+$t = $v == 2 ? str_replace(array('&', '<', '>'), array('&', '<', '>'), $t) : $t;
+return str_replace(array('&', '<', '>'), array("\x03", "\x04", "\x05"), ($n == 'comment' ? "\x01\x02\x04!--$t--\x05\x02\x01" : "\x01\x01\x04$t\x05\x01\x01"));
+// eof
+}
+
+public static function hl_ent($t){
+// entitity handler
+global $C;
+$t = $t[1];
+static $U = array('quot'=>1,'amp'=>1,'lt'=>1,'gt'=>1);
+static $N = array('fnof'=>'402', 'Alpha'=>'913', 'Beta'=>'914', 'Gamma'=>'915', 'Delta'=>'916', 'Epsilon'=>'917', 'Zeta'=>'918', 'Eta'=>'919', 'Theta'=>'920', 'Iota'=>'921', 'Kappa'=>'922', 'Lambda'=>'923', 'Mu'=>'924', 'Nu'=>'925', 'Xi'=>'926', 'Omicron'=>'927', 'Pi'=>'928', 'Rho'=>'929', 'Sigma'=>'931', 'Tau'=>'932', 'Upsilon'=>'933', 'Phi'=>'934', 'Chi'=>'935', 'Psi'=>'936', 'Omega'=>'937', 'alpha'=>'945', 'beta'=>'946', 'gamma'=>'947', 'delta'=>'948', 'epsilon'=>'949', 'zeta'=>'950', 'eta'=>'951', 'theta'=>'952', 'iota'=>'953', 'kappa'=>'954', 'lambda'=>'955', 'mu'=>'956', 'nu'=>'957', 'xi'=>'958', 'omicron'=>'959', 'pi'=>'960', 'rho'=>'961', 'sigmaf'=>'962', 'sigma'=>'963', 'tau'=>'964', 'upsilon'=>'965', 'phi'=>'966', 'chi'=>'967', 'psi'=>'968', 'omega'=>'969', 'thetasym'=>'977', 'upsih'=>'978', 'piv'=>'982', 'bull'=>'8226', 'hellip'=>'8230', 'prime'=>'8242', 'Prime'=>'8243', 'oline'=>'8254', 'frasl'=>'8260', 'weierp'=>'8472', 'image'=>'8465', 'real'=>'8476', 'trade'=>'8482', 'alefsym'=>'8501', 'larr'=>'8592', 'uarr'=>'8593', 'rarr'=>'8594', 'darr'=>'8595', 'harr'=>'8596', 'crarr'=>'8629', 'lArr'=>'8656', 'uArr'=>'8657', 'rArr'=>'8658', 'dArr'=>'8659', 'hArr'=>'8660', 'forall'=>'8704', 'part'=>'8706', 'exist'=>'8707', 'empty'=>'8709', 'nabla'=>'8711', 'isin'=>'8712', 'notin'=>'8713', 'ni'=>'8715', 'prod'=>'8719', 'sum'=>'8721', 'minus'=>'8722', 'lowast'=>'8727', 'radic'=>'8730', 'prop'=>'8733', 'infin'=>'8734', 'ang'=>'8736', 'and'=>'8743', 'or'=>'8744', 'cap'=>'8745', 'cup'=>'8746', 'int'=>'8747', 'there4'=>'8756', 'sim'=>'8764', 'cong'=>'8773', 'asymp'=>'8776', 'ne'=>'8800', 'equiv'=>'8801', 'le'=>'8804', 'ge'=>'8805', 'sub'=>'8834', 'sup'=>'8835', 'nsub'=>'8836', 'sube'=>'8838', 'supe'=>'8839', 'oplus'=>'8853', 'otimes'=>'8855', 'perp'=>'8869', 'sdot'=>'8901', 'lceil'=>'8968', 'rceil'=>'8969', 'lfloor'=>'8970', 'rfloor'=>'8971', 'lang'=>'9001', 'rang'=>'9002', 'loz'=>'9674', 'spades'=>'9824', 'clubs'=>'9827', 'hearts'=>'9829', 'diams'=>'9830', 'apos'=>'39', 'OElig'=>'338', 'oelig'=>'339', 'Scaron'=>'352', 'scaron'=>'353', 'Yuml'=>'376', 'circ'=>'710', 'tilde'=>'732', 'ensp'=>'8194', 'emsp'=>'8195', 'thinsp'=>'8201', 'zwnj'=>'8204', 'zwj'=>'8205', 'lrm'=>'8206', 'rlm'=>'8207', 'ndash'=>'8211', 'mdash'=>'8212', 'lsquo'=>'8216', 'rsquo'=>'8217', 'sbquo'=>'8218', 'ldquo'=>'8220', 'rdquo'=>'8221', 'bdquo'=>'8222', 'dagger'=>'8224', 'Dagger'=>'8225', 'permil'=>'8240', 'lsaquo'=>'8249', 'rsaquo'=>'8250', 'euro'=>'8364', 'nbsp'=>'160', 'iexcl'=>'161', 'cent'=>'162', 'pound'=>'163', 'curren'=>'164', 'yen'=>'165', 'brvbar'=>'166', 'sect'=>'167', 'uml'=>'168', 'copy'=>'169', 'ordf'=>'170', 'laquo'=>'171', 'not'=>'172', 'shy'=>'173', 'reg'=>'174', 'macr'=>'175', 'deg'=>'176', 'plusmn'=>'177', 'sup2'=>'178', 'sup3'=>'179', 'acute'=>'180', 'micro'=>'181', 'para'=>'182', 'middot'=>'183', 'cedil'=>'184', 'sup1'=>'185', 'ordm'=>'186', 'raquo'=>'187', 'frac14'=>'188', 'frac12'=>'189', 'frac34'=>'190', 'iquest'=>'191', 'Agrave'=>'192', 'Aacute'=>'193', 'Acirc'=>'194', 'Atilde'=>'195', 'Auml'=>'196', 'Aring'=>'197', 'AElig'=>'198', 'Ccedil'=>'199', 'Egrave'=>'200', 'Eacute'=>'201', 'Ecirc'=>'202', 'Euml'=>'203', 'Igrave'=>'204', 'Iacute'=>'205', 'Icirc'=>'206', 'Iuml'=>'207', 'ETH'=>'208', 'Ntilde'=>'209', 'Ograve'=>'210', 'Oacute'=>'211', 'Ocirc'=>'212', 'Otilde'=>'213', 'Ouml'=>'214', 'times'=>'215', 'Oslash'=>'216', 'Ugrave'=>'217', 'Uacute'=>'218', 'Ucirc'=>'219', 'Uuml'=>'220', 'Yacute'=>'221', 'THORN'=>'222', 'szlig'=>'223', 'agrave'=>'224', 'aacute'=>'225', 'acirc'=>'226', 'atilde'=>'227', 'auml'=>'228', 'aring'=>'229', 'aelig'=>'230', 'ccedil'=>'231', 'egrave'=>'232', 'eacute'=>'233', 'ecirc'=>'234', 'euml'=>'235', 'igrave'=>'236', 'iacute'=>'237', 'icirc'=>'238', 'iuml'=>'239', 'eth'=>'240', 'ntilde'=>'241', 'ograve'=>'242', 'oacute'=>'243', 'ocirc'=>'244', 'otilde'=>'245', 'ouml'=>'246', 'divide'=>'247', 'oslash'=>'248', 'ugrave'=>'249', 'uacute'=>'250', 'ucirc'=>'251', 'uuml'=>'252', 'yacute'=>'253', 'thorn'=>'254', 'yuml'=>'255');
+if($t[0] != '#'){
+ return ($C['and_mark'] ? "\x06" : '&'). (isset($U[$t]) ? $t : (isset($N[$t]) ? (!$C['named_entity'] ? '#'. ($C['hexdec_entity'] > 1 ? 'x'. dechex($N[$t]) : $N[$t]) : $t) : 'amp;'. $t)). ';';
+}
+if(($n = ctype_digit($t = substr($t, 1)) ? intval($t) : hexdec(substr($t, 1))) < 9 or ($n > 13 && $n < 32) or $n == 11 or $n == 12 or ($n > 126 && $n < 160 && $n != 133) or ($n > 55295 && ($n < 57344 or ($n > 64975 && $n < 64992) or $n == 65534 or $n == 65535 or $n > 1114111))){
+ return ($C['and_mark'] ? "\x06" : '&'). "amp;#{$t};";
+}
+return ($C['and_mark'] ? "\x06" : '&'). '#'. (((ctype_digit($t) && $C['hexdec_entity'] < 2) or !$C['hexdec_entity']) ? $n : 'x'. dechex($n)). ';';
+// eof
+}
+
+public static function hl_prot($p, $c=null){
+// check URL scheme
+global $C;
+$b = $a = '';
+if($c == null){$c = 'style'; $b = $p[1]; $a = $p[3]; $p = trim($p[2]);}
+$c = isset($C['schemes'][$c]) ? $C['schemes'][$c] : $C['schemes']['*'];
+static $d = 'denied:';
+if(isset($c['!']) && substr($p, 0, 7) != $d){$p = "$d$p";}
+if(isset($c['*']) or !strcspn($p, '#?;') or (substr($p, 0, 7) == $d)){return "{$b}{$p}{$a}";} // All ok, frag, query, param
+if(preg_match('`^([a-z\d\-+. ]+?)(:|(58|x3a);|%3a|\\\\0{0,4}3a).`i', $p, $m) && !isset($c[strtolower($m[1])])){ // Denied prot
+ return "{$b}{$d}{$p}{$a}";
+}
+if($C['abs_url']){
+ if($C['abs_url'] == -1 && strpos($p, $C['base_url']) === 0){ // Make url rel
+ $p = substr($p, strlen($C['base_url']));
+ }elseif(empty($m[1])){ // Make URL abs
+ if(substr($p, 0, 2) == '//'){$p = substr($C['base_url'], 0, strpos($C['base_url'], ':')+1). $p;}
+ elseif($p[0] == '/'){$p = preg_replace('`(^.+?://[^/]+)(.*)`', '$1', $C['base_url']). $p;}
+ elseif(strcspn($p, './')){$p = $C['base_url']. $p;}
+ else{
+ preg_match('`^([a-zA-Z\d\-+.]+://[^/]+)(.*)`', $C['base_url'], $m);
+ $p = preg_replace('`(?<=/)\./`', '', $m[2]. $p);
+ while(preg_match('`(?<=/)([^/]{3,}|[^/.]+?|\.[^/.]|[^/.]\.)/\.\./`', $p)){
+ $p = preg_replace('`(?<=/)([^/]{3,}|[^/.]+?|\.[^/.]|[^/.]\.)/\.\./`', '', $p);
+ }
+ $p = $m[1]. $p;
+ }
+ }
+}
+return "{$b}{$p}{$a}";
+// eof
+}
+
+public static function hl_regex($p){
+// ?regex
+if(empty($p)){return 0;}
+if($t = ini_get('track_errors')){$o = isset($php_errormsg) ? $php_errormsg : null;}
+else{ini_set('track_errors', 1);}
+unset($php_errormsg);
+if(($d = ini_get('display_errors'))){ini_set('display_errors', 0);}
+preg_match($p, '');
+if($d){ini_set('display_errors', 1);}
+$r = isset($php_errormsg) ? 0 : 1;
+if($t){$php_errormsg = isset($o) ? $o : null;}
+else{ini_set('track_errors', 0);}
+return $r;
+// eof
+}
+
+public static function hl_spec($t){
+// final $spec
+$s = array();
+$t = str_replace(array("\t", "\r", "\n", ' '), '', preg_replace('/"(?>(`.|[^"])*)"/sme', 'substr(str_replace(array(";", "|", "~", " ", ",", "/", "(", ")", \'`"\'), array("\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\""), "$0"), 1, -1)', trim($t)));
+for($i = count(($t = explode(';', $t))); --$i>=0;){
+ $w = $t[$i];
+ if(empty($w) or ($e = strpos($w, '=')) === false or !strlen(($a = substr($w, $e+1)))){continue;}
+ $y = $n = array();
+ foreach(explode(',', $a) as $v){
+ if(!preg_match('`^([a-z:\-\*]+)(?:\((.*?)\))?`i', $v, $m)){continue;}
+ if(($x = strtolower($m[1])) == '-*'){$n['*'] = 1; continue;}
+ if($x[0] == '-'){$n[substr($x, 1)] = 1; continue;}
+ if(!isset($m[2])){$y[$x] = 1; continue;}
+ foreach(explode('/', $m[2]) as $m){
+ if(empty($m) or ($p = strpos($m, '=')) == 0 or $p < 5){$y[$x] = 1; continue;}
+ $y[$x][strtolower(substr($m, 0, $p))] = str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08"), array(";", "|", "~", " ", ",", "/", "(", ")"), substr($m, $p+1));
+ }
+ if(isset($y[$x]['match']) && !htmLawed::hl_regex($y[$x]['match'])){unset($y[$x]['match']);}
+ if(isset($y[$x]['nomatch']) && !htmLawed::hl_regex($y[$x]['nomatch'])){unset($y[$x]['nomatch']);}
+ }
+ if(!count($y) && !count($n)){continue;}
+ foreach(explode(',', substr($w, 0, $e)) as $v){
+ if(!strlen(($v = strtolower($v)))){continue;}
+ if(count($y)){$s[$v] = $y;}
+ if(count($n)){$s[$v]['n'] = $n;}
+ }
+}
+return $s;
+// eof
+}
+
+public static function hl_tag($t){
+// tag/attribute handler
+global $C;
+$t = $t[0];
+// invalid < >
+if($t == '< '){return '< ';}
+if($t == '>'){return '>';}
+if(!preg_match('`^<(/?)([a-zA-Z][a-zA-Z1-6]*)([^>]*?)\s?>$`m', $t, $m)){
+ return str_replace(array('<', '>'), array('<', '>'), $t);
+}elseif(!isset($C['elements'][($e = strtolower($m[2]))])){
+ return (($C['keep_bad']%2) ? str_replace(array('<', '>'), array('<', '>'), $t) : '');
+}
+// attr string
+$a = str_replace(array("\n", "\r", "\t"), ' ', trim($m[3]));
+// tag transform
+static $eD = array('applet'=>1, 'center'=>1, 'dir'=>1, 'embed'=>1, 'font'=>1, 'isindex'=>1, 'menu'=>1, 's'=>1, 'strike'=>1, 'u'=>1); // Deprecated
+if($C['make_tag_strict'] && isset($eD[$e])){
+ $trt = htmLawed::hl_tag2($e, $a, $C['make_tag_strict']);
+ if(!$e){return (($C['keep_bad']%2) ? str_replace(array('<', '>'), array('<', '>'), $t) : '');}
+}
+// close tag
+static $eE = array('area'=>1, 'br'=>1, 'col'=>1, 'embed'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'isindex'=>1, 'param'=>1); // Empty ele
+if(!empty($m[1])){
+ return (!isset($eE[$e]) ? (empty($C['hook_tag']) ? "$e>" : $C['hook_tag']($e)) : (($C['keep_bad'])%2 ? str_replace(array('<', '>'), array('<', '>'), $t) : ''));
+}
+
+// open tag & attr
+static $aN = array('abbr'=>array('td'=>1, 'th'=>1), 'accept-charset'=>array('form'=>1), 'accept'=>array('form'=>1, 'input'=>1), 'accesskey'=>array('a'=>1, 'area'=>1, 'button'=>1, 'input'=>1, 'label'=>1, 'legend'=>1, 'textarea'=>1), 'action'=>array('form'=>1), 'align'=>array('caption'=>1, 'embed'=>1, 'applet'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'object'=>1, 'legend'=>1, 'table'=>1, 'hr'=>1, 'div'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'p'=>1, 'col'=>1, 'colgroup'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1), 'alt'=>array('applet'=>1, 'area'=>1, 'img'=>1, 'input'=>1), 'archive'=>array('applet'=>1, 'object'=>1), 'axis'=>array('td'=>1, 'th'=>1), 'bgcolor'=>array('embed'=>1, 'table'=>1, 'tr'=>1, 'td'=>1, 'th'=>1), 'border'=>array('table'=>1, 'img'=>1, 'object'=>1), 'bordercolor'=>array('table'=>1, 'td'=>1, 'tr'=>1), 'cellpadding'=>array('table'=>1), 'cellspacing'=>array('table'=>1), 'char'=>array('col'=>1, 'colgroup'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1), 'charoff'=>array('col'=>1, 'colgroup'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1), 'charset'=>array('a'=>1, 'script'=>1), 'checked'=>array('input'=>1), 'cite'=>array('blockquote'=>1, 'q'=>1, 'del'=>1, 'ins'=>1), 'classid'=>array('object'=>1), 'clear'=>array('br'=>1), 'code'=>array('applet'=>1), 'codebase'=>array('object'=>1, 'applet'=>1), 'codetype'=>array('object'=>1), 'color'=>array('font'=>1), 'cols'=>array('textarea'=>1), 'colspan'=>array('td'=>1, 'th'=>1), 'compact'=>array('dir'=>1, 'dl'=>1, 'menu'=>1, 'ol'=>1, 'ul'=>1), 'coords'=>array('area'=>1, 'a'=>1), 'data'=>array('object'=>1), 'datetime'=>array('del'=>1, 'ins'=>1), 'declare'=>array('object'=>1), 'defer'=>array('script'=>1), 'dir'=>array('bdo'=>1), 'disabled'=>array('button'=>1, 'input'=>1, 'optgroup'=>1, 'option'=>1, 'select'=>1, 'textarea'=>1), 'enctype'=>array('form'=>1), 'face'=>array('font'=>1), 'for'=>array('label'=>1), 'frame'=>array('table'=>1), 'frameborder'=>array('iframe'=>1), 'headers'=>array('td'=>1, 'th'=>1), 'height'=>array('embed'=>1, 'iframe'=>1, 'td'=>1, 'th'=>1, 'img'=>1, 'object'=>1, 'applet'=>1), 'href'=>array('a'=>1, 'area'=>1), 'hreflang'=>array('a'=>1), 'hspace'=>array('applet'=>1, 'img'=>1, 'object'=>1), 'ismap'=>array('img'=>1, 'input'=>1), 'label'=>array('option'=>1, 'optgroup'=>1), 'language'=>array('script'=>1), 'longdesc'=>array('img'=>1, 'iframe'=>1), 'marginheight'=>array('iframe'=>1), 'marginwidth'=>array('iframe'=>1), 'maxlength'=>array('input'=>1), 'method'=>array('form'=>1), 'model'=>array('embed'=>1), 'multiple'=>array('select'=>1), 'name'=>array('button'=>1, 'embed'=>1, 'textarea'=>1, 'applet'=>1, 'select'=>1, 'form'=>1, 'iframe'=>1, 'img'=>1, 'a'=>1, 'input'=>1, 'object'=>1, 'map'=>1, 'param'=>1), 'nohref'=>array('area'=>1), 'noshade'=>array('hr'=>1), 'nowrap'=>array('td'=>1, 'th'=>1), 'object'=>array('applet'=>1), 'onblur'=>array('a'=>1, 'area'=>1, 'button'=>1, 'input'=>1, 'label'=>1, 'select'=>1, 'textarea'=>1), 'onchange'=>array('input'=>1, 'select'=>1, 'textarea'=>1), 'onfocus'=>array('a'=>1, 'area'=>1, 'button'=>1, 'input'=>1, 'label'=>1, 'select'=>1, 'textarea'=>1), 'onreset'=>array('form'=>1), 'onselect'=>array('input'=>1, 'textarea'=>1), 'onsubmit'=>array('form'=>1), 'pluginspage'=>array('embed'=>1), 'pluginurl'=>array('embed'=>1), 'prompt'=>array('isindex'=>1), 'readonly'=>array('textarea'=>1, 'input'=>1), 'rel'=>array('a'=>1), 'rev'=>array('a'=>1), 'rows'=>array('textarea'=>1), 'rowspan'=>array('td'=>1, 'th'=>1), 'rules'=>array('table'=>1), 'scope'=>array('td'=>1, 'th'=>1), 'scrolling'=>array('iframe'=>1), 'selected'=>array('option'=>1), 'shape'=>array('area'=>1, 'a'=>1), 'size'=>array('hr'=>1, 'font'=>1, 'input'=>1, 'select'=>1), 'span'=>array('col'=>1, 'colgroup'=>1), 'src'=>array('embed'=>1, 'script'=>1, 'input'=>1, 'iframe'=>1, 'img'=>1), 'standby'=>array('object'=>1), 'start'=>array('ol'=>1), 'summary'=>array('table'=>1), 'tabindex'=>array('a'=>1, 'area'=>1, 'button'=>1, 'input'=>1, 'object'=>1, 'select'=>1, 'textarea'=>1), 'target'=>array('a'=>1, 'area'=>1, 'form'=>1), 'type'=>array('a'=>1, 'embed'=>1, 'object'=>1, 'param'=>1, 'script'=>1, 'input'=>1, 'li'=>1, 'ol'=>1, 'ul'=>1, 'button'=>1), 'usemap'=>array('img'=>1, 'input'=>1, 'object'=>1), 'valign'=>array('col'=>1, 'colgroup'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1), 'value'=>array('input'=>1, 'option'=>1, 'param'=>1, 'button'=>1, 'li'=>1), 'valuetype'=>array('param'=>1), 'vspace'=>array('applet'=>1, 'img'=>1, 'object'=>1), 'width'=>array('embed'=>1, 'hr'=>1, 'iframe'=>1, 'img'=>1, 'object'=>1, 'table'=>1, 'td'=>1, 'th'=>1, 'applet'=>1, 'col'=>1, 'colgroup'=>1, 'pre'=>1), 'wmode'=>array('embed'=>1), 'xml:space'=>array('pre'=>1, 'script'=>1, 'style'=>1)); // Ele-specific
+static $aNE = array('checked'=>1, 'compact'=>1, 'declare'=>1, 'defer'=>1, 'disabled'=>1, 'ismap'=>1, 'multiple'=>1, 'nohref'=>1, 'noresize'=>1, 'noshade'=>1, 'nowrap'=>1, 'readonly'=>1, 'selected'=>1); // Empty
+static $aNP = array('action'=>1, 'cite'=>1, 'classid'=>1, 'codebase'=>1, 'data'=>1, 'href'=>1, 'longdesc'=>1, 'model'=>1, 'pluginspage'=>1, 'pluginurl'=>1, 'usemap'=>1); // Need scheme check; excludes style, on* & src
+static $aNU = array('class'=>array('param'=>1, 'script'=>1), 'dir'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'iframe'=>1, 'param'=>1, 'script'=>1), 'id'=>array('script'=>1), 'lang'=>array('applet'=>1, 'br'=>1, 'iframe'=>1, 'param'=>1, 'script'=>1), 'xml:lang'=>array('applet'=>1, 'br'=>1, 'iframe'=>1, 'param'=>1, 'script'=>1), 'onclick'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'ondblclick'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onkeydown'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onkeypress'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onkeyup'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmousedown'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmousemove'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmouseout'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmouseover'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmouseup'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'style'=>array('param'=>1, 'script'=>1), 'title'=>array('param'=>1, 'script'=>1)); // Univ & exceptions
+
+if($C['lc_std_val']){
+ // predef attr vals for $eAL & $aNE ele
+ static $aNL = array('all'=>1, 'baseline'=>1, 'bottom'=>1, 'button'=>1, 'center'=>1, 'char'=>1, 'checkbox'=>1, 'circle'=>1, 'col'=>1, 'colgroup'=>1, 'cols'=>1, 'data'=>1, 'default'=>1, 'file'=>1, 'get'=>1, 'groups'=>1, 'hidden'=>1, 'image'=>1, 'justify'=>1, 'left'=>1, 'ltr'=>1, 'middle'=>1, 'none'=>1, 'object'=>1, 'password'=>1, 'poly'=>1, 'post'=>1, 'preserve'=>1, 'radio'=>1, 'rect'=>1, 'ref'=>1, 'reset'=>1, 'right'=>1, 'row'=>1, 'rowgroup'=>1, 'rows'=>1, 'rtl'=>1, 'submit'=>1, 'text'=>1, 'top'=>1);
+ static $eAL = array('a'=>1, 'area'=>1, 'bdo'=>1, 'button'=>1, 'col'=>1, 'form'=>1, 'img'=>1, 'input'=>1, 'object'=>1, 'optgroup'=>1, 'option'=>1, 'param'=>1, 'script'=>1, 'select'=>1, 'table'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1, 'xml:space'=>1);
+ $lcase = isset($eAL[$e]) ? 1 : 0;
+}
+
+$depTr = 0;
+if($C['no_deprecated_attr']){
+ // dep attr:applicable ele
+ static $aND = array('align'=>array('caption'=>1, 'div'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'legend'=>1, 'object'=>1, 'p'=>1, 'table'=>1), 'bgcolor'=>array('table'=>1, 'td'=>1, 'th'=>1, 'tr'=>1), 'border'=>array('img'=>1, 'object'=>1), 'bordercolor'=>array('table'=>1, 'td'=>1, 'tr'=>1), 'clear'=>array('br'=>1), 'compact'=>array('dl'=>1, 'ol'=>1, 'ul'=>1), 'height'=>array('td'=>1, 'th'=>1), 'hspace'=>array('img'=>1, 'object'=>1), 'language'=>array('script'=>1), 'name'=>array('a'=>1, 'form'=>1, 'iframe'=>1, 'img'=>1, 'map'=>1), 'noshade'=>array('hr'=>1), 'nowrap'=>array('td'=>1, 'th'=>1), 'size'=>array('hr'=>1), 'start'=>array('ol'=>1), 'type'=>array('li'=>1, 'ol'=>1, 'ul'=>1), 'value'=>array('li'=>1), 'vspace'=>array('img'=>1, 'object'=>1), 'width'=>array('hr'=>1, 'pre'=>1, 'td'=>1, 'th'=>1));
+ static $eAD = array('a'=>1, 'br'=>1, 'caption'=>1, 'div'=>1, 'dl'=>1, 'form'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'hr'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'legend'=>1, 'li'=>1, 'map'=>1, 'object'=>1, 'ol'=>1, 'p'=>1, 'pre'=>1, 'script'=>1, 'table'=>1, 'td'=>1, 'th'=>1, 'tr'=>1, 'ul'=>1);
+ $depTr = isset($eAD[$e]) ? 1 : 0;
+}
+
+// attr name-vals
+if(strpos($a, "\x01") !== false){$a = preg_replace('`\x01[^\x01]*\x01`', '', $a);} // No comment/CDATA sec
+$mode = 0; $a = trim($a, ' /'); $aA = array();
+while(strlen($a)){
+ $w = 0;
+ switch($mode){
+ case 0: // Name
+ if(preg_match('`^[a-zA-Z][\-a-zA-Z:]+`', $a, $m)){
+ $nm = strtolower($m[0]);
+ $w = $mode = 1; $a = ltrim(substr_replace($a, '', 0, strlen($m[0])));
+ }
+ break; case 1:
+ if($a[0] == '='){ // =
+ $w = 1; $mode = 2; $a = ltrim($a, '= ');
+ }else{ // No val
+ $w = 1; $mode = 0; $a = ltrim($a);
+ $aA[$nm] = '';
+ }
+ break; case 2: // Val
+ if(preg_match('`^((?:"[^"]*")|(?:\'[^\']*\')|(?:\s*[^\s"\']+))(.*)`', $a, $m)){
+ $a = ltrim($m[2]); $m = $m[1]; $w = 1; $mode = 0;
+ $aA[$nm] = trim(($m[0] == '"' or $m[0] == '\'') ? substr($m, 1, -1) : $m);
+ }
+ break;
+ }
+ if($w == 0){ // Parse errs, deal with space, " & '
+ $a = preg_replace('`^(?:"[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*`', '', $a);
+ $mode = 0;
+ }
+}
+if($mode == 1){$aA[$nm] = '';}
+
+// clean attrs
+global $S;
+$rl = isset($S[$e]) ? $S[$e] : array();
+$a = array(); $nfr = 0;
+foreach($aA as $k=>$v){
+ if(((isset($C['deny_attribute']['*']) ? isset($C['deny_attribute'][$k]) : !isset($C['deny_attribute'][$k])) && (isset($aN[$k][$e]) or (isset($aNU[$k]) && !isset($aNU[$k][$e]))) && !isset($rl['n'][$k]) && !isset($rl['n']['*'])) or isset($rl[$k])){
+ if(isset($aNE[$k])){$v = $k;}
+ elseif(!empty($lcase) && (($e != 'button' or $e != 'input') or $k == 'type')){ // Rather loose but ?not cause issues
+ $v = (isset($aNL[($v2 = strtolower($v))])) ? $v2 : $v;
+ }
+ if($k == 'style' && !$C['style_pass']){
+ if(false !== strpos($v, '')){
+ static $sC = array(' '=>' ', ' '=>' ', 'E'=>'e', 'E'=>'e', 'e'=>'e', 'e'=>'e', 'X'=>'x', 'X'=>'x', 'x'=>'x', 'x'=>'x', 'P'=>'p', 'P'=>'p', 'p'=>'p', 'p'=>'p', 'S'=>'s', 'S'=>'s', 's'=>'s', 's'=>'s', 'I'=>'i', 'I'=>'i', 'i'=>'i', 'i'=>'i', 'O'=>'o', 'O'=>'o', 'o'=>'o', 'o'=>'o', 'N'=>'n', 'N'=>'n', 'n'=>'n', 'n'=>'n', 'U'=>'u', 'U'=>'u', 'u'=>'u', 'u'=>'u', 'R'=>'r', 'R'=>'r', 'r'=>'r', 'r'=>'r', 'L'=>'l', 'L'=>'l', 'l'=>'l', 'l'=>'l', '('=>'(', '('=>'(', ')'=>')', ')'=>')', ' '=>':', ' '=>':', '"'=>'"', '"'=>'"', '''=>"'", '''=>"'", '/'=>'/', '/'=>'/', '*'=>'*', '*'=>'*', '\'=>'\\', '\'=>'\\');
+ $v = strtr($v, $sC);
+ }
+ $v = preg_replace_callback('`(url(?:\()(?: )*(?:\'|"|&(?:quot|apos);)?)(.+?)((?:\'|"|&(?:quot|apos);)?(?: )*(?:\)))`iS', 'htmLawed::hl_prot', $v);
+ $v = !$C['css_expression'] ? preg_replace('`expression`i', ' ', preg_replace('`\\\\\S|(/|(%2f))(\*|(%2a))`i', ' ', $v)) : $v;
+ }elseif(isset($aNP[$k]) or strpos($k, 'src') !== false or $k[0] == 'o'){
+ $v = str_replace("\xad", ' ', (strpos($v, '&') !== false ? str_replace(array('', '', ''), ' ', $v) : $v));
+ $v = htmLawed::hl_prot($v, $k);
+ if($k == 'href'){ // X-spam
+ if($C['anti_mail_spam'] && strpos($v, 'mailto:') === 0){
+ $v = str_replace('@', htmlspecialchars($C['anti_mail_spam']), $v);
+ }elseif($C['anti_link_spam']){
+ $r1 = $C['anti_link_spam'][1];
+ if(!empty($r1) && preg_match($r1, $v)){continue;}
+ $r0 = $C['anti_link_spam'][0];
+ if(!empty($r0) && preg_match($r0, $v)){
+ if(isset($a['rel'])){
+ if(!preg_match('`\bnofollow\b`i', $a['rel'])){$a['rel'] .= ' nofollow';}
+ }elseif(isset($aA['rel'])){
+ if(!preg_match('`\bnofollow\b`i', $aA['rel'])){$nfr = 1;}
+ }else{$a['rel'] = 'nofollow';}
+ }
+ }
+ }
+ }
+ if(isset($rl[$k]) && is_array($rl[$k]) && ($v = htmLawed::hl_attrval($v, $rl[$k])) === 0){continue;}
+ $a[$k] = str_replace('"', '"', $v);
+ }
+}
+if($nfr){$a['rel'] = isset($a['rel']) ? $a['rel']. ' nofollow' : 'nofollow';}
+
+// rqd attr
+static $eAR = array('area'=>array('alt'=>'area'), 'bdo'=>array('dir'=>'ltr'), 'form'=>array('action'=>''), 'img'=>array('src'=>'', 'alt'=>'image'), 'map'=>array('name'=>''), 'optgroup'=>array('label'=>''), 'param'=>array('name'=>''), 'script'=>array('type'=>'text/javascript'), 'textarea'=>array('rows'=>'10', 'cols'=>'50'));
+if(isset($eAR[$e])){
+ foreach($eAR[$e] as $k=>$v){
+ if(!isset($a[$k])){$a[$k] = isset($v[0]) ? $v : $k;}
+ }
+}
+
+// depr attrs
+if($depTr){
+ $c = array();
+ foreach($a as $k=>$v){
+ if($k == 'style' or !isset($aND[$k][$e])){continue;}
+ if($k == 'align'){
+ unset($a['align']);
+ if($e == 'img' && ($v == 'left' or $v == 'right')){$c[] = 'float: '. $v;}
+ elseif(($e == 'div' or $e == 'table') && $v == 'center'){$c[] = 'margin: auto';}
+ else{$c[] = 'text-align: '. $v;}
+ }elseif($k == 'bgcolor'){
+ unset($a['bgcolor']);
+ $c[] = 'background-color: '. $v;
+ }elseif($k == 'border'){
+ unset($a['border']); $c[] = "border: {$v}px";
+ }elseif($k == 'bordercolor'){
+ unset($a['bordercolor']); $c[] = 'border-color: '. $v;
+ }elseif($k == 'clear'){
+ unset($a['clear']); $c[] = 'clear: '. ($v != 'all' ? $v : 'both');
+ }elseif($k == 'compact'){
+ unset($a['compact']); $c[] = 'font-size: 85%';
+ }elseif($k == 'height' or $k == 'width'){
+ unset($a[$k]); $c[] = $k. ': '. ($v[0] != '*' ? $v. (ctype_digit($v) ? 'px' : '') : 'auto');
+ }elseif($k == 'hspace'){
+ unset($a['hspace']); $c[] = "margin-left: {$v}px; margin-right: {$v}px";
+ }elseif($k == 'language' && !isset($a['type'])){
+ unset($a['language']);
+ $a['type'] = 'text/'. strtolower($v);
+ }elseif($k == 'name'){
+ if($C['no_deprecated_attr'] == 2 or ($e != 'a' && $e != 'map')){unset($a['name']);}
+ if(!isset($a['id']) && preg_match('`[a-zA-Z][a-zA-Z\d.:_\-]*`', $v)){$a['id'] = $v;}
+ }elseif($k == 'noshade'){
+ unset($a['noshade']); $c[] = 'border-style: none; border: 0; background-color: gray; color: gray';
+ }elseif($k == 'nowrap'){
+ unset($a['nowrap']); $c[] = 'white-space: nowrap';
+ }elseif($k == 'size'){
+ unset($a['size']); $c[] = 'size: '. $v. 'px';
+ }elseif($k == 'start' or $k == 'value'){
+ unset($a[$k]);
+ }elseif($k == 'type'){
+ unset($a['type']);
+ static $ol_type = array('i'=>'lower-roman', 'I'=>'upper-roman', 'a'=>'lower-latin', 'A'=>'upper-latin', '1'=>'decimal');
+ $c[] = 'list-style-type: '. (isset($ol_type[$v]) ? $ol_type[$v] : 'decimal');
+ }elseif($k == 'vspace'){
+ unset($a['vspace']); $c[] = "margin-top: {$v}px; margin-bottom: {$v}px";
+ }
+ }
+ if(count($c)){
+ $c = implode('; ', $c);
+ $a['style'] = isset($a['style']) ? rtrim($a['style'], ' ;'). '; '. $c. ';': $c. ';';
+ }
+}
+// unique ID
+if($C['unique_ids'] && isset($a['id'])){
+ if(!preg_match('`^[A-Za-z][A-Za-z0-9_\-.:]*$`', ($id = $a['id'])) or (isset($GLOBALS['hl_Ids'][$id]) && $C['unique_ids'] == 1)){unset($a['id']);
+ }else{
+ while(isset($GLOBALS['hl_Ids'][$id])){$id = $C['unique_ids']. $id;}
+ $GLOBALS['hl_Ids'][($a['id'] = $id)] = 1;
+ }
+}
+// xml:lang
+if($C['xml:lang'] && isset($a['lang'])){
+ $a['xml:lang'] = isset($a['xml:lang']) ? $a['xml:lang'] : $a['lang'];
+ if($C['xml:lang'] == 2){unset($a['lang']);}
+}
+// for transformed tag
+if(!empty($trt)){
+ $a['style'] = isset($a['style']) ? rtrim($a['style'], ' ;'). '; '. $trt : $trt;
+}
+// return with empty ele /
+if(empty($C['hook_tag'])){
+ $aA = '';
+ foreach($a as $k=>$v){$aA .= " {$k}=\"{$v}\"";}
+ return "<{$e}{$aA}". (isset($eE[$e]) ? ' /' : ''). '>';
+}
+else{return $C['hook_tag']($e, $a);}
+// eof
+}
+
+public static function hl_tag2(&$e, &$a, $t=1){
+// transform tag
+if($e == 'center'){$e = 'div'; return 'text-align: center;';}
+if($e == 'dir' or $e == 'menu'){$e = 'ul'; return '';}
+if($e == 's' or $e == 'strike'){$e = 'span'; return 'text-decoration: line-through;';}
+if($e == 'u'){$e = 'span'; return 'text-decoration: underline;';}
+static $fs = array('0'=>'xx-small', '1'=>'xx-small', '2'=>'small', '3'=>'medium', '4'=>'large', '5'=>'x-large', '6'=>'xx-large', '7'=>'300%', '-1'=>'smaller', '-2'=>'60%', '+1'=>'larger', '+2'=>'150%', '+3'=>'200%', '+4'=>'300%');
+if($e == 'font'){
+ $a2 = '';
+ if(preg_match('`face\s*=\s*(\'|")([^=]+?)\\1`i', $a, $m) or preg_match('`face\s*=(\s*)(\S+)`i', $a, $m)){
+ $a2 .= ' font-family: '. str_replace('"', '\'', trim($m[2])). ';';
+ }
+ if(preg_match('`color\s*=\s*(\'|")?(.+?)(\\1|\s|$)`i', $a, $m)){
+ $a2 .= ' color: '. trim($m[2]). ';';
+ }
+ if(preg_match('`size\s*=\s*(\'|")?(.+?)(\\1|\s|$)`i', $a, $m) && isset($fs[($m = trim($m[2]))])){
+ $a2 .= ' font-size: '. $fs[$m]. ';';
+ }
+ $e = 'span'; return ltrim($a2);
+}
+if($t == 2){$e = 0; return 0;}
+return '';
+// eof
+}
+
+public static function hl_tidy($t, $w, $p){
+// Tidy/compact HTM
+if(strpos(' pre,script,textarea', "$p,")){return $t;}
+$t = str_replace(' ', '', preg_replace(array('`(<\w[^>]*(?)\s+`', '`\s+`', '`(<\w[^>]*(?) `'), array(' $1', ' ', '$1'), preg_replace_callback(array('`(<(!\[CDATA\[))(.+?)(\]\]>)`sm', '`(<(!--))(.+?)(-->)`sm', '`(<(pre|script|textarea)[^>]*?>)(.+?)(\2>)`sm'), create_function('$m', 'return $m[1]. str_replace(array("<", ">", "\n", "\r", "\t", " "), array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), $m[3]). $m[4];'), $t)));
+if(($w = strtolower($w)) == -1){
+ return str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), array('<', '>', "\n", "\r", "\t", ' '), $t);
+}
+$s = strpos(" $w", 't') ? "\t" : ' ';
+$s = preg_match('`\d`', $w, $m) ? str_repeat($s, $m[0]) : str_repeat($s, ($s == "\t" ? 1 : 2));
+$N = preg_match('`[ts]([1-9])`', $w, $m) ? $m[1] : 0;
+$a = array('br'=>1);
+$b = array('button'=>1, 'input'=>1, 'option'=>1);
+$c = array('caption'=>1, 'dd'=>1, 'dt'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'isindex'=>1, 'label'=>1, 'legend'=>1, 'li'=>1, 'object'=>1, 'p'=>1, 'pre'=>1, 'td'=>1, 'textarea'=>1, 'th'=>1);
+$d = array('address'=>1, 'blockquote'=>1, 'center'=>1, 'colgroup'=>1, 'dir'=>1, 'div'=>1, 'dl'=>1, 'fieldset'=>1, 'form'=>1, 'hr'=>1, 'iframe'=>1, 'map'=>1, 'menu'=>1, 'noscript'=>1, 'ol'=>1, 'optgroup'=>1, 'rbc'=>1, 'rtc'=>1, 'ruby'=>1, 'script'=>1, 'select'=>1, 'table'=>1, 'tbody'=>1, 'tfoot'=>1, 'thead'=>1, 'tr'=>1, 'ul'=>1);
+$T = explode('<', $t);
+$X = 1;
+while($X){
+ $n = $N;
+ $t = $T;
+ ob_start();
+ if(isset($d[$p])){echo str_repeat($s, ++$n);}
+ echo ltrim(array_shift($t));
+ for($i=-1, $j=count($t); ++$i<$j;){
+ $r = ''; list($e, $r) = explode('>', $t[$i]);
+ $x = $e[0] == '/' ? 0 : (substr($e, -1) == '/' ? 1 : ($e[0] != '!' ? 2 : -1));
+ $y = !$x ? ltrim($e, '/') : ($x > 0 ? substr($e, 0, strcspn($e, ' ')) : 0);
+ $e = "<$e>";
+ if(isset($d[$y])){
+ if(!$x){
+ if($n){echo "\n", str_repeat($s, --$n), "$e\n", str_repeat($s, $n);}
+ else{++$N; ob_end_clean(); continue 2;}
+ }
+ else{echo "\n", str_repeat($s, $n), "$e\n", str_repeat($s, ($x != 1 ? ++$n : $n));}
+ echo ltrim($r); continue;
+ }
+ $f = "\n". str_repeat($s, $n);
+ if(isset($c[$y])){
+ if(!$x){echo $e, $f, ltrim($r);}
+ else{echo $f, $e, $r;}
+ }elseif(isset($b[$y])){echo $f, $e, $r;
+ }elseif(isset($a[$y])){echo $e, $f, ltrim($r);
+ }elseif(!$y){echo $f, $e, $f, ltrim($r);
+ }else{echo $e, $r;}
+ }
+ $X = 0;
+}
+$t = preg_replace('`[\n]\s*?[\n]+`', "\n", ob_get_contents());
+ob_end_clean();
+if(($l = strpos(" $w", 'r') ? (strpos(" $w", 'n') ? "\r\n" : "\r") : 0)){
+ $t = str_replace("\n", $l, $t);
+}
+return str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), array('<', '>', "\n", "\r", "\t", ' '), $t);
+// eof
+}
+
+public static function hl_version(){
+// rel
+return '1.1.14';
+// eof
+}
+
+public static function kses($t, $h, $p=array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'gopher', 'mailto')){
+// kses compat
+foreach($h as $k=>$v){
+ $h[$k]['n']['*'] = 1;
+}
+$C['cdata'] = $C['comment'] = $C['make_tag_strict'] = $C['no_deprecated_attr'] = $C['unique_ids'] = 0;
+$C['keep_bad'] = 1;
+$C['elements'] = count($h) ? strtolower(implode(',', array_keys($h))) : '-*';
+$C['hook'] = 'htmLawed::kses_hook';
+$C['schemes'] = '*:'. implode(',', $p);
+return htmLawed::hl($t, $C, $h);
+// eof
+}
+
+public static function kses_hook($t, &$C, &$S){
+// kses compat
+return $t;
+// eof
+}
+// end class
+}
\ No newline at end of file
diff --git a/libraries/html5/Data.php b/libraries/html5/Data.php
new file mode 100644
index 0000000..497345f
--- /dev/null
+++ b/libraries/html5/Data.php
@@ -0,0 +1,114 @@
+ 0xFFFD, // REPLACEMENT CHARACTER
+ 0x0D => 0x000A, // LINE FEED (LF)
+ 0x80 => 0x20AC, // EURO SIGN ('€')
+ 0x81 => 0x0081, //
+ 0x82 => 0x201A, // SINGLE LOW-9 QUOTATION MARK ('‚')
+ 0x83 => 0x0192, // LATIN SMALL LETTER F WITH HOOK ('ƒ')
+ 0x84 => 0x201E, // DOUBLE LOW-9 QUOTATION MARK ('„')
+ 0x85 => 0x2026, // HORIZONTAL ELLIPSIS ('…')
+ 0x86 => 0x2020, // DAGGER ('†')
+ 0x87 => 0x2021, // DOUBLE DAGGER ('‡')
+ 0x88 => 0x02C6, // MODIFIER LETTER CIRCUMFLEX ACCENT ('ˆ')
+ 0x89 => 0x2030, // PER MILLE SIGN ('‰')
+ 0x8A => 0x0160, // LATIN CAPITAL LETTER S WITH CARON ('Š')
+ 0x8B => 0x2039, // SINGLE LEFT-POINTING ANGLE QUOTATION MARK ('‹')
+ 0x8C => 0x0152, // LATIN CAPITAL LIGATURE OE ('Œ')
+ 0x8D => 0x008D, //
+ 0x8E => 0x017D, // LATIN CAPITAL LETTER Z WITH CARON ('Ž')
+ 0x8F => 0x008F, //
+ 0x90 => 0x0090, //
+ 0x91 => 0x2018, // LEFT SINGLE QUOTATION MARK ('‘')
+ 0x92 => 0x2019, // RIGHT SINGLE QUOTATION MARK ('’')
+ 0x93 => 0x201C, // LEFT DOUBLE QUOTATION MARK ('“')
+ 0x94 => 0x201D, // RIGHT DOUBLE QUOTATION MARK ('”')
+ 0x95 => 0x2022, // BULLET ('•')
+ 0x96 => 0x2013, // EN DASH ('–')
+ 0x97 => 0x2014, // EM DASH ('—')
+ 0x98 => 0x02DC, // SMALL TILDE ('˜')
+ 0x99 => 0x2122, // TRADE MARK SIGN ('™')
+ 0x9A => 0x0161, // LATIN SMALL LETTER S WITH CARON ('š')
+ 0x9B => 0x203A, // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK ('›')
+ 0x9C => 0x0153, // LATIN SMALL LIGATURE OE ('œ')
+ 0x9D => 0x009D, //
+ 0x9E => 0x017E, // LATIN SMALL LETTER Z WITH CARON ('ž')
+ 0x9F => 0x0178, // LATIN CAPITAL LETTER Y WITH DIAERESIS ('Ÿ')
+ );
+
+ protected static $namedCharacterReferences;
+
+ protected static $namedCharacterReferenceMaxLength;
+
+ /**
+ * Returns the "real" Unicode codepoint of a malformed character
+ * reference.
+ */
+ public static function getRealCodepoint($ref) {
+ if (!isset(self::$realCodepointTable[$ref])) return false;
+ else return self::$realCodepointTable[$ref];
+ }
+
+ public static function getNamedCharacterReferences() {
+ if (!self::$namedCharacterReferences) {
+ self::$namedCharacterReferences = unserialize(
+ file_get_contents(dirname(__FILE__) . '/named-character-references.ser'));
+ }
+ return self::$namedCharacterReferences;
+ }
+
+ /**
+ * Converts a Unicode codepoint to sequence of UTF-8 bytes.
+ * @note Shamelessly stolen from HTML Purifier, which is also
+ * shamelessly stolen from Feyd (which is in public domain).
+ */
+ public static function utf8chr($code) {
+ /* We don't care: we live dangerously
+ * if($code > 0x10FFFF or $code < 0x0 or
+ ($code >= 0xD800 and $code <= 0xDFFF) ) {
+ // bits are set outside the "valid" range as defined
+ // by UNICODE 4.1.0
+ return "\xEF\xBF\xBD";
+ }*/
+
+ $x = $y = $z = $w = 0;
+ if ($code < 0x80) {
+ // regular ASCII character
+ $x = $code;
+ } else {
+ // set up bits for UTF-8
+ $x = ($code & 0x3F) | 0x80;
+ if ($code < 0x800) {
+ $y = (($code & 0x7FF) >> 6) | 0xC0;
+ } else {
+ $y = (($code & 0xFC0) >> 6) | 0x80;
+ if($code < 0x10000) {
+ $z = (($code >> 12) & 0x0F) | 0xE0;
+ } else {
+ $z = (($code >> 12) & 0x3F) | 0x80;
+ $w = (($code >> 18) & 0x07) | 0xF0;
+ }
+ }
+ }
+ // set up the actual character
+ $ret = '';
+ if($w) $ret .= chr($w);
+ if($z) $ret .= chr($z);
+ if($y) $ret .= chr($y);
+ $ret .= chr($x);
+
+ return $ret;
+ }
+
+}
diff --git a/libraries/html5/InputStream.php b/libraries/html5/InputStream.php
new file mode 100644
index 0000000..f98b427
--- /dev/null
+++ b/libraries/html5/InputStream.php
@@ -0,0 +1,284 @@
+
+
+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.
+
+*/
+
+// Some conventions:
+// /* */ indicates verbatim text from the HTML 5 specification
+// // indicates regular comments
+
+class HTML5_InputStream {
+ /**
+ * The string data we're parsing.
+ */
+ private $data;
+
+ /**
+ * The current integer byte position we are in $data
+ */
+ private $char;
+
+ /**
+ * Length of $data; when $char === $data, we are at the end-of-file.
+ */
+ private $EOF;
+
+ /**
+ * Parse errors.
+ */
+ public $errors = array();
+
+ /**
+ * @param $data Data to parse
+ */
+ public function __construct($data) {
+
+ /* Given an encoding, the bytes in the input stream must be
+ converted to Unicode characters for the tokeniser, as
+ described by the rules for that encoding, except that the
+ leading U+FEFF BYTE ORDER MARK character, if any, must not
+ be stripped by the encoding layer (it is stripped by the rule below).
+
+ Bytes or sequences of bytes in the original byte stream that
+ could not be converted to Unicode characters must be converted
+ to U+FFFD REPLACEMENT CHARACTER code points. */
+
+ // XXX currently assuming input data is UTF-8; once we
+ // build encoding detection this will no longer be the case
+ //
+ // We previously had an mbstring implementation here, but that
+ // implementation is heavily non-conforming, so it's been
+ // omitted.
+ if (extension_loaded('iconv')) {
+ // non-conforming
+ $data = @iconv('UTF-8', 'UTF-8//IGNORE', $data);
+ } else {
+ // we can make a conforming native implementation
+ throw new Exception('Not implemented, please install mbstring or iconv');
+ }
+
+ /* One leading U+FEFF BYTE ORDER MARK character must be
+ ignored if any are present. */
+ if (substr($data, 0, 3) === "\xEF\xBB\xBF") {
+ $data = substr($data, 3);
+ }
+
+ /* All U+0000 NULL characters in the input must be replaced
+ by U+FFFD REPLACEMENT CHARACTERs. Any occurrences of such
+ characters is a parse error. */
+ for ($i = 0, $count = substr_count($data, "\0"); $i < $count; $i++) {
+ $this->errors[] = array(
+ 'type' => HTML5_Tokenizer::PARSEERROR,
+ 'data' => 'null-character'
+ );
+ }
+ /* U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED
+ (LF) characters are treated specially. Any CR characters
+ that are followed by LF characters must be removed, and any
+ CR characters not followed by LF characters must be converted
+ to LF characters. Thus, newlines in HTML DOMs are represented
+ by LF characters, and there are never any CR characters in the
+ input to the tokenization stage. */
+ $data = str_replace(
+ array(
+ "\0",
+ "\r\n",
+ "\r"
+ ),
+ array(
+ "\xEF\xBF\xBD",
+ "\n",
+ "\n"
+ ),
+ $data
+ );
+
+ /* Any occurrences of any characters in the ranges U+0001 to
+ U+0008, U+000B, U+000E to U+001F, U+007F to U+009F,
+ U+D800 to U+DFFF , U+FDD0 to U+FDEF, and
+ characters 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, and
+ U+10FFFF are parse errors. (These are all control characters
+ or permanently undefined Unicode characters.) */
+ // Check PCRE is loaded.
+ if (extension_loaded('pcre')) {
+ $count = preg_match_all(
+ '/(?:
+ [\x01-\x08\x0B\x0E-\x1F\x7F] # U+0001 to U+0008, U+000B, U+000E to U+001F and U+007F
+ |
+ \xC2[\x80-\x9F] # U+0080 to U+009F
+ |
+ \xED(?:\xA0[\x80-\xFF]|[\xA1-\xBE][\x00-\xFF]|\xBF[\x00-\xBF]) # U+D800 to U+DFFFF
+ |
+ \xEF\xB7[\x90-\xAF] # U+FDD0 to U+FDEF
+ |
+ \xEF\xBF[\xBE\xBF] # U+FFFE and U+FFFF
+ |
+ [\xF0-\xF4][\x8F-\xBF]\xBF[\xBE\xBF] # U+nFFFE and U+nFFFF (1 <= n <= 10_{16})
+ )/x',
+ $data,
+ $matches
+ );
+ for ($i = 0; $i < $count; $i++) {
+ $this->errors[] = array(
+ 'type' => HTML5_Tokenizer::PARSEERROR,
+ 'data' => 'invalid-codepoint'
+ );
+ }
+ } else {
+ // XXX: Need non-PCRE impl, probably using substr_count
+ }
+
+ $this->data = $data;
+ $this->char = 0;
+ $this->EOF = strlen($data);
+ }
+
+ /**
+ * Returns the current line that the tokenizer is at.
+ */
+ public function getCurrentLine() {
+ // Check the string isn't empty
+ if($this->EOF) {
+ // Add one to $this->char because we want the number for the next
+ // byte to be processed.
+ return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
+ } else {
+ // If the string is empty, we are on the first line (sorta).
+ return 1;
+ }
+ }
+
+ /**
+ * Returns the current column of the current line that the tokenizer is at.
+ */
+ public function getColumnOffset() {
+ // strrpos is weird, and the offset needs to be negative for what we
+ // want (i.e., the last \n before $this->char). This needs to not have
+ // one (to make it point to the next character, the one we want the
+ // position of) added to it because strrpos's behaviour includes the
+ // final offset byte.
+ $lastLine = strrpos($this->data, "\n", $this->char - 1 - strlen($this->data));
+
+ // However, for here we want the length up until the next byte to be
+ // processed, so add one to the current byte ($this->char).
+ if($lastLine !== false) {
+ $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
+ } else {
+ $findLengthOf = substr($this->data, 0, $this->char);
+ }
+
+ // Get the length for the string we need.
+ if(extension_loaded('iconv')) {
+ return iconv_strlen($findLengthOf, 'utf-8');
+ } elseif(extension_loaded('mbstring')) {
+ return mb_strlen($findLengthOf, 'utf-8');
+ } elseif(extension_loaded('xml')) {
+ return strlen(utf8_decode($findLengthOf));
+ } else {
+ $count = count_chars($findLengthOf);
+ // 0x80 = 0x7F - 0 + 1 (one added to get inclusive range)
+ // 0x33 = 0xF4 - 0x2C + 1 (one added to get inclusive range)
+ return array_sum(array_slice($count, 0, 0x80)) +
+ array_sum(array_slice($count, 0xC2, 0x33));
+ }
+ }
+
+ /**
+ * Retrieve the currently consume character.
+ * @note This performs bounds checking
+ */
+ public function char() {
+ return ($this->char++ < $this->EOF)
+ ? $this->data[$this->char - 1]
+ : false;
+ }
+
+ /**
+ * Get all characters until EOF.
+ * @note This performs bounds checking
+ */
+ public function remainingChars() {
+ if($this->char < $this->EOF) {
+ $data = substr($this->data, $this->char);
+ $this->char = $this->EOF;
+ return $data;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Matches as far as possible until we reach a certain set of bytes
+ * and returns the matched substring.
+ * @param $bytes Bytes to match.
+ */
+ public function charsUntil($bytes, $max = null) {
+ if ($this->char < $this->EOF) {
+ if ($max === 0 || $max) {
+ $len = strcspn($this->data, $bytes, $this->char, $max);
+ } else {
+ $len = strcspn($this->data, $bytes, $this->char);
+ }
+ $string = (string) substr($this->data, $this->char, $len);
+ $this->char += $len;
+ return $string;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Matches as far as possible with a certain set of bytes
+ * and returns the matched substring.
+ * @param $bytes Bytes to match.
+ */
+ public function charsWhile($bytes, $max = null) {
+ if ($this->char < $this->EOF) {
+ if ($max === 0 || $max) {
+ $len = strspn($this->data, $bytes, $this->char, $max);
+ } else {
+ $len = strspn($this->data, $bytes, $this->char);
+ }
+ $string = (string) substr($this->data, $this->char, $len);
+ $this->char += $len;
+ return $string;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Unconsume one character.
+ */
+ public function unget() {
+ if ($this->char <= $this->EOF) {
+ $this->char--;
+ }
+ }
+}
diff --git a/libraries/html5/Parser.php b/libraries/html5/Parser.php
new file mode 100644
index 0000000..5f9ca56
--- /dev/null
+++ b/libraries/html5/Parser.php
@@ -0,0 +1,36 @@
+parse();
+ return $tokenizer->save();
+ }
+ /**
+ * Parses an HTML fragment.
+ * @param $text HTML text to parse
+ * @param $context String name of context element to pretend parsing is in.
+ * @param $builder Custom builder implementation
+ * @return Parsed HTML as DOMDocument
+ */
+ static public function parseFragment($text, $context = null, $builder = null) {
+ $tokenizer = new HTML5_Tokenizer($text, $builder);
+ $tokenizer->parseFragment($context);
+ return $tokenizer->save();
+ }
+}
diff --git a/libraries/html5/Tokenizer.php b/libraries/html5/Tokenizer.php
new file mode 100644
index 0000000..0af0716
--- /dev/null
+++ b/libraries/html5/Tokenizer.php
@@ -0,0 +1,2422 @@
+
+Copyright 2008 Edward Z. Yang
+Copyright 2009 Geoffrey Sneddon
+
+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.
+
+*/
+
+// Some conventions:
+// /* */ indicates verbatim text from the HTML 5 specification
+// // indicates regular comments
+
+// all flags are in hyphenated form
+
+class HTML5_Tokenizer {
+ /**
+ * Points to an InputStream object.
+ */
+ protected $stream;
+
+ /**
+ * Tree builder that the tokenizer emits token to.
+ */
+ private $tree;
+
+ /**
+ * Current content model we are parsing as.
+ */
+ protected $content_model;
+
+ /**
+ * Current token that is being built, but not yet emitted. Also
+ * is the last token emitted, if applicable.
+ */
+ protected $token;
+
+ // These are constants describing the content model
+ const PCDATA = 0;
+ const RCDATA = 1;
+ const CDATA = 2;
+ const PLAINTEXT = 3;
+
+ // These are constants describing tokens
+ // XXX should probably be moved somewhere else, probably the
+ // HTML5 class.
+ const DOCTYPE = 0;
+ const STARTTAG = 1;
+ const ENDTAG = 2;
+ const COMMENT = 3;
+ const CHARACTER = 4;
+ const SPACECHARACTER = 5;
+ const EOF = 6;
+ const PARSEERROR = 7;
+
+ // These are constants representing bunches of characters.
+ const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+ const UPPER_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ const LOWER_ALPHA = 'abcdefghijklmnopqrstuvwxyz';
+ const DIGIT = '0123456789';
+ const HEX = '0123456789ABCDEFabcdef';
+ const WHITESPACE = "\t\n\x0c ";
+
+ /**
+ * @param $data Data to parse
+ */
+ public function __construct($data, $builder = null) {
+ $this->stream = new HTML5_InputStream($data);
+ if (!$builder) $this->tree = new HTML5_TreeBuilder;
+ else $this->tree = $builder;
+ $this->content_model = self::PCDATA;
+ }
+
+ public function parseFragment($context = null) {
+ $this->tree->setupContext($context);
+ if ($this->tree->content_model) {
+ $this->content_model = $this->tree->content_model;
+ $this->tree->content_model = null;
+ }
+ $this->parse();
+ }
+
+ // XXX maybe convert this into an iterator? regardless, this function
+ // and the save function should go into a Parser facade of some sort
+ /**
+ * Performs the actual parsing of the document.
+ */
+ public function parse() {
+ // Current state
+ $state = 'data';
+ // This is used to avoid having to have look-behind in the data state.
+ $lastFourChars = '';
+ /**
+ * Escape flag as specified by the HTML5 specification: "used to
+ * control the behavior of the tokeniser. It is either true or
+ * false, and initially must be set to the false state."
+ */
+ $escape = false;
+ //echo "\n\n";
+ while($state !== null) {
+
+ /*echo $state . ' ';
+ switch ($this->content_model) {
+ case self::PCDATA: echo 'PCDATA'; break;
+ case self::RCDATA: echo 'RCDATA'; break;
+ case self::CDATA: echo 'CDATA'; break;
+ case self::PLAINTEXT: echo 'PLAINTEXT'; break;
+ }
+ if ($escape) echo " escape";
+ echo "\n";*/
+
+ switch($state) {
+ case 'data':
+
+ /* Consume the next input character */
+ $char = $this->stream->char();
+ $lastFourChars .= $char;
+ if (strlen($lastFourChars) > 4) $lastFourChars = substr($lastFourChars, -4);
+
+ // see below for meaning
+ $hyp_cond =
+ !$escape &&
+ (
+ $this->content_model === self::RCDATA ||
+ $this->content_model === self::CDATA
+ );
+ $amp_cond =
+ !$escape &&
+ (
+ $this->content_model === self::PCDATA ||
+ $this->content_model === self::RCDATA
+ );
+ $lt_cond =
+ $this->content_model === self::PCDATA ||
+ (
+ (
+ $this->content_model === self::RCDATA ||
+ $this->content_model === self::CDATA
+ ) &&
+ !$escape
+ );
+ $gt_cond =
+ $escape &&
+ (
+ $this->content_model === self::RCDATA ||
+ $this->content_model === self::CDATA
+ );
+
+ if($char === '&' && $amp_cond) {
+ /* U+0026 AMPERSAND (&)
+ When the content model flag is set to one of the PCDATA or RCDATA
+ states and the escape flag is false: switch to the
+ character reference data state. Otherwise: treat it as per
+ the "anything else" entry below. */
+ $state = 'character reference data';
+
+ } elseif(
+ $char === '-' &&
+ $hyp_cond &&
+ $lastFourChars === ''
+ ) {
+ /* If the content model flag is set to either the RCDATA state or
+ the CDATA state, and the escape flag is true, and the last three
+ characters in the input stream including this one are U+002D
+ HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN ("-->"),
+ set the escape flag to false. */
+ $escape = false;
+
+ /* In any case, emit the input character as a character token.
+ Stay in the data state. */
+ $this->emitToken(array(
+ 'type' => self::CHARACTER,
+ 'data' => '>'
+ ));
+ // We do the "any case" part as part of "anything else".
+
+ } elseif($char === false) {
+ /* EOF
+ Emit an end-of-file token. */
+ $state = null;
+ $this->tree->emitToken(array(
+ 'type' => self::EOF
+ ));
+
+ } elseif($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ // Directly after emitting a token you switch back to the "data
+ // state". At that point spaceCharacters are important so they are
+ // emitted separately.
+ $chars = $this->stream->charsWhile(self::WHITESPACE);
+ $this->emitToken(array(
+ 'type' => self::SPACECHARACTER,
+ 'data' => $char . $chars
+ ));
+ $lastFourChars .= $chars;
+ if (strlen($lastFourChars) > 4) $lastFourChars = substr($lastFourChars, -4);
+
+ } else {
+ /* Anything else
+ THIS IS AN OPTIMIZATION: Get as many character that
+ otherwise would also be treated as a character token and emit it
+ as a single character token. Stay in the data state. */
+
+ $mask = '';
+ if ($hyp_cond) $mask .= '-';
+ if ($amp_cond) $mask .= '&';
+ if ($lt_cond) $mask .= '<';
+ if ($gt_cond) $mask .= '>';
+
+ if ($mask === '') {
+ $chars = $this->stream->remainingChars();
+ } else {
+ $chars = $this->stream->charsUntil($mask);
+ }
+
+ $this->emitToken(array(
+ 'type' => self::CHARACTER,
+ 'data' => $char . $chars
+ ));
+
+ $lastFourChars .= $chars;
+ if (strlen($lastFourChars) > 4) $lastFourChars = substr($lastFourChars, -4);
+
+ $state = 'data';
+ }
+ break;
+
+ case 'character reference data':
+ /* (This cannot happen if the content model flag
+ is set to the CDATA state.) */
+
+ /* Attempt to consume a character reference, with no
+ additional allowed character. */
+ $entity = $this->consumeCharacterReference();
+
+ /* If nothing is returned, emit a U+0026 AMPERSAND
+ character token. Otherwise, emit the character token that
+ was returned. */
+ // This is all done when consuming the character reference.
+ $this->emitToken(array(
+ 'type' => self::CHARACTER,
+ 'data' => $entity
+ ));
+
+ /* Finally, switch to the data state. */
+ $state = 'data';
+ break;
+
+ case 'tag open':
+ $char = $this->stream->char();
+
+ switch($this->content_model) {
+ case self::RCDATA:
+ case self::CDATA:
+ /* Consume the next input character. If it is a
+ U+002F SOLIDUS (/) character, switch to the close
+ tag open state. Otherwise, emit a U+003C LESS-THAN
+ SIGN character token and reconsume the current input
+ character in the data state. */
+ // We consumed above.
+
+ if($char === '/') {
+ $state = 'close tag open';
+
+ } else {
+ $this->emitToken(array(
+ 'type' => self::CHARACTER,
+ 'data' => '<'
+ ));
+
+ $this->stream->unget();
+
+ $state = 'data';
+ }
+ break;
+
+ case self::PCDATA:
+ /* If the content model flag is set to the PCDATA state
+ Consume the next input character: */
+ // We consumed above.
+
+ if($char === '!') {
+ /* U+0021 EXCLAMATION MARK (!)
+ Switch to the markup declaration open state. */
+ $state = 'markup declaration open';
+
+ } elseif($char === '/') {
+ /* U+002F SOLIDUS (/)
+ Switch to the close tag open state. */
+ $state = 'close tag open';
+
+ } elseif('A' <= $char && $char <= 'Z') {
+ /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
+ Create a new start tag token, set its tag name to the lowercase
+ version of the input character (add 0x0020 to the character's code
+ point), then switch to the tag name state. (Don't emit the token
+ yet; further details will be filled in before it is emitted.) */
+ $this->token = array(
+ 'name' => strtolower($char),
+ 'type' => self::STARTTAG,
+ 'attr' => array()
+ );
+
+ $state = 'tag name';
+
+ } elseif('a' <= $char && $char <= 'z') {
+ /* U+0061 LATIN SMALL LETTER A through to U+007A LATIN SMALL LETTER Z
+ Create a new start tag token, set its tag name to the input
+ character, then switch to the tag name state. (Don't emit
+ the token yet; further details will be filled in before it
+ is emitted.) */
+ $this->token = array(
+ 'name' => $char,
+ 'type' => self::STARTTAG,
+ 'attr' => array()
+ );
+
+ $state = 'tag name';
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Emit a U+003C LESS-THAN SIGN character token and a
+ U+003E GREATER-THAN SIGN character token. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-tag-name-but-got-right-bracket'
+ ));
+ $this->emitToken(array(
+ 'type' => self::CHARACTER,
+ 'data' => '<>'
+ ));
+
+ $state = 'data';
+
+ } elseif($char === '?') {
+ /* U+003F QUESTION MARK (?)
+ Parse error. Switch to the bogus comment state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-tag-name-but-got-question-mark'
+ ));
+ $this->token = array(
+ 'data' => '?',
+ 'type' => self::COMMENT
+ );
+ $state = 'bogus comment';
+
+ } else {
+ /* Anything else
+ Parse error. Emit a U+003C LESS-THAN SIGN character token and
+ reconsume the current input character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-tag-name'
+ ));
+ $this->emitToken(array(
+ 'type' => self::CHARACTER,
+ 'data' => '<'
+ ));
+
+ $state = 'data';
+ $this->stream->unget();
+ }
+ break;
+ }
+ break;
+
+ case 'close tag open':
+ if (
+ $this->content_model === self::RCDATA ||
+ $this->content_model === self::CDATA
+ ) {
+ /* If the content model flag is set to the RCDATA or CDATA
+ states... */
+ $name = strtolower($this->stream->charsWhile(self::ALPHA));
+ $following = $this->stream->char();
+ $this->stream->unget();
+ if (
+ !$this->token ||
+ $this->token['name'] !== $name ||
+ $this->token['name'] === $name && !in_array($following, array("\x09", "\x0A", "\x0C", "\x20", "\x3E", "\x2F", false))
+ ) {
+ /* if no start tag token has ever been emitted by this instance
+ of the tokenizer (fragment case), or, if the next few
+ characters do not match the tag name of the last start tag
+ token emitted (compared in an ASCII case-insensitive manner),
+ or if they do but they are not immediately followed by one of
+ the following characters:
+
+ * U+0009 CHARACTER TABULATION
+ * U+000A LINE FEED (LF)
+ * U+000C FORM FEED (FF)
+ * U+0020 SPACE
+ * U+003E GREATER-THAN SIGN (>)
+ * U+002F SOLIDUS (/)
+ * EOF
+
+ ...then emit a U+003C LESS-THAN SIGN character token, a
+ U+002F SOLIDUS character token, and switch to the data
+ state to process the next input character. */
+ // XXX: Probably ought to replace in_array with $following === x ||...
+
+ // We also need to emit $name now we've consumed that, as we
+ // know it'll just be emitted as a character token.
+ $this->emitToken(array(
+ 'type' => self::CHARACTER,
+ 'data' => '' . $name
+ ));
+
+ $state = 'data';
+ } else {
+ // This matches what would happen if we actually did the
+ // otherwise below (but we can't because we've consumed too
+ // much).
+
+ // Start the end tag token with the name we already have.
+ $this->token = array(
+ 'name' => $name,
+ 'type' => self::ENDTAG
+ );
+
+ // Change to tag name state.
+ $state = 'tag name';
+ }
+ } elseif ($this->content_model === self::PCDATA) {
+ /* Otherwise, if the content model flag is set to the PCDATA
+ state [...]: */
+ $char = $this->stream->char();
+
+ if ('A' <= $char && $char <= 'Z') {
+ /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
+ Create a new end tag token, set its tag name to the lowercase version
+ of the input character (add 0x0020 to the character's code point), then
+ switch to the tag name state. (Don't emit the token yet; further details
+ will be filled in before it is emitted.) */
+ $this->token = array(
+ 'name' => strtolower($char),
+ 'type' => self::ENDTAG
+ );
+
+ $state = 'tag name';
+
+ } elseif ('a' <= $char && $char <= 'z') {
+ /* U+0061 LATIN SMALL LETTER A through to U+007A LATIN SMALL LETTER Z
+ Create a new end tag token, set its tag name to the
+ input character, then switch to the tag name state.
+ (Don't emit the token yet; further details will be
+ filled in before it is emitted.) */
+ $this->token = array(
+ 'name' => $char,
+ 'type' => self::ENDTAG
+ );
+
+ $state = 'tag name';
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-closing-tag-but-got-right-bracket'
+ ));
+ $state = 'data';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F
+ SOLIDUS character token. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-closing-tag-but-got-eof'
+ ));
+ $this->emitToken(array(
+ 'type' => self::CHARACTER,
+ 'data' => ''
+ ));
+
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Parse error. Switch to the bogus comment state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-closing-tag-but-got-char'
+ ));
+ $this->token = array(
+ 'data' => $char,
+ 'type' => self::COMMENT
+ );
+ $state = 'bogus comment';
+ }
+ }
+ break;
+
+ case 'tag name':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the before attribute name state. */
+ $state = 'before attribute name';
+
+ } elseif($char === '/') {
+ /* U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state. */
+ $state = 'self-closing start tag';
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif('A' <= $char && $char <= 'Z') {
+ /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Append the lowercase version of the current input
+ character (add 0x0020 to the character's code point) to
+ the current tag token's tag name. Stay in the tag name state. */
+ $chars = $this->stream->charsWhile(self::UPPER_ALPHA);
+
+ $this->token['name'] .= strtolower($char . $chars);
+ $state = 'tag name';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-tag-name'
+ ));
+
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Append the current input character to the current tag token's tag name.
+ Stay in the tag name state. */
+ $chars = $this->stream->charsUntil("\t\n\x0C />" . self::UPPER_ALPHA);
+
+ $this->token['name'] .= $char . $chars;
+ $state = 'tag name';
+ }
+ break;
+
+ case 'before attribute name':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ // this conditional is optimized, check bottom
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before attribute name state. */
+ $state = 'before attribute name';
+
+ } elseif($char === '/') {
+ /* U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state. */
+ $state = 'self-closing start tag';
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif('A' <= $char && $char <= 'Z') {
+ /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Start a new attribute in the current tag token. Set that
+ attribute's name to the lowercase version of the current
+ input character (add 0x0020 to the character's code
+ point), and its value to the empty string. Switch to the
+ attribute name state.*/
+ $this->token['attr'][] = array(
+ 'name' => strtolower($char),
+ 'value' => ''
+ );
+
+ $state = 'attribute name';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-attribute-name-but-got-eof'
+ ));
+
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* U+0022 QUOTATION MARK (")
+ U+0027 APOSTROPHE (')
+ U+003C LESS-THAN SIGN (<)
+ U+003D EQUALS SIGN (=)
+ Parse error. Treat it as per the "anything else" entry
+ below. */
+ if($char === '"' || $char === "'" || $char === '<' || $char === '=') {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'invalid-character-in-attribute-name'
+ ));
+ }
+
+ /* Anything else
+ Start a new attribute in the current tag token. Set that attribute's
+ name to the current input character, and its value to the empty string.
+ Switch to the attribute name state. */
+ $this->token['attr'][] = array(
+ 'name' => $char,
+ 'value' => ''
+ );
+
+ $state = 'attribute name';
+ }
+ break;
+
+ case 'attribute name':
+ // Consume the next input character:
+ $char = $this->stream->char();
+
+ // this conditional is optimized, check bottom
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the after attribute name state. */
+ $state = 'after attribute name';
+
+ } elseif($char === '/') {
+ /* U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state. */
+ $state = 'self-closing start tag';
+
+ } elseif($char === '=') {
+ /* U+003D EQUALS SIGN (=)
+ Switch to the before attribute value state. */
+ $state = 'before attribute value';
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif('A' <= $char && $char <= 'Z') {
+ /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Append the lowercase version of the current input
+ character (add 0x0020 to the character's code point) to
+ the current attribute's name. Stay in the attribute name
+ state. */
+ $chars = $this->stream->charsWhile(self::UPPER_ALPHA);
+
+ $last = count($this->token['attr']) - 1;
+ $this->token['attr'][$last]['name'] .= strtolower($char . $chars);
+
+ $state = 'attribute name';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-attribute-name'
+ ));
+
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* U+0022 QUOTATION MARK (")
+ U+0027 APOSTROPHE (')
+ U+003C LESS-THAN SIGN (<)
+ Parse error. Treat it as per the "anything else"
+ entry below. */
+ if($char === '"' || $char === "'" || $char === '<') {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'invalid-character-in-attribute-name'
+ ));
+ }
+
+ /* Anything else
+ Append the current input character to the current attribute's name.
+ Stay in the attribute name state. */
+ $chars = $this->stream->charsUntil("\t\n\x0C /=>\"'" . self::UPPER_ALPHA);
+
+ $last = count($this->token['attr']) - 1;
+ $this->token['attr'][$last]['name'] .= $char . $chars;
+
+ $state = 'attribute name';
+ }
+
+ /* When the user agent leaves the attribute name state
+ (and before emitting the tag token, if appropriate), the
+ complete attribute's name must be compared to the other
+ attributes on the same token; if there is already an
+ attribute on the token with the exact same name, then this
+ is a parse error and the new attribute must be dropped, along
+ with the value that gets associated with it (if any). */
+ // this might be implemented in the emitToken method
+ break;
+
+ case 'after attribute name':
+ // Consume the next input character:
+ $char = $this->stream->char();
+
+ // this is an optimized conditional, check the bottom
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the after attribute name state. */
+ $state = 'after attribute name';
+
+ } elseif($char === '/') {
+ /* U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state. */
+ $state = 'self-closing start tag';
+
+ } elseif($char === '=') {
+ /* U+003D EQUALS SIGN (=)
+ Switch to the before attribute value state. */
+ $state = 'before attribute value';
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif('A' <= $char && $char <= 'Z') {
+ /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Start a new attribute in the current tag token. Set that
+ attribute's name to the lowercase version of the current
+ input character (add 0x0020 to the character's code
+ point), and its value to the empty string. Switch to the
+ attribute name state. */
+ $this->token['attr'][] = array(
+ 'name' => strtolower($char),
+ 'value' => ''
+ );
+
+ $state = 'attribute name';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-end-of-tag-but-got-eof'
+ ));
+
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* U+0022 QUOTATION MARK (")
+ U+0027 APOSTROPHE (')
+ U+003C LESS-THAN SIGN(<)
+ Parse error. Treat it as per the "anything else"
+ entry below. */
+ if($char === '"' || $char === "'" || $char === "<") {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'invalid-character-after-attribute-name'
+ ));
+ }
+
+ /* Anything else
+ Start a new attribute in the current tag token. Set that attribute's
+ name to the current input character, and its value to the empty string.
+ Switch to the attribute name state. */
+ $this->token['attr'][] = array(
+ 'name' => $char,
+ 'value' => ''
+ );
+
+ $state = 'attribute name';
+ }
+ break;
+
+ case 'before attribute value':
+ // Consume the next input character:
+ $char = $this->stream->char();
+
+ // this is an optimized conditional
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before attribute value state. */
+ $state = 'before attribute value';
+
+ } elseif($char === '"') {
+ /* U+0022 QUOTATION MARK (")
+ Switch to the attribute value (double-quoted) state. */
+ $state = 'attribute value (double-quoted)';
+
+ } elseif($char === '&') {
+ /* U+0026 AMPERSAND (&)
+ Switch to the attribute value (unquoted) state and reconsume
+ this input character. */
+ $this->stream->unget();
+ $state = 'attribute value (unquoted)';
+
+ } elseif($char === '\'') {
+ /* U+0027 APOSTROPHE (')
+ Switch to the attribute value (single-quoted) state. */
+ $state = 'attribute value (single-quoted)';
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Emit the current tag token. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-attribute-value-but-got-right-bracket'
+ ));
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-attribute-value-but-got-eof'
+ ));
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* U+003D EQUALS SIGN (=)
+ * U+003C LESS-THAN SIGN (<)
+ Parse error. Treat it as per the "anything else" entry below. */
+ if($char === '=' || $char === '<') {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'equals-in-unquoted-attribute-value'
+ ));
+ }
+
+ /* Anything else
+ Append the current input character to the current attribute's value.
+ Switch to the attribute value (unquoted) state. */
+ $last = count($this->token['attr']) - 1;
+ $this->token['attr'][$last]['value'] .= $char;
+
+ $state = 'attribute value (unquoted)';
+ }
+ break;
+
+ case 'attribute value (double-quoted)':
+ // Consume the next input character:
+ $char = $this->stream->char();
+
+ if($char === '"') {
+ /* U+0022 QUOTATION MARK (")
+ Switch to the after attribute value (quoted) state. */
+ $state = 'after attribute value (quoted)';
+
+ } elseif($char === '&') {
+ /* U+0026 AMPERSAND (&)
+ Switch to the character reference in attribute value
+ state, with the additional allowed character
+ being U+0022 QUOTATION MARK ("). */
+ $this->characterReferenceInAttributeValue('"');
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-attribute-value-double-quote'
+ ));
+
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Append the current input character to the current attribute's value.
+ Stay in the attribute value (double-quoted) state. */
+ $chars = $this->stream->charsUntil('"&');
+
+ $last = count($this->token['attr']) - 1;
+ $this->token['attr'][$last]['value'] .= $char . $chars;
+
+ $state = 'attribute value (double-quoted)';
+ }
+ break;
+
+ case 'attribute value (single-quoted)':
+ // Consume the next input character:
+ $char = $this->stream->char();
+
+ if($char === "'") {
+ /* U+0022 QUOTATION MARK (')
+ Switch to the after attribute value state. */
+ $state = 'after attribute value (quoted)';
+
+ } elseif($char === '&') {
+ /* U+0026 AMPERSAND (&)
+ Switch to the entity in attribute value state. */
+ $this->characterReferenceInAttributeValue("'");
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-attribute-value-single-quote'
+ ));
+
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Append the current input character to the current attribute's value.
+ Stay in the attribute value (single-quoted) state. */
+ $chars = $this->stream->charsUntil("'&");
+
+ $last = count($this->token['attr']) - 1;
+ $this->token['attr'][$last]['value'] .= $char . $chars;
+
+ $state = 'attribute value (single-quoted)';
+ }
+ break;
+
+ case 'attribute value (unquoted)':
+ // Consume the next input character:
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the before attribute name state. */
+ $state = 'before attribute name';
+
+ } elseif($char === '&') {
+ /* U+0026 AMPERSAND (&)
+ Switch to the entity in attribute value state, with the
+ additional allowed character being U+003E
+ GREATER-THAN SIGN (>). */
+ $this->characterReferenceInAttributeValue('>');
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif ($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-attribute-value-no-quotes'
+ ));
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* U+0022 QUOTATION MARK (")
+ U+0027 APOSTROPHE (')
+ U+003C LESS-THAN SIGN (<)
+ U+003D EQUALS SIGN (=)
+ Parse error. Treat it as per the "anything else"
+ entry below. */
+ if($char === '"' || $char === "'" || $char === '=' || $char == '<') {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-character-in-unquoted-attribute-value'
+ ));
+ }
+
+ /* Anything else
+ Append the current input character to the current attribute's value.
+ Stay in the attribute value (unquoted) state. */
+ $chars = $this->stream->charsUntil("\t\n\x0c &>\"'=");
+
+ $last = count($this->token['attr']) - 1;
+ $this->token['attr'][$last]['value'] .= $char . $chars;
+
+ $state = 'attribute value (unquoted)';
+ }
+ break;
+
+ case 'after attribute value (quoted)':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the before attribute name state. */
+ $state = 'before attribute name';
+
+ } elseif ($char === '/') {
+ /* U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state. */
+ $state = 'self-closing start tag';
+
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif ($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-EOF-after-attribute-value'
+ ));
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Parse error. Reconsume the character in the before attribute
+ name state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-character-after-attribute-value'
+ ));
+ $this->stream->unget();
+ $state = 'before attribute name';
+ }
+ break;
+
+ case 'self-closing start tag':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Set the self-closing flag of the current tag token.
+ Emit the current tag token. Switch to the data state. */
+ // not sure if this is the name we want
+ $this->token['self-closing'] = true;
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif ($char === false) {
+ /* EOF
+ Parse error. Reconsume the EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-eof-after-self-closing'
+ ));
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Parse error. Reconsume the character in the before attribute name state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-character-after-self-closing'
+ ));
+ $this->stream->unget();
+ $state = 'before attribute name';
+ }
+ break;
+
+ case 'bogus comment':
+ /* (This can only happen if the content model flag is set to the PCDATA state.) */
+ /* Consume every character up to the first U+003E GREATER-THAN SIGN
+ character (>) or the end of the file (EOF), whichever comes first. Emit
+ a comment token whose data is the concatenation of all the characters
+ starting from and including the character that caused the state machine
+ to switch into the bogus comment state, up to and including the last
+ consumed character before the U+003E character, if any, or up to the
+ end of the file otherwise. (If the comment was started by the end of
+ the file (EOF), the token is empty.) */
+ $this->token['data'] .= (string) $this->stream->charsUntil('>');
+ $this->stream->char();
+
+ $this->emitToken($this->token);
+
+ /* Switch to the data state. */
+ $state = 'data';
+ break;
+
+ case 'markup declaration open':
+ // Consume for below
+ $hyphens = $this->stream->charsWhile('-', 2);
+ if ($hyphens === '-') {
+ $this->stream->unget();
+ }
+ if ($hyphens !== '--') {
+ $alpha = $this->stream->charsWhile(self::ALPHA, 7);
+ }
+
+ /* If the next two characters are both U+002D HYPHEN-MINUS (-)
+ characters, consume those two characters, create a comment token whose
+ data is the empty string, and switch to the comment state. */
+ if($hyphens === '--') {
+ $state = 'comment start';
+ $this->token = array(
+ 'data' => '',
+ 'type' => self::COMMENT
+ );
+
+ /* Otherwise if the next seven characters are a case-insensitive match
+ for the word "DOCTYPE", then consume those characters and switch to the
+ DOCTYPE state. */
+ } elseif(strtoupper($alpha) === 'DOCTYPE') {
+ $state = 'DOCTYPE';
+
+ // XXX not implemented
+ /* Otherwise, if the insertion mode is "in foreign content"
+ and the current node is not an element in the HTML namespace
+ and the next seven characters are an ASCII case-sensitive
+ match for the string "[CDATA[" (the five uppercase letters
+ "CDATA" with a U+005B LEFT SQUARE BRACKET character before
+ and after), then consume those characters and switch to the
+ CDATA section state (which is unrelated to the content model
+ flag's CDATA state). */
+
+ /* Otherwise, is is a parse error. Switch to the bogus comment state.
+ The next character that is consumed, if any, is the first character
+ that will be in the comment. */
+ } else {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-dashes-or-doctype'
+ ));
+ $this->token = array(
+ 'data' => (string) $alpha,
+ 'type' => self::COMMENT
+ );
+ $state = 'bogus comment';
+ }
+ break;
+
+ case 'comment start':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if ($char === '-') {
+ /* U+002D HYPHEN-MINUS (-)
+ Switch to the comment start dash state. */
+ $state = 'comment start dash';
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Emit the comment token. Switch to the
+ data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'incorrect-comment'
+ ));
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* EOF
+ Parse error. Emit the comment token. Reconsume the
+ EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-comment'
+ ));
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Anything else
+ Append the input character to the comment token's
+ data. Switch to the comment state. */
+ $this->token['data'] .= $char;
+ $state = 'comment';
+ }
+ break;
+
+ case 'comment start dash':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+ if ($char === '-') {
+ /* U+002D HYPHEN-MINUS (-)
+ Switch to the comment end state */
+ $state = 'comment end';
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Emit the comment token. Switch to the
+ data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'incorrect-comment'
+ ));
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* Parse error. Emit the comment token. Reconsume the
+ EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-comment'
+ ));
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ $this->token['data'] .= '-' . $char;
+ $state = 'comment';
+ }
+ break;
+
+ case 'comment':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === '-') {
+ /* U+002D HYPHEN-MINUS (-)
+ Switch to the comment end dash state */
+ $state = 'comment end dash';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Emit the comment token. Reconsume the EOF character
+ in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-comment'
+ ));
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Append the input character to the comment token's data. Stay in
+ the comment state. */
+ $chars = $this->stream->charsUntil('-');
+
+ $this->token['data'] .= $char . $chars;
+ }
+ break;
+
+ case 'comment end dash':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === '-') {
+ /* U+002D HYPHEN-MINUS (-)
+ Switch to the comment end state */
+ $state = 'comment end';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Emit the comment token. Reconsume the EOF character
+ in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-comment-end-dash'
+ ));
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Append a U+002D HYPHEN-MINUS (-) character and the input
+ character to the comment token's data. Switch to the comment state. */
+ $this->token['data'] .= '-'.$char;
+ $state = 'comment';
+ }
+ break;
+
+ case 'comment end':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the comment token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif($char === '-') {
+ /* U+002D HYPHEN-MINUS (-)
+ Parse error. Append a U+002D HYPHEN-MINUS (-) character
+ to the comment token's data. Stay in the comment end
+ state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-dash-after-double-dash-in-comment'
+ ));
+ $this->token['data'] .= '-';
+
+ } elseif($char === "\t" || $char === "\n" || $char === "\x0a" || $char === ' ') {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-space-after-double-dash-in-comment'
+ ));
+ $this->token['data'] .= '--' . $char;
+ $state = 'comment end space';
+
+ } elseif($char === '!') {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-bang-after-double-dash-in-comment'
+ ));
+ $state = 'comment end bang';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Emit the comment token. Reconsume the
+ EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-comment-double-dash'
+ ));
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Parse error. Append two U+002D HYPHEN-MINUS (-)
+ characters and the input character to the comment token's
+ data. Switch to the comment state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-char-in-comment'
+ ));
+ $this->token['data'] .= '--'.$char;
+ $state = 'comment';
+ }
+ break;
+
+ case 'comment end bang':
+ $char = $this->stream->char();
+ if ($char === '>') {
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === "-") {
+ $this->token['data'] .= '--!';
+ $state = 'comment end dash';
+ } elseif ($char === false) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-comment-end-bang'
+ ));
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ $this->token['data'] .= '--!' . $char;
+ $state = 'comment';
+ }
+ break;
+
+ case 'comment end space':
+ $char = $this->stream->char();
+ if ($char === '>') {
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === '-') {
+ $state = 'comment end dash';
+ } elseif ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ $this->token['data'] .= $char;
+ } elseif ($char === false) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-eof-in-comment-end-space',
+ ));
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ $this->token['data'] .= $char;
+ $state = 'comment';
+ }
+ break;
+
+ case 'DOCTYPE':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the before DOCTYPE name state. */
+ $state = 'before DOCTYPE name';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Create a new DOCTYPE token. Set its
+ force-quirks flag to on. Emit the token. Reconsume the
+ EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'need-space-after-doctype-but-got-eof'
+ ));
+ $this->emitToken(array(
+ 'name' => '',
+ 'type' => self::DOCTYPE,
+ 'force-quirks' => true,
+ 'error' => true
+ ));
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Parse error. Reconsume the current character in the
+ before DOCTYPE name state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'need-space-after-doctype'
+ ));
+ $this->stream->unget();
+ $state = 'before DOCTYPE name';
+ }
+ break;
+
+ case 'before DOCTYPE name':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before DOCTYPE name state. */
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Create a new DOCTYPE token. Set its
+ force-quirks flag to on. Emit the token. Switch to the
+ data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-doctype-name-but-got-right-bracket'
+ ));
+ $this->emitToken(array(
+ 'name' => '',
+ 'type' => self::DOCTYPE,
+ 'force-quirks' => true,
+ 'error' => true
+ ));
+
+ $state = 'data';
+
+ } elseif('A' <= $char && $char <= 'Z') {
+ /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Create a new DOCTYPE token. Set the token's name to the
+ lowercase version of the input character (add 0x0020 to
+ the character's code point). Switch to the DOCTYPE name
+ state. */
+ $this->token = array(
+ 'name' => strtolower($char),
+ 'type' => self::DOCTYPE,
+ 'error' => true
+ );
+
+ $state = 'DOCTYPE name';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Create a new DOCTYPE token. Set its
+ force-quirks flag to on. Emit the token. Reconsume the
+ EOF character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-doctype-name-but-got-eof'
+ ));
+ $this->emitToken(array(
+ 'name' => '',
+ 'type' => self::DOCTYPE,
+ 'force-quirks' => true,
+ 'error' => true
+ ));
+
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Create a new DOCTYPE token. Set the token's name to the
+ current input character. Switch to the DOCTYPE name state. */
+ $this->token = array(
+ 'name' => $char,
+ 'type' => self::DOCTYPE,
+ 'error' => true
+ );
+
+ $state = 'DOCTYPE name';
+ }
+ break;
+
+ case 'DOCTYPE name':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the after DOCTYPE name state. */
+ $state = 'after DOCTYPE name';
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current DOCTYPE token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif('A' <= $char && $char <= 'Z') {
+ /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Append the lowercase version of the input character
+ (add 0x0020 to the character's code point) to the current
+ DOCTYPE token's name. Stay in the DOCTYPE name state. */
+ $this->token['name'] .= strtolower($char);
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype-name'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Append the current input character to the current
+ DOCTYPE token's name. Stay in the DOCTYPE name state. */
+ $this->token['name'] .= $char;
+ }
+
+ // XXX this is probably some sort of quirks mode designation,
+ // check tree-builder to be sure. In general 'error' needs
+ // to be specc'ified, this probably means removing it at the end
+ $this->token['error'] = ($this->token['name'] === 'HTML')
+ ? false
+ : true;
+ break;
+
+ case 'after DOCTYPE name':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the after DOCTYPE name state. */
+
+ } elseif($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current DOCTYPE token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif($char === false) {
+ /* EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else */
+
+ $nextSix = strtoupper($char . $this->stream->charsWhile(self::ALPHA, 5));
+ if ($nextSix === 'PUBLIC') {
+ /* If the next six characters are an ASCII
+ case-insensitive match for the word "PUBLIC", then
+ consume those characters and switch to the before
+ DOCTYPE public identifier state. */
+ $state = 'before DOCTYPE public identifier';
+
+ } elseif ($nextSix === 'SYSTEM') {
+ /* Otherwise, if the next six characters are an ASCII
+ case-insensitive match for the word "SYSTEM", then
+ consume those characters and switch to the before
+ DOCTYPE system identifier state. */
+ $state = 'before DOCTYPE system identifier';
+
+ } else {
+ /* Otherwise, this is the parse error. Set the DOCTYPE
+ token's force-quirks flag to on. Switch to the bogus
+ DOCTYPE state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-space-or-right-bracket-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->token['error'] = true;
+ $state = 'bogus DOCTYPE';
+ }
+ }
+ break;
+
+ case 'before DOCTYPE public identifier':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before DOCTYPE public identifier state. */
+ } elseif ($char === '"') {
+ /* U+0022 QUOTATION MARK (")
+ Set the DOCTYPE token's public identifier to the empty
+ string (not missing), then switch to the DOCTYPE public
+ identifier (double-quoted) state. */
+ $this->token['public'] = '';
+ $state = 'DOCTYPE public identifier (double-quoted)';
+ } elseif ($char === "'") {
+ /* U+0027 APOSTROPHE (')
+ Set the DOCTYPE token's public identifier to the empty
+ string (not missing), then switch to the DOCTYPE public
+ identifier (single-quoted) state. */
+ $this->token['public'] = '';
+ $state = 'DOCTYPE public identifier (single-quoted)';
+ } elseif ($char === '>') {
+ /* Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-end-of-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* Parse error. Set the DOCTYPE token's force-quirks
+ flag to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Switch to the bogus DOCTYPE state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-char-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $state = 'bogus DOCTYPE';
+ }
+ break;
+
+ case 'DOCTYPE public identifier (double-quoted)':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if ($char === '"') {
+ /* U+0022 QUOTATION MARK (")
+ Switch to the after DOCTYPE public identifier state. */
+ $state = 'after DOCTYPE public identifier';
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-end-of-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Anything else
+ Append the current input character to the current
+ DOCTYPE token's public identifier. Stay in the DOCTYPE
+ public identifier (double-quoted) state. */
+ $this->token['public'] .= $char;
+ }
+ break;
+
+ case 'DOCTYPE public identifier (single-quoted)':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if ($char === "'") {
+ /* U+0027 APOSTROPHE (')
+ Switch to the after DOCTYPE public identifier state. */
+ $state = 'after DOCTYPE public identifier';
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-end-of-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Anything else
+ Append the current input character to the current
+ DOCTYPE token's public identifier. Stay in the DOCTYPE
+ public identifier (double-quoted) state. */
+ $this->token['public'] .= $char;
+ }
+ break;
+
+ case 'after DOCTYPE public identifier':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the after DOCTYPE public identifier state. */
+ } elseif ($char === '"') {
+ /* U+0022 QUOTATION MARK (")
+ Set the DOCTYPE token's system identifier to the
+ empty string (not missing), then switch to the DOCTYPE
+ system identifier (double-quoted) state. */
+ $this->token['system'] = '';
+ $state = 'DOCTYPE system identifier (double-quoted)';
+ } elseif ($char === "'") {
+ /* U+0027 APOSTROPHE (')
+ Set the DOCTYPE token's system identifier to the
+ empty string (not missing), then switch to the DOCTYPE
+ system identifier (single-quoted) state. */
+ $this->token['system'] = '';
+ $state = 'DOCTYPE system identifier (single-quoted)';
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current DOCTYPE token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* Parse error. Set the DOCTYPE token's force-quirks
+ flag to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Anything else
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Switch to the bogus DOCTYPE state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-char-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $state = 'bogus DOCTYPE';
+ }
+ break;
+
+ case 'before DOCTYPE system identifier':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before DOCTYPE system identifier state. */
+ } elseif ($char === '"') {
+ /* U+0022 QUOTATION MARK (")
+ Set the DOCTYPE token's system identifier to the empty
+ string (not missing), then switch to the DOCTYPE system
+ identifier (double-quoted) state. */
+ $this->token['system'] = '';
+ $state = 'DOCTYPE system identifier (double-quoted)';
+ } elseif ($char === "'") {
+ /* U+0027 APOSTROPHE (')
+ Set the DOCTYPE token's system identifier to the empty
+ string (not missing), then switch to the DOCTYPE system
+ identifier (single-quoted) state. */
+ $this->token['system'] = '';
+ $state = 'DOCTYPE system identifier (single-quoted)';
+ } elseif ($char === '>') {
+ /* Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-char-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* Parse error. Set the DOCTYPE token's force-quirks
+ flag to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Switch to the bogus DOCTYPE state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-char-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $state = 'bogus DOCTYPE';
+ }
+ break;
+
+ case 'DOCTYPE system identifier (double-quoted)':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if ($char === '"') {
+ /* U+0022 QUOTATION MARK (")
+ Switch to the after DOCTYPE system identifier state. */
+ $state = 'after DOCTYPE system identifier';
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-end-of-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Anything else
+ Append the current input character to the current
+ DOCTYPE token's system identifier. Stay in the DOCTYPE
+ system identifier (double-quoted) state. */
+ $this->token['system'] .= $char;
+ }
+ break;
+
+ case 'DOCTYPE system identifier (single-quoted)':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if ($char === "'") {
+ /* U+0027 APOSTROPHE (')
+ Switch to the after DOCTYPE system identifier state. */
+ $state = 'after DOCTYPE system identifier';
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Switch to the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-end-of-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag
+ to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Anything else
+ Append the current input character to the current
+ DOCTYPE token's system identifier. Stay in the DOCTYPE
+ system identifier (double-quoted) state. */
+ $this->token['system'] .= $char;
+ }
+ break;
+
+ case 'after DOCTYPE system identifier':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the after DOCTYPE system identifier state. */
+ } elseif ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the current DOCTYPE token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+ } elseif ($char === false) {
+ /* Parse error. Set the DOCTYPE token's force-quirks
+ flag to on. Emit that DOCTYPE token. Reconsume the EOF
+ character in the data state. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'eof-in-doctype'
+ ));
+ $this->token['force-quirks'] = true;
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+ } else {
+ /* Anything else
+ Parse error. Switch to the bogus DOCTYPE state.
+ (This does not set the DOCTYPE token's force-quirks
+ flag to on.) */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'unexpected-char-in-doctype'
+ ));
+ $state = 'bogus DOCTYPE';
+ }
+ break;
+
+ case 'bogus DOCTYPE':
+ /* Consume the next input character: */
+ $char = $this->stream->char();
+
+ if ($char === '>') {
+ /* U+003E GREATER-THAN SIGN (>)
+ Emit the DOCTYPE token. Switch to the data state. */
+ $this->emitToken($this->token);
+ $state = 'data';
+
+ } elseif($char === false) {
+ /* EOF
+ Emit the DOCTYPE token. Reconsume the EOF character in
+ the data state. */
+ $this->emitToken($this->token);
+ $this->stream->unget();
+ $state = 'data';
+
+ } else {
+ /* Anything else
+ Stay in the bogus DOCTYPE state. */
+ }
+ break;
+
+ // case 'cdataSection':
+
+ }
+ }
+ }
+
+ /**
+ * Returns a serialized representation of the tree.
+ */
+ public function save() {
+ return $this->tree->save();
+ }
+
+ /**
+ * Returns the input stream.
+ */
+ public function stream() {
+ return $this->stream;
+ }
+
+ private function consumeCharacterReference($allowed = false, $inattr = false) {
+ // This goes quite far against spec, and is far closer to the Python
+ // impl., mainly because we don't do the large unconsuming the spec
+ // requires.
+
+ // All consumed characters.
+ $chars = $this->stream->char();
+
+ /* This section defines how to consume a character
+ reference. This definition is used when parsing character
+ references in text and in attributes.
+
+ The behavior depends on the identity of the next character
+ (the one immediately after the U+0026 AMPERSAND character): */
+
+ if (
+ $chars[0] === "\x09" ||
+ $chars[0] === "\x0A" ||
+ $chars[0] === "\x0C" ||
+ $chars[0] === "\x20" ||
+ $chars[0] === '<' ||
+ $chars[0] === '&' ||
+ $chars === false ||
+ $chars[0] === $allowed
+ ) {
+ /* U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ U+003C LESS-THAN SIGN
+ U+0026 AMPERSAND
+ EOF
+ The additional allowed character, if there is one
+ Not a character reference. No characters are consumed,
+ and nothing is returned. (This is not an error, either.) */
+ // We already consumed, so unconsume.
+ $this->stream->unget();
+ return '&';
+ } elseif ($chars[0] === '#') {
+ /* Consume the U+0023 NUMBER SIGN. */
+ // Um, yeah, we already did that.
+ /* The behavior further depends on the character after
+ the U+0023 NUMBER SIGN: */
+ $chars .= $this->stream->char();
+ if (isset($chars[1]) && ($chars[1] === 'x' || $chars[1] === 'X')) {
+ /* U+0078 LATIN SMALL LETTER X
+ U+0058 LATIN CAPITAL LETTER X */
+ /* Consume the X. */
+ // Um, yeah, we already did that.
+ /* Follow the steps below, but using the range of
+ characters U+0030 DIGIT ZERO through to U+0039 DIGIT
+ NINE, U+0061 LATIN SMALL LETTER A through to U+0066
+ LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER
+ A, through to U+0046 LATIN CAPITAL LETTER F (in other
+ words, 0123456789, ABCDEF, abcdef). */
+ $char_class = self::HEX;
+ /* When it comes to interpreting the
+ number, interpret it as a hexadecimal number. */
+ $hex = true;
+ } else {
+ /* Anything else */
+ // Unconsume because we shouldn't have consumed this.
+ $chars = $chars[0];
+ $this->stream->unget();
+ /* Follow the steps below, but using the range of
+ characters U+0030 DIGIT ZERO through to U+0039 DIGIT
+ NINE (i.e. just 0123456789). */
+ $char_class = self::DIGIT;
+ /* When it comes to interpreting the number,
+ interpret it as a decimal number. */
+ $hex = false;
+ }
+
+ /* Consume as many characters as match the range of characters given above. */
+ $consumed = $this->stream->charsWhile($char_class);
+ if ($consumed === '' || $consumed === false) {
+ /* If no characters match the range, then don't consume
+ any characters (and unconsume the U+0023 NUMBER SIGN
+ character and, if appropriate, the X character). This
+ is a parse error; nothing is returned. */
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-numeric-entity'
+ ));
+ return '&' . $chars;
+ } else {
+ /* Otherwise, if the next character is a U+003B SEMICOLON,
+ consume that too. If it isn't, there is a parse error. */
+ if ($this->stream->char() !== ';') {
+ $this->stream->unget();
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'numeric-entity-without-semicolon'
+ ));
+ }
+
+ /* If one or more characters match the range, then take
+ them all and interpret the string of characters as a number
+ (either hexadecimal or decimal as appropriate). */
+ $codepoint = $hex ? hexdec($consumed) : (int) $consumed;
+
+ /* If that number is one of the numbers in the first column
+ of the following table, then this is a parse error. Find the
+ row with that number in the first column, and return a
+ character token for the Unicode character given in the
+ second column of that row. */
+ $new_codepoint = HTML5_Data::getRealCodepoint($codepoint);
+ if ($new_codepoint) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'illegal-windows-1252-entity'
+ ));
+ return HTML5_Data::utf8chr($new_codepoint);
+ } else {
+ /* Otherwise, if the number is greater than 0x10FFFF, then
+ * this is a parse error. Return a U+FFFD REPLACEMENT
+ * CHARACTER. */
+ if ($codepoint > 0x10FFFF) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'overlong-character-entity' // XXX probably not correct
+ ));
+ return "\xEF\xBF\xBD";
+ }
+ /* Otherwise, return a character token for the Unicode
+ * character whose code point is that number. If the
+ * number is in the range 0x0001 to 0x0008, 0x000E to
+ * 0x001F, 0x007F to 0x009F, 0xD800 to 0xDFFF, 0xFDD0 to
+ * 0xFDEF, or is one of 0x000B, 0xFFFE, 0xFFFF, 0x1FFFE,
+ * 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF, 0x4FFFE,
+ * 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE,
+ * 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE,
+ * 0xAFFFF, 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE,
+ * 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE,
+ * or 0x10FFFF, then this is a parse error. */
+ // && has higher precedence than ||
+ if (
+ $codepoint >= 0x0000 && $codepoint <= 0x0008 ||
+ $codepoint === 0x000B ||
+ $codepoint >= 0x000E && $codepoint <= 0x001F ||
+ $codepoint >= 0x007F && $codepoint <= 0x009F ||
+ $codepoint >= 0xD800 && $codepoint <= 0xDFFF ||
+ $codepoint >= 0xFDD0 && $codepoint <= 0xFDEF ||
+ ($codepoint & 0xFFFE) === 0xFFFE ||
+ $codepoint == 0x10FFFF || $codepoint == 0x10FFFE
+ ) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'illegal-codepoint-for-numeric-entity'
+ ));
+ }
+ return HTML5_Data::utf8chr($codepoint);
+ }
+ }
+
+ } else {
+ /* Anything else */
+
+ /* Consume the maximum number of characters possible,
+ with the consumed characters matching one of the
+ identifiers in the first column of the named character
+ references table (in a case-sensitive manner). */
+ // What we actually do here is consume as much as we can while it
+ // matches the start of one of the identifiers in the first column.
+
+ $refs = HTML5_Data::getNamedCharacterReferences();
+
+ // Get the longest string which is the start of an identifier
+ // ($chars) as well as the longest identifier which matches ($id)
+ // and its codepoint ($codepoint).
+ $codepoint = false;
+ $char = $chars;
+ while ($char !== false && isset($refs[$char])) {
+ $refs = $refs[$char];
+ if (isset($refs['codepoint'])) {
+ $id = $chars;
+ $codepoint = $refs['codepoint'];
+ }
+ $chars .= $char = $this->stream->char();
+ }
+
+ // Unconsume the one character we just took which caused the while
+ // statement to fail. This could be anything and could cause state
+ // changes (as if it matches the while loop it must be
+ // alphanumeric so we can just concat it to whatever we get later).
+ $this->stream->unget();
+ if ($char !== false) {
+ $chars = substr($chars, 0, -1);
+ }
+
+ /* If no match can be made, then this is a parse error.
+ No characters are consumed, and nothing is returned. */
+ if (!$codepoint) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'expected-named-entity'
+ ));
+ return '&' . $chars;
+ }
+
+ /* If the last character matched is not a U+003B SEMICOLON
+ (;), there is a parse error. */
+ $semicolon = true;
+ if (substr($id, -1) !== ';') {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'named-entity-without-semicolon'
+ ));
+ $semicolon = false;
+ }
+
+ /* If the character reference is being consumed as part of
+ an attribute, and the last character matched is not a
+ U+003B SEMICOLON (;), and the next character is in the
+ range U+0030 DIGIT ZERO to U+0039 DIGIT NINE, U+0041
+ LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z,
+ or U+0061 LATIN SMALL LETTER A to U+007A LATIN SMALL LETTER Z,
+ then, for historical reasons, all the characters that were
+ matched after the U+0026 AMPERSAND (&) must be unconsumed,
+ and nothing is returned. */
+ if ($inattr && !$semicolon) {
+ // The next character is either the next character in $chars or in the stream.
+ if (strlen($chars) > strlen($id)) {
+ $next = substr($chars, strlen($id), 1);
+ } else {
+ $next = $this->stream->char();
+ $this->stream->unget();
+ }
+ if (
+ '0' <= $next && $next <= '9' ||
+ 'A' <= $next && $next <= 'Z' ||
+ 'a' <= $next && $next <= 'z'
+ ) {
+ return '&' . $chars;
+ }
+ }
+
+ /* Otherwise, return a character token for the character
+ corresponding to the character reference name (as given
+ by the second column of the named character references table). */
+ return HTML5_Data::utf8chr($codepoint) . substr($chars, strlen($id));
+ }
+ }
+
+ private function characterReferenceInAttributeValue($allowed = false) {
+ /* Attempt to consume a character reference. */
+ $entity = $this->consumeCharacterReference($allowed, true);
+
+ /* If nothing is returned, append a U+0026 AMPERSAND
+ character to the current attribute's value.
+
+ Otherwise, append the returned character token to the
+ current attribute's value. */
+ $char = (!$entity)
+ ? '&'
+ : $entity;
+
+ $last = count($this->token['attr']) - 1;
+ $this->token['attr'][$last]['value'] .= $char;
+
+ /* Finally, switch back to the attribute value state that you
+ were in when were switched into this state. */
+ }
+
+ /**
+ * Emits a token, passing it on to the tree builder.
+ */
+ protected function emitToken($token, $checkStream = true, $dry = false) {
+ if ($checkStream) {
+ // Emit errors from input stream.
+ while ($this->stream->errors) {
+ $this->emitToken(array_shift($this->stream->errors), false);
+ }
+ }
+ if($token['type'] === self::ENDTAG && !empty($token['attr'])) {
+ for ($i = 0; $i < count($token['attr']); $i++) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'attributes-in-end-tag'
+ ));
+ }
+ }
+ if($token['type'] === self::ENDTAG && !empty($token['self-closing'])) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'self-closing-flag-on-end-tag',
+ ));
+ }
+ if($token['type'] === self::STARTTAG) {
+ // This could be changed to actually pass the tree-builder a hash
+ $hash = array();
+ foreach ($token['attr'] as $keypair) {
+ if (isset($hash[$keypair['name']])) {
+ $this->emitToken(array(
+ 'type' => self::PARSEERROR,
+ 'data' => 'duplicate-attribute',
+ ));
+ } else {
+ $hash[$keypair['name']] = $keypair['value'];
+ }
+ }
+ }
+
+ if(!$dry) {
+ // the current structure of attributes is not a terribly good one
+ $this->tree->emitToken($token);
+ }
+
+ if(!$dry && is_int($this->tree->content_model)) {
+ $this->content_model = $this->tree->content_model;
+ $this->tree->content_model = null;
+
+ } elseif($token['type'] === self::ENDTAG) {
+ $this->content_model = self::PCDATA;
+ }
+ }
+}
+
diff --git a/libraries/html5/TreeBuilder.php b/libraries/html5/TreeBuilder.php
new file mode 100644
index 0000000..2f5244f
--- /dev/null
+++ b/libraries/html5/TreeBuilder.php
@@ -0,0 +1,3840 @@
+
+Copyright 2009 Edward Z. Yang
+
+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.
+
+*/
+
+// Tags for FIX ME!!!: (in order of priority)
+// XXX - should be fixed NAO!
+// XERROR - with regards to parse errors
+// XSCRIPT - with regards to scripting mode
+// XENCODING - with regards to encoding (for reparsing tests)
+// XDOM - DOM specific code (tagName is explicitly not marked).
+// this is not (yet) in helper functions.
+
+class HTML5_TreeBuilder {
+ public $stack = array();
+ public $content_model;
+
+ private $mode;
+ private $original_mode;
+ private $secondary_mode;
+ private $dom;
+ // Whether or not normal insertion of nodes should actually foster
+ // parent (used in one case in spec)
+ private $foster_parent = false;
+ private $a_formatting = array();
+
+ private $head_pointer = null;
+ private $form_pointer = null;
+
+ private $flag_frameset_ok = true;
+ private $flag_force_quirks = false;
+ private $ignored = false;
+ private $quirks_mode = null;
+ // this gets to 2 when we want to ignore the next lf character, and
+ // is decrement at the beginning of each processed token (this way,
+ // code can check for (bool)$ignore_lf_token, but it phases out
+ // appropriately)
+ private $ignore_lf_token = 0;
+ private $fragment = false;
+ private $root;
+
+ private $scoping = array('applet','button','caption','html','marquee','object','table','td','th', 'svg:foreignObject');
+ private $formatting = array('a','b','big','code','em','font','i','nobr','s','small','strike','strong','tt','u');
+ // dl and ds are speculative
+ private $special = array('address','area','article','aside','base','basefont','bgsound',
+ 'blockquote','body','br','center','col','colgroup','command','dc','dd','details','dir','div','dl','ds',
+ 'dt','embed','fieldset','figure','footer','form','frame','frameset','h1','h2','h3','h4','h5',
+ 'h6','head','header','hgroup','hr','iframe','img','input','isindex','li','link',
+ 'listing','menu','meta','nav','noembed','noframes','noscript','ol',
+ 'p','param','plaintext','pre','script','select','spacer','style',
+ 'tbody','textarea','tfoot','thead','title','tr','ul','wbr');
+
+ private $pendingTableCharacters;
+ private $pendingTableCharactersDirty;
+
+ // Tree construction modes
+ const INITIAL = 0;
+ const BEFORE_HTML = 1;
+ const BEFORE_HEAD = 2;
+ const IN_HEAD = 3;
+ const IN_HEAD_NOSCRIPT = 4;
+ const AFTER_HEAD = 5;
+ const IN_BODY = 6;
+ const IN_CDATA_RCDATA = 7;
+ const IN_TABLE = 8;
+ const IN_TABLE_TEXT = 9;
+ const IN_CAPTION = 10;
+ const IN_COLUMN_GROUP = 11;
+ const IN_TABLE_BODY = 12;
+ const IN_ROW = 13;
+ const IN_CELL = 14;
+ const IN_SELECT = 15;
+ const IN_SELECT_IN_TABLE= 16;
+ const IN_FOREIGN_CONTENT= 17;
+ const AFTER_BODY = 18;
+ const IN_FRAMESET = 19;
+ const AFTER_FRAMESET = 20;
+ const AFTER_AFTER_BODY = 21;
+ const AFTER_AFTER_FRAMESET = 22;
+
+ /**
+ * Converts a magic number to a readable name. Use for debugging.
+ */
+ private function strConst($number) {
+ static $lookup;
+ if (!$lookup) {
+ $lookup = array();
+ $r = new ReflectionClass('HTML5_TreeBuilder');
+ $consts = $r->getConstants();
+ foreach ($consts as $const => $num) {
+ if (!is_int($num)) continue;
+ $lookup[$num] = $const;
+ }
+ }
+ return $lookup[$number];
+ }
+
+ // The different types of elements.
+ const SPECIAL = 100;
+ const SCOPING = 101;
+ const FORMATTING = 102;
+ const PHRASING = 103;
+
+ // Quirks modes in $quirks_mode
+ const NO_QUIRKS = 200;
+ const QUIRKS_MODE = 201;
+ const LIMITED_QUIRKS_MODE = 202;
+
+ // Marker to be placed in $a_formatting
+ const MARKER = 300;
+
+ // Namespaces for foreign content
+ const NS_HTML = null; // to prevent DOM from requiring NS on everything
+ const NS_MATHML = 'http://www.w3.org/1998/Math/MathML';
+ const NS_SVG = 'http://www.w3.org/2000/svg';
+ const NS_XLINK = 'http://www.w3.org/1999/xlink';
+ const NS_XML = 'http://www.w3.org/XML/1998/namespace';
+ const NS_XMLNS = 'http://www.w3.org/2000/xmlns/';
+
+ // Different types of scopes to test for elements
+ const SCOPE = 0;
+ const SCOPE_LISTITEM = 1;
+ const SCOPE_TABLE = 2;
+
+ public function __construct() {
+ $this->mode = self::INITIAL;
+ $this->dom = new DOMDocument;
+
+ $this->dom->encoding = 'UTF-8';
+ $this->dom->preserveWhiteSpace = true;
+ $this->dom->substituteEntities = true;
+ $this->dom->strictErrorChecking = false;
+ }
+
+ // Process tag tokens
+ public function emitToken($token, $mode = null) {
+ // XXX: ignore parse errors... why are we emitting them, again?
+ if ($token['type'] === HTML5_Tokenizer::PARSEERROR) return;
+ if ($mode === null) $mode = $this->mode;
+
+ /*
+ $backtrace = debug_backtrace();
+ if ($backtrace[1]['class'] !== 'HTML5_TreeBuilder') echo "--\n";
+ echo $this->strConst($mode);
+ if ($this->original_mode) echo " (originally ".$this->strConst($this->original_mode).")";
+ echo "\n ";
+ token_dump($token);
+ $this->printStack();
+ $this->printActiveFormattingElements();
+ if ($this->foster_parent) echo " -> this is a foster parent mode\n";
+ if ($this->flag_frameset_ok) echo " -> frameset ok\n";
+ */
+
+ if ($this->ignore_lf_token) $this->ignore_lf_token--;
+ $this->ignored = false;
+ // indenting is a little wonky, this can be changed later on
+ switch ($mode) {
+
+ case self::INITIAL:
+
+ /* A character token that is one of U+0009 CHARACTER TABULATION,
+ * U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE */
+ if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Ignore the token. */
+ $this->ignored = true;
+ } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ if (
+ $token['name'] !== 'html' || !empty($token['public']) ||
+ !empty($token['system']) || $token !== 'about:legacy-compat'
+ ) {
+ /* If the DOCTYPE token's name is not a case-sensitive match
+ * for the string "html", or if the token's public identifier
+ * is not missing, or if the token's system identifier is
+ * neither missing nor a case-sensitive match for the string
+ * "about:legacy-compat", then there is a parse error (this
+ * is the DOCTYPE parse error). */
+ // DOCTYPE parse error
+ }
+ /* Append a DocumentType node to the Document node, with the name
+ * attribute set to the name given in the DOCTYPE token, or the
+ * empty string if the name was missing; the publicId attribute
+ * set to the public identifier given in the DOCTYPE token, or
+ * the empty string if the public identifier was missing; the
+ * systemId attribute set to the system identifier given in the
+ * DOCTYPE token, or the empty string if the system identifier
+ * was missing; and the other attributes specific to
+ * DocumentType objects set to null and empty lists as
+ * appropriate. Associate the DocumentType node with the
+ * Document object so that it is returned as the value of the
+ * doctype attribute of the Document object. */
+ if (!isset($token['public'])) $token['public'] = null;
+ if (!isset($token['system'])) $token['system'] = null;
+ // XDOM
+ // Yes this is hacky. I'm kind of annoyed that I can't appendChild
+ // a doctype to DOMDocument. Maybe I haven't chanted the right
+ // syllables.
+ $impl = new DOMImplementation();
+ // This call can fail for particularly pathological cases (namely,
+ // the qualifiedName parameter ($token['name']) could be missing.
+ if ($token['name']) {
+ $doctype = $impl->createDocumentType($token['name'], $token['public'], $token['system']);
+ $this->dom->appendChild($doctype);
+ } else {
+ // It looks like libxml's not actually *able* to express this case.
+ // So... don't.
+ $this->dom->emptyDoctype = true;
+ }
+ $public = is_null($token['public']) ? false : strtolower($token['public']);
+ $system = is_null($token['system']) ? false : strtolower($token['system']);
+ $publicStartsWithForQuirks = array(
+ "+//silmaril//dtd html pro v0r11 19970101//",
+ "-//advasoft ltd//dtd html 3.0 aswedit + extensions//",
+ "-//as//dtd html 3.0 aswedit + extensions//",
+ "-//ietf//dtd html 2.0 level 1//",
+ "-//ietf//dtd html 2.0 level 2//",
+ "-//ietf//dtd html 2.0 strict level 1//",
+ "-//ietf//dtd html 2.0 strict level 2//",
+ "-//ietf//dtd html 2.0 strict//",
+ "-//ietf//dtd html 2.0//",
+ "-//ietf//dtd html 2.1e//",
+ "-//ietf//dtd html 3.0//",
+ "-//ietf//dtd html 3.2 final//",
+ "-//ietf//dtd html 3.2//",
+ "-//ietf//dtd html 3//",
+ "-//ietf//dtd html level 0//",
+ "-//ietf//dtd html level 1//",
+ "-//ietf//dtd html level 2//",
+ "-//ietf//dtd html level 3//",
+ "-//ietf//dtd html strict level 0//",
+ "-//ietf//dtd html strict level 1//",
+ "-//ietf//dtd html strict level 2//",
+ "-//ietf//dtd html strict level 3//",
+ "-//ietf//dtd html strict//",
+ "-//ietf//dtd html//",
+ "-//metrius//dtd metrius presentational//",
+ "-//microsoft//dtd internet explorer 2.0 html strict//",
+ "-//microsoft//dtd internet explorer 2.0 html//",
+ "-//microsoft//dtd internet explorer 2.0 tables//",
+ "-//microsoft//dtd internet explorer 3.0 html strict//",
+ "-//microsoft//dtd internet explorer 3.0 html//",
+ "-//microsoft//dtd internet explorer 3.0 tables//",
+ "-//netscape comm. corp.//dtd html//",
+ "-//netscape comm. corp.//dtd strict html//",
+ "-//o'reilly and associates//dtd html 2.0//",
+ "-//o'reilly and associates//dtd html extended 1.0//",
+ "-//o'reilly and associates//dtd html extended relaxed 1.0//",
+ "-//spyglass//dtd html 2.0 extended//",
+ "-//sq//dtd html 2.0 hotmetal + extensions//",
+ "-//sun microsystems corp.//dtd hotjava html//",
+ "-//sun microsystems corp.//dtd hotjava strict html//",
+ "-//w3c//dtd html 3 1995-03-24//",
+ "-//w3c//dtd html 3.2 draft//",
+ "-//w3c//dtd html 3.2 final//",
+ "-//w3c//dtd html 3.2//",
+ "-//w3c//dtd html 3.2s draft//",
+ "-//w3c//dtd html 4.0 frameset//",
+ "-//w3c//dtd html 4.0 transitional//",
+ "-//w3c//dtd html experimental 19960712//",
+ "-//w3c//dtd html experimental 970421//",
+ "-//w3c//dtd w3 html//",
+ "-//w3o//dtd w3 html 3.0//",
+ "-//webtechs//dtd mozilla html 2.0//",
+ "-//webtechs//dtd mozilla html//",
+ );
+ $publicSetToForQuirks = array(
+ "-//w3o//dtd w3 html strict 3.0//",
+ "-/w3c/dtd html 4.0 transitional/en",
+ "html",
+ );
+ $publicStartsWithAndSystemForQuirks = array(
+ "-//w3c//dtd html 4.01 frameset//",
+ "-//w3c//dtd html 4.01 transitional//",
+ );
+ $publicStartsWithForLimitedQuirks = array(
+ "-//w3c//dtd xhtml 1.0 frameset//",
+ "-//w3c//dtd xhtml 1.0 transitional//",
+ );
+ $publicStartsWithAndSystemForLimitedQuirks = array(
+ "-//w3c//dtd html 4.01 frameset//",
+ "-//w3c//dtd html 4.01 transitional//",
+ );
+ // first, do easy checks
+ if (
+ !empty($token['force-quirks']) ||
+ strtolower($token['name']) !== 'html'
+ ) {
+ $this->quirks_mode = self::QUIRKS_MODE;
+ } else {
+ do {
+ if ($system) {
+ foreach ($publicStartsWithAndSystemForQuirks as $x) {
+ if (strncmp($public, $x, strlen($x)) === 0) {
+ $this->quirks_mode = self::QUIRKS_MODE;
+ break;
+ }
+ }
+ if (!is_null($this->quirks_mode)) break;
+ foreach ($publicStartsWithAndSystemForLimitedQuirks as $x) {
+ if (strncmp($public, $x, strlen($x)) === 0) {
+ $this->quirks_mode = self::LIMITED_QUIRKS_MODE;
+ break;
+ }
+ }
+ if (!is_null($this->quirks_mode)) break;
+ }
+ foreach ($publicSetToForQuirks as $x) {
+ if ($public === $x) {
+ $this->quirks_mode = self::QUIRKS_MODE;
+ break;
+ }
+ }
+ if (!is_null($this->quirks_mode)) break;
+ foreach ($publicStartsWithForLimitedQuirks as $x) {
+ if (strncmp($public, $x, strlen($x)) === 0) {
+ $this->quirks_mode = self::LIMITED_QUIRKS_MODE;
+ }
+ }
+ if (!is_null($this->quirks_mode)) break;
+ if ($system === "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
+ $this->quirks_mode = self::QUIRKS_MODE;
+ break;
+ }
+ foreach ($publicStartsWithForQuirks as $x) {
+ if (strncmp($public, $x, strlen($x)) === 0) {
+ $this->quirks_mode = self::QUIRKS_MODE;
+ break;
+ }
+ }
+ if (is_null($this->quirks_mode)) {
+ $this->quirks_mode = self::NO_QUIRKS;
+ }
+ } while (false);
+ }
+ $this->mode = self::BEFORE_HTML;
+ } else {
+ // parse error
+ /* Switch the insertion mode to "before html", then reprocess the
+ * current token. */
+ $this->mode = self::BEFORE_HTML;
+ $this->quirks_mode = self::QUIRKS_MODE;
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::BEFORE_HTML:
+
+ /* A DOCTYPE token */
+ if($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // Parse error. Ignore the token.
+ $this->ignored = true;
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the Document object with the data
+ attribute set to the data given in the comment token. */
+ // XDOM
+ $comment = $this->dom->createComment($token['data']);
+ $this->dom->appendChild($comment);
+
+ /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+ or U+0020 SPACE */
+ } elseif($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Ignore the token. */
+ $this->ignored = true;
+
+ /* A start tag whose tag name is "html" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] == 'html') {
+ /* Create an element for the token in the HTML namespace. Append it
+ * to the Document object. Put this element in the stack of open
+ * elements. */
+ // XDOM
+ $html = $this->insertElement($token, false);
+ $this->dom->appendChild($html);
+ $this->stack[] = $html;
+
+ $this->mode = self::BEFORE_HEAD;
+
+ } else {
+ /* Create an html element. Append it to the Document object. Put
+ * this element in the stack of open elements. */
+ // XDOM
+ $html = $this->dom->createElementNS(self::NS_HTML, 'html');
+ $this->dom->appendChild($html);
+ $this->stack[] = $html;
+
+ /* Switch the insertion mode to "before head", then reprocess the
+ * current token. */
+ $this->mode = self::BEFORE_HEAD;
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::BEFORE_HEAD:
+
+ /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+ or U+0020 SPACE */
+ if($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Ignore the token. */
+ $this->ignored = true;
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the current node with the data attribute
+ set to the data given in the comment token. */
+ $this->insertComment($token['data']);
+
+ /* A DOCTYPE token */
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ /* Parse error. Ignore the token */
+ $this->ignored = true;
+ // parse error
+
+ /* A start tag token with the tag name "html" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
+ /* Process the token using the rules for the "in body"
+ * insertion mode. */
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* A start tag token with the tag name "head" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'head') {
+ /* Insert an HTML element for the token. */
+ $element = $this->insertElement($token);
+
+ /* Set the head element pointer to this new element node. */
+ $this->head_pointer = $element;
+
+ /* Change the insertion mode to "in head". */
+ $this->mode = self::IN_HEAD;
+
+ /* An end tag whose tag name is one of: "head", "body", "html", "br" */
+ } elseif(
+ $token['type'] === HTML5_Tokenizer::ENDTAG && (
+ $token['name'] === 'head' || $token['name'] === 'body' ||
+ $token['name'] === 'html' || $token['name'] === 'br'
+ )) {
+ /* Act as if a start tag token with the tag name "head" and no
+ * attributes had been seen, then reprocess the current token. */
+ $this->emitToken(array(
+ 'name' => 'head',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => array()
+ ));
+ $this->emitToken($token);
+
+ /* Any other end tag */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG) {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+
+ } else {
+ /* Act as if a start tag token with the tag name "head" and no
+ * attributes had been seen, then reprocess the current token.
+ * Note: This will result in an empty head element being
+ * generated, with the current token being reprocessed in the
+ * "after head" insertion mode. */
+ $this->emitToken(array(
+ 'name' => 'head',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => array()
+ ));
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::IN_HEAD:
+
+ /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+ or U+0020 SPACE. */
+ if($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Insert the character into the current node. */
+ $this->insertText($token['data']);
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the current node with the data attribute
+ set to the data given in the comment token. */
+ $this->insertComment($token['data']);
+
+ /* A DOCTYPE token */
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+ // parse error
+
+ /* A start tag whose tag name is "html" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'html') {
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* A start tag whose tag name is one of: "base", "command", "link" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ ($token['name'] === 'base' || $token['name'] === 'command' ||
+ $token['name'] === 'link')) {
+ /* Insert an HTML element for the token. Immediately pop the
+ * current node off the stack of open elements. */
+ $this->insertElement($token);
+ array_pop($this->stack);
+
+ // YYY: Acknowledge the token's self-closing flag, if it is set.
+
+ /* A start tag whose tag name is "meta" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'meta') {
+ /* Insert an HTML element for the token. Immediately pop the
+ * current node off the stack of open elements. */
+ $this->insertElement($token);
+ array_pop($this->stack);
+
+ // XERROR: Acknowledge the token's self-closing flag, if it is set.
+
+ // XENCODING: If the element has a charset attribute, and its value is a
+ // supported encoding, and the confidence is currently tentative,
+ // then change the encoding to the encoding given by the value of
+ // the charset attribute.
+ //
+ // Otherwise, if the element has a content attribute, and applying
+ // the algorithm for extracting an encoding from a Content-Type to
+ // its value returns a supported encoding encoding, and the
+ // confidence is currently tentative, then change the encoding to
+ // the encoding encoding.
+
+ /* A start tag with the tag name "title" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'title') {
+ $this->insertRCDATAElement($token);
+
+ /* A start tag whose tag name is "noscript", if the scripting flag is enabled, or
+ * A start tag whose tag name is one of: "noframes", "style" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ ($token['name'] === 'noscript' || $token['name'] === 'noframes' || $token['name'] === 'style')) {
+ // XSCRIPT: Scripting flag not respected
+ $this->insertCDATAElement($token);
+
+ // XSCRIPT: Scripting flag disable not implemented
+
+ /* A start tag with the tag name "script" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'script') {
+ /* 1. Create an element for the token in the HTML namespace. */
+ $node = $this->insertElement($token, false);
+
+ /* 2. Mark the element as being "parser-inserted" */
+ // Uhhh... XSCRIPT
+
+ /* 3. If the parser was originally created for the HTML
+ * fragment parsing algorithm, then mark the script element as
+ * "already executed". (fragment case) */
+ // ditto... XSCRIPT
+
+ /* 4. Append the new element to the current node and push it onto
+ * the stack of open elements. */
+ end($this->stack)->appendChild($node);
+ $this->stack[] = $node;
+ // I guess we could squash these together
+
+ /* 6. Let the original insertion mode be the current insertion mode. */
+ $this->original_mode = $this->mode;
+ /* 7. Switch the insertion mode to "in CDATA/RCDATA" */
+ $this->mode = self::IN_CDATA_RCDATA;
+ /* 5. Switch the tokeniser's content model flag to the CDATA state. */
+ $this->content_model = HTML5_Tokenizer::CDATA;
+
+ /* An end tag with the tag name "head" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'head') {
+ /* Pop the current node (which will be the head element) off the stack of open elements. */
+ array_pop($this->stack);
+
+ /* Change the insertion mode to "after head". */
+ $this->mode = self::AFTER_HEAD;
+
+ // Slight logic inversion here to minimize duplication
+ /* A start tag with the tag name "head". */
+ /* An end tag whose tag name is not one of: "body", "html", "br" */
+ } elseif(($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'head') ||
+ ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] !== 'html' &&
+ $token['name'] !== 'body' && $token['name'] !== 'br')) {
+ // Parse error. Ignore the token.
+ $this->ignored = true;
+
+ /* Anything else */
+ } else {
+ /* Act as if an end tag token with the tag name "head" had been
+ * seen, and reprocess the current token. */
+ $this->emitToken(array(
+ 'name' => 'head',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ /* Then, reprocess the current token. */
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::IN_HEAD_NOSCRIPT:
+ if ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // parse error
+ } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
+ $this->processWithRulesFor($token, self::IN_BODY);
+ } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'noscript') {
+ /* Pop the current node (which will be a noscript element) from the
+ * stack of open elements; the new current node will be a head
+ * element. */
+ array_pop($this->stack);
+ $this->mode = self::IN_HEAD;
+ } elseif (
+ ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) ||
+ ($token['type'] === HTML5_Tokenizer::COMMENT) ||
+ ($token['type'] === HTML5_Tokenizer::STARTTAG && (
+ $token['name'] === 'link' || $token['name'] === 'meta' ||
+ $token['name'] === 'noframes' || $token['name'] === 'style'))) {
+ $this->processWithRulesFor($token, self::IN_HEAD);
+ // inverted logic
+ } elseif (
+ ($token['type'] === HTML5_Tokenizer::STARTTAG && (
+ $token['name'] === 'head' || $token['name'] === 'noscript')) ||
+ ($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] !== 'br')) {
+ // parse error
+ } else {
+ // parse error
+ $this->emitToken(array(
+ 'type' => HTML5_Tokenizer::ENDTAG,
+ 'name' => 'noscript',
+ ));
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::AFTER_HEAD:
+ /* Handle the token as follows: */
+
+ /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+ or U+0020 SPACE */
+ if($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Append the character to the current node. */
+ $this->insertText($token['data']);
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the current node with the data attribute
+ set to the data given in the comment token. */
+ $this->insertComment($token['data']);
+
+ } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // parse error
+
+ } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* A start tag token with the tag name "body" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'body') {
+ $this->insertElement($token);
+
+ /* Set the frameset-ok flag to "not ok". */
+ $this->flag_frameset_ok = false;
+
+ /* Change the insertion mode to "in body". */
+ $this->mode = self::IN_BODY;
+
+ /* A start tag token with the tag name "frameset" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'frameset') {
+ /* Insert a frameset element for the token. */
+ $this->insertElement($token);
+
+ /* Change the insertion mode to "in frameset". */
+ $this->mode = self::IN_FRAMESET;
+
+ /* A start tag token whose tag name is one of: "base", "link", "meta",
+ "script", "style", "title" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
+ array('base', 'link', 'meta', 'noframes', 'script', 'style', 'title'))) {
+ // parse error
+ /* Push the node pointed to by the head element pointer onto the
+ * stack of open elements. */
+ $this->stack[] = $this->head_pointer;
+ $this->processWithRulesFor($token, self::IN_HEAD);
+ array_splice($this->stack, array_search($this->head_pointer, $this->stack, true), 1);
+
+ // inversion of specification
+ } elseif(
+ ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'head') ||
+ ($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] !== 'body' && $token['name'] !== 'html' &&
+ $token['name'] !== 'br')) {
+ // parse error
+
+ /* Anything else */
+ } else {
+ $this->emitToken(array(
+ 'name' => 'body',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => array()
+ ));
+ $this->flag_frameset_ok = true;
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::IN_BODY:
+ /* Handle the token as follows: */
+
+ switch($token['type']) {
+ /* A character token */
+ case HTML5_Tokenizer::CHARACTER:
+ case HTML5_Tokenizer::SPACECHARACTER:
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ /* Append the token's character to the current node. */
+ $this->insertText($token['data']);
+
+ /* If the token is not one of U+0009 CHARACTER TABULATION,
+ * U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020
+ * SPACE, then set the frameset-ok flag to "not ok". */
+ // i.e., if any of the characters is not whitespace
+ if (strlen($token['data']) !== strspn($token['data'], HTML5_Tokenizer::WHITESPACE)) {
+ $this->flag_frameset_ok = false;
+ }
+ break;
+
+ /* A comment token */
+ case HTML5_Tokenizer::COMMENT:
+ /* Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token. */
+ $this->insertComment($token['data']);
+ break;
+
+ case HTML5_Tokenizer::DOCTYPE:
+ // parse error
+ break;
+
+ case HTML5_Tokenizer::EOF:
+ // parse error
+ break;
+
+ case HTML5_Tokenizer::STARTTAG:
+ switch($token['name']) {
+ case 'html':
+ // parse error
+ /* For each attribute on the token, check to see if the
+ * attribute is already present on the top element of the
+ * stack of open elements. If it is not, add the attribute
+ * and its corresponding value to that element. */
+ foreach($token['attr'] as $attr) {
+ if(!$this->stack[0]->hasAttribute($attr['name'])) {
+ $this->stack[0]->setAttribute($attr['name'], $attr['value']);
+ }
+ }
+ break;
+
+ case 'base': case 'command': case 'link': case 'meta': case 'noframes':
+ case 'script': case 'style': case 'title':
+ /* Process the token as if the insertion mode had been "in
+ head". */
+ $this->processWithRulesFor($token, self::IN_HEAD);
+ break;
+
+ /* A start tag token with the tag name "body" */
+ case 'body':
+ /* Parse error. If the second element on the stack of open
+ elements is not a body element, or, if the stack of open
+ elements has only one node on it, then ignore the token.
+ (fragment case) */
+ if(count($this->stack) === 1 || $this->stack[1]->tagName !== 'body') {
+ $this->ignored = true;
+ // Ignore
+
+ /* Otherwise, for each attribute on the token, check to see
+ if the attribute is already present on the body element (the
+ second element) on the stack of open elements. If it is not,
+ add the attribute and its corresponding value to that
+ element. */
+ } else {
+ foreach($token['attr'] as $attr) {
+ if(!$this->stack[1]->hasAttribute($attr['name'])) {
+ $this->stack[1]->setAttribute($attr['name'], $attr['value']);
+ }
+ }
+ }
+ break;
+
+ case 'frameset':
+ // parse error
+ /* If the second element on the stack of open elements is
+ * not a body element, or, if the stack of open elements
+ * has only one node on it, then ignore the token.
+ * (fragment case) */
+ if(count($this->stack) === 1 || $this->stack[1]->tagName !== 'body') {
+ $this->ignored = true;
+ // Ignore
+ } elseif (!$this->flag_frameset_ok) {
+ $this->ignored = true;
+ // Ignore
+ } else {
+ /* 1. Remove the second element on the stack of open
+ * elements from its parent node, if it has one. */
+ if($this->stack[1]->parentNode) {
+ $this->stack[1]->parentNode->removeChild($this->stack[1]);
+ }
+
+ /* 2. Pop all the nodes from the bottom of the stack of
+ * open elements, from the current node up to the root
+ * html element. */
+ array_splice($this->stack, 1);
+
+ $this->insertElement($token);
+ $this->mode = self::IN_FRAMESET;
+ }
+ break;
+
+ // in spec, there is a diversion here
+
+ case 'address': case 'article': case 'aside': case 'blockquote':
+ case 'center': case 'datagrid': case 'details': case 'dir':
+ case 'div': case 'dl': case 'fieldset': case 'figure': case 'footer':
+ case 'header': case 'hgroup': case 'menu': case 'nav':
+ case 'ol': case 'p': case 'section': case 'ul':
+ /* If the stack of open elements has a p element in scope,
+ then act as if an end tag with the tag name p had been
+ seen. */
+ if($this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+ break;
+
+ /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4",
+ "h5", "h6" */
+ case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
+ /* If the stack of open elements has a p element in scope,
+ then act as if an end tag with the tag name p had been seen. */
+ if($this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* If the current node is an element whose tag name is one
+ * of "h1", "h2", "h3", "h4", "h5", or "h6", then this is a
+ * parse error; pop the current node off the stack of open
+ * elements. */
+ $peek = array_pop($this->stack);
+ if (in_array($peek->tagName, array("h1", "h2", "h3", "h4", "h5", "h6"))) {
+ // parse error
+ } else {
+ $this->stack[] = $peek;
+ }
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+ break;
+
+ case 'pre': case 'listing':
+ /* If the stack of open elements has a p element in scope,
+ then act as if an end tag with the tag name p had been seen. */
+ if($this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+ $this->insertElement($token);
+ /* If the next token is a U+000A LINE FEED (LF) character
+ * token, then ignore that token and move on to the next
+ * one. (Newlines at the start of pre blocks are ignored as
+ * an authoring convenience.) */
+ $this->ignore_lf_token = 2;
+ $this->flag_frameset_ok = false;
+ break;
+
+ /* A start tag whose tag name is "form" */
+ case 'form':
+ /* If the form element pointer is not null, ignore the
+ token with a parse error. */
+ if($this->form_pointer !== null) {
+ $this->ignored = true;
+ // Ignore.
+
+ /* Otherwise: */
+ } else {
+ /* If the stack of open elements has a p element in
+ scope, then act as if an end tag with the tag name p
+ had been seen. */
+ if($this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Insert an HTML element for the token, and set the
+ form element pointer to point to the element created. */
+ $element = $this->insertElement($token);
+ $this->form_pointer = $element;
+ }
+ break;
+
+ // condensed specification
+ case 'li': case 'dc': case 'dd': case 'ds': case 'dt':
+ /* 1. Set the frameset-ok flag to "not ok". */
+ $this->flag_frameset_ok = false;
+
+ $stack_length = count($this->stack) - 1;
+ for($n = $stack_length; 0 <= $n; $n--) {
+ /* 2. Initialise node to be the current node (the
+ bottommost node of the stack). */
+ $stop = false;
+ $node = $this->stack[$n];
+ $cat = $this->getElementCategory($node);
+
+ // for case 'li':
+ /* 3. If node is an li element, then act as if an end
+ * tag with the tag name "li" had been seen, then jump
+ * to the last step. */
+ // for case 'dc': case 'dd': case 'ds': case 'dt':
+ /* If node is a dc, dd, ds or dt element, then act as if an end
+ * tag with the same tag name as node had been seen, then
+ * jump to the last step. */
+ if(($token['name'] === 'li' && $node->tagName === 'li') ||
+ ($token['name'] !== 'li' && ($node->tagName == 'dc' || $node->tagName === 'dd' || $node->tagName == 'ds' || $node->tagName === 'dt'))) { // limited conditional
+ $this->emitToken(array(
+ 'type' => HTML5_Tokenizer::ENDTAG,
+ 'name' => $node->tagName,
+ ));
+ break;
+ }
+
+ /* 4. If node is not in the formatting category, and is
+ not in the phrasing category, and is not an address,
+ div or p element, then stop this algorithm. */
+ if($cat !== self::FORMATTING && $cat !== self::PHRASING &&
+ $node->tagName !== 'address' && $node->tagName !== 'div' &&
+ $node->tagName !== 'p') {
+ break;
+ }
+
+ /* 5. Otherwise, set node to the previous entry in the
+ * stack of open elements and return to step 2. */
+ }
+
+ /* 6. This is the last step. */
+
+ /* If the stack of open elements has a p element in scope,
+ then act as if an end tag with the tag name p had been
+ seen. */
+ if($this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Finally, insert an HTML element with the same tag
+ name as the token's. */
+ $this->insertElement($token);
+ break;
+
+ /* A start tag token whose tag name is "plaintext" */
+ case 'plaintext':
+ /* If the stack of open elements has a p element in scope,
+ then act as if an end tag with the tag name p had been
+ seen. */
+ if($this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ $this->content_model = HTML5_Tokenizer::PLAINTEXT;
+ break;
+
+ // more diversions
+
+ /* A start tag whose tag name is "a" */
+ case 'a':
+ /* If the list of active formatting elements contains
+ an element whose tag name is "a" between the end of the
+ list and the last marker on the list (or the start of
+ the list if there is no marker on the list), then this
+ is a parse error; act as if an end tag with the tag name
+ "a" had been seen, then remove that element from the list
+ of active formatting elements and the stack of open
+ elements if the end tag didn't already remove it (it
+ might not have if the element is not in table scope). */
+ $leng = count($this->a_formatting);
+
+ for($n = $leng - 1; $n >= 0; $n--) {
+ if($this->a_formatting[$n] === self::MARKER) {
+ break;
+
+ } elseif($this->a_formatting[$n]->tagName === 'a') {
+ $a = $this->a_formatting[$n];
+ $this->emitToken(array(
+ 'name' => 'a',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ if (in_array($a, $this->a_formatting)) {
+ $a_i = array_search($a, $this->a_formatting, true);
+ if($a_i !== false) array_splice($this->a_formatting, $a_i, 1);
+ }
+ if (in_array($a, $this->stack)) {
+ $a_i = array_search($a, $this->stack, true);
+ if ($a_i !== false) array_splice($this->stack, $a_i, 1);
+ }
+ break;
+ }
+ }
+
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ /* Insert an HTML element for the token. */
+ $el = $this->insertElement($token);
+
+ /* Add that element to the list of active formatting
+ elements. */
+ $this->a_formatting[] = $el;
+ break;
+
+ case 'b': case 'big': case 'code': case 'em': case 'font': case 'i':
+ case 's': case 'small': case 'strike':
+ case 'strong': case 'tt': case 'u':
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ /* Insert an HTML element for the token. */
+ $el = $this->insertElement($token);
+
+ /* Add that element to the list of active formatting
+ elements. */
+ $this->a_formatting[] = $el;
+ break;
+
+ case 'nobr':
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ /* If the stack of open elements has a nobr element in
+ * scope, then this is a parse error; act as if an end tag
+ * with the tag name "nobr" had been seen, then once again
+ * reconstruct the active formatting elements, if any. */
+ if ($this->elementInScope('nobr')) {
+ $this->emitToken(array(
+ 'name' => 'nobr',
+ 'type' => HTML5_Tokenizer::ENDTAG,
+ ));
+ $this->reconstructActiveFormattingElements();
+ }
+
+ /* Insert an HTML element for the token. */
+ $el = $this->insertElement($token);
+
+ /* Add that element to the list of active formatting
+ elements. */
+ $this->a_formatting[] = $el;
+ break;
+
+ // another diversion
+
+ /* A start tag token whose tag name is "button" */
+ case 'button':
+ /* If the stack of open elements has a button element in scope,
+ then this is a parse error; act as if an end tag with the tag
+ name "button" had been seen, then reprocess the token. (We don't
+ do that. Unnecessary.) (I hope you're right! -- ezyang) */
+ if($this->elementInScope('button')) {
+ $this->emitToken(array(
+ 'name' => 'button',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ /* Insert a marker at the end of the list of active
+ formatting elements. */
+ $this->a_formatting[] = self::MARKER;
+
+ $this->flag_frameset_ok = false;
+ break;
+
+ case 'applet': case 'marquee': case 'object':
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ /* Insert a marker at the end of the list of active
+ formatting elements. */
+ $this->a_formatting[] = self::MARKER;
+
+ $this->flag_frameset_ok = false;
+ break;
+
+ // spec diversion
+
+ /* A start tag whose tag name is "table" */
+ case 'table':
+ /* If the Document is not set to quirks mode, and the
+ * stack of open elements has a p element in scope, then
+ * act as if an end tag with the tag name "p" had been
+ * seen. */
+ if($this->quirks_mode !== self::QUIRKS_MODE &&
+ $this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ $this->flag_frameset_ok = false;
+
+ /* Change the insertion mode to "in table". */
+ $this->mode = self::IN_TABLE;
+ break;
+
+ /* A start tag whose tag name is one of: "area", "basefont",
+ "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */
+ case 'area': case 'basefont': case 'bgsound': case 'br':
+ case 'embed': case 'img': case 'input': case 'keygen': case 'spacer':
+ case 'wbr':
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ /* Immediately pop the current node off the stack of open elements. */
+ array_pop($this->stack);
+
+ // YYY: Acknowledge the token's self-closing flag, if it is set.
+
+ $this->flag_frameset_ok = false;
+ break;
+
+ case 'param': case 'source':
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ /* Immediately pop the current node off the stack of open elements. */
+ array_pop($this->stack);
+
+ // YYY: Acknowledge the token's self-closing flag, if it is set.
+ break;
+
+ /* A start tag whose tag name is "hr" */
+ case 'hr':
+ /* If the stack of open elements has a p element in scope,
+ then act as if an end tag with the tag name p had been seen. */
+ if($this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ /* Immediately pop the current node off the stack of open elements. */
+ array_pop($this->stack);
+
+ // YYY: Acknowledge the token's self-closing flag, if it is set.
+
+ $this->flag_frameset_ok = false;
+ break;
+
+ /* A start tag whose tag name is "image" */
+ case 'image':
+ /* Parse error. Change the token's tag name to "img" and
+ reprocess it. (Don't ask.) */
+ $token['name'] = 'img';
+ $this->emitToken($token);
+ break;
+
+ /* A start tag whose tag name is "isindex" */
+ case 'isindex':
+ /* Parse error. */
+
+ /* If the form element pointer is not null,
+ then ignore the token. */
+ if($this->form_pointer === null) {
+ /* Act as if a start tag token with the tag name "form" had
+ been seen. */
+ /* If the token has an attribute called "action", set
+ * the action attribute on the resulting form
+ * element to the value of the "action" attribute of
+ * the token. */
+ $attr = array();
+ $action = $this->getAttr($token, 'action');
+ if ($action !== false) {
+ $attr[] = array('name' => 'action', 'value' => $action);
+ }
+ $this->emitToken(array(
+ 'name' => 'form',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => $attr
+ ));
+
+ /* Act as if a start tag token with the tag name "hr" had
+ been seen. */
+ $this->emitToken(array(
+ 'name' => 'hr',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => array()
+ ));
+
+ /* Act as if a start tag token with the tag name "label"
+ had been seen. */
+ $this->emitToken(array(
+ 'name' => 'label',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => array()
+ ));
+
+ /* Act as if a stream of character tokens had been seen. */
+ $prompt = $this->getAttr($token, 'prompt');
+ if ($prompt === false) {
+ $prompt = 'This is a searchable index. '.
+ 'Insert your search keywords here: ';
+ }
+ $this->emitToken(array(
+ 'data' => $prompt,
+ 'type' => HTML5_Tokenizer::CHARACTER,
+ ));
+
+ /* Act as if a start tag token with the tag name "input"
+ had been seen, with all the attributes from the "isindex"
+ token, except with the "name" attribute set to the value
+ "isindex" (ignoring any explicit "name" attribute). */
+ $attr = array();
+ foreach ($token['attr'] as $keypair) {
+ if ($keypair['name'] === 'name' || $keypair['name'] === 'action' ||
+ $keypair['name'] === 'prompt') continue;
+ $attr[] = $keypair;
+ }
+ $attr[] = array('name' => 'name', 'value' => 'isindex');
+
+ $this->emitToken(array(
+ 'name' => 'input',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => $attr
+ ));
+
+ /* Act as if an end tag token with the tag name "label"
+ had been seen. */
+ $this->emitToken(array(
+ 'name' => 'label',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ /* Act as if a start tag token with the tag name "hr" had
+ been seen. */
+ $this->emitToken(array(
+ 'name' => 'hr',
+ 'type' => HTML5_Tokenizer::STARTTAG
+ ));
+
+ /* Act as if an end tag token with the tag name "form" had
+ been seen. */
+ $this->emitToken(array(
+ 'name' => 'form',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ } else {
+ $this->ignored = true;
+ }
+ break;
+
+ /* A start tag whose tag name is "textarea" */
+ case 'textarea':
+ $this->insertElement($token);
+
+ /* If the next token is a U+000A LINE FEED (LF)
+ * character token, then ignore that token and move on to
+ * the next one. (Newlines at the start of textarea
+ * elements are ignored as an authoring convenience.)
+ * need flag, see also
*/
+ $this->ignore_lf_token = 2;
+
+ $this->original_mode = $this->mode;
+ $this->flag_frameset_ok = false;
+ $this->mode = self::IN_CDATA_RCDATA;
+
+ /* Switch the tokeniser's content model flag to the
+ RCDATA state. */
+ $this->content_model = HTML5_Tokenizer::RCDATA;
+ break;
+
+ /* A start tag token whose tag name is "xmp" */
+ case 'xmp':
+ /* If the stack of open elements has a p element in
+ scope, then act as if an end tag with the tag name
+ "p" has been seen. */
+ if ($this->elementInScope('p')) {
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ $this->flag_frameset_ok = false;
+
+ $this->insertCDATAElement($token);
+ break;
+
+ case 'iframe':
+ $this->flag_frameset_ok = false;
+ $this->insertCDATAElement($token);
+ break;
+
+ case 'noembed': case 'noscript':
+ // XSCRIPT: should check scripting flag
+ $this->insertCDATAElement($token);
+ break;
+
+ /* A start tag whose tag name is "select" */
+ case 'select':
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ $this->flag_frameset_ok = false;
+
+ /* If the insertion mode is one of in table", "in caption",
+ * "in column group", "in table body", "in row", or "in
+ * cell", then switch the insertion mode to "in select in
+ * table". Otherwise, switch the insertion mode to "in
+ * select". */
+ if (
+ $this->mode === self::IN_TABLE || $this->mode === self::IN_CAPTION ||
+ $this->mode === self::IN_COLUMN_GROUP || $this->mode ==+self::IN_TABLE_BODY ||
+ $this->mode === self::IN_ROW || $this->mode === self::IN_CELL
+ ) {
+ $this->mode = self::IN_SELECT_IN_TABLE;
+ } else {
+ $this->mode = self::IN_SELECT;
+ }
+ break;
+
+ case 'option': case 'optgroup':
+ if ($this->elementInScope('option')) {
+ $this->emitToken(array(
+ 'name' => 'option',
+ 'type' => HTML5_Tokenizer::ENDTAG,
+ ));
+ }
+ $this->reconstructActiveFormattingElements();
+ $this->insertElement($token);
+ break;
+
+ case 'rp': case 'rt':
+ /* If the stack of open elements has a ruby element in scope, then generate
+ * implied end tags. If the current node is not then a ruby element, this is
+ * a parse error; pop all the nodes from the current node up to the node
+ * immediately before the bottommost ruby element on the stack of open elements.
+ */
+ if ($this->elementInScope('ruby')) {
+ $this->generateImpliedEndTags();
+ }
+ $peek = false;
+ do {
+ if ($peek) {
+ // parse error
+ }
+ $peek = array_pop($this->stack);
+ } while ($peek->tagName !== 'ruby');
+ $this->stack[] = $peek; // we popped one too many
+ $this->insertElement($token);
+ break;
+
+ // spec diversion
+
+ case 'math':
+ $this->reconstructActiveFormattingElements();
+ $token = $this->adjustMathMLAttributes($token);
+ $token = $this->adjustForeignAttributes($token);
+ $this->insertForeignElement($token, self::NS_MATHML);
+ if (isset($token['self-closing'])) {
+ // XERROR: acknowledge the token's self-closing flag
+ array_pop($this->stack);
+ }
+ if ($this->mode !== self::IN_FOREIGN_CONTENT) {
+ $this->secondary_mode = $this->mode;
+ $this->mode = self::IN_FOREIGN_CONTENT;
+ }
+ break;
+
+ case 'svg':
+ $this->reconstructActiveFormattingElements();
+ $token = $this->adjustSVGAttributes($token);
+ $token = $this->adjustForeignAttributes($token);
+ $this->insertForeignElement($token, self::NS_SVG);
+ if (isset($token['self-closing'])) {
+ // XERROR: acknowledge the token's self-closing flag
+ array_pop($this->stack);
+ }
+ if ($this->mode !== self::IN_FOREIGN_CONTENT) {
+ $this->secondary_mode = $this->mode;
+ $this->mode = self::IN_FOREIGN_CONTENT;
+ }
+ break;
+
+ case 'caption': case 'col': case 'colgroup': case 'frame': case 'head':
+ case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead': case 'tr':
+ // parse error
+ break;
+
+ /* A start tag token not covered by the previous entries */
+ default:
+ /* Reconstruct the active formatting elements, if any. */
+ $this->reconstructActiveFormattingElements();
+
+ $this->insertElement($token);
+ /* This element will be a phrasing element. */
+ break;
+ }
+ break;
+
+ case HTML5_Tokenizer::ENDTAG:
+ switch($token['name']) {
+ /* An end tag with the tag name "body" */
+ case 'body':
+ /* If the stack of open elements does not have a body
+ * element in scope, this is a parse error; ignore the
+ * token. */
+ if(!$this->elementInScope('body')) {
+ $this->ignored = true;
+
+ /* Otherwise, if there is a node in the stack of open
+ * elements that is not either a dc element, a dd element,
+ * a ds element, a dt element, an li element, an optgroup
+ * element, an option element, a p element, an rp element,
+ * an rt element, a tbody element, a td element, a tfoot
+ * element, a th element, a thead element, a tr element,
+ * the body element, or the html element, then this is a
+ * parse error.
+ */
+ } else {
+ // XERROR: implement this check for parse error
+ }
+
+ /* Change the insertion mode to "after body". */
+ $this->mode = self::AFTER_BODY;
+ break;
+
+ /* An end tag with the tag name "html" */
+ case 'html':
+ /* Act as if an end tag with tag name "body" had been seen,
+ then, if that token wasn't ignored, reprocess the current
+ token. */
+ $this->emitToken(array(
+ 'name' => 'body',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ if (!$this->ignored) $this->emitToken($token);
+ break;
+
+ case 'address': case 'article': case 'aside': case 'blockquote':
+ case 'center': case 'datagrid': case 'details': case 'dir':
+ case 'div': case 'dl': case 'fieldset': case 'footer':
+ case 'header': case 'hgroup': case 'listing': case 'menu':
+ case 'nav': case 'ol': case 'pre': case 'section': case 'ul':
+ /* If the stack of open elements has an element in scope
+ with the same tag name as that of the token, then generate
+ implied end tags. */
+ if($this->elementInScope($token['name'])) {
+ $this->generateImpliedEndTags();
+
+ /* Now, if the current node is not an element with
+ the same tag name as that of the token, then this
+ is a parse error. */
+ // XERROR: implement parse error logic
+
+ /* If the stack of open elements has an element in
+ scope with the same tag name as that of the token,
+ then pop elements from this stack until an element
+ with that tag name has been popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== $token['name']);
+ } else {
+ // parse error
+ }
+ break;
+
+ /* An end tag whose tag name is "form" */
+ case 'form':
+ /* Let node be the element that the form element pointer is set to. */
+ $node = $this->form_pointer;
+ /* Set the form element pointer to null. */
+ $this->form_pointer = null;
+ /* If node is null or the stack of open elements does not
+ * have node in scope, then this is a parse error; ignore the token. */
+ if ($node === null || !in_array($node, $this->stack)) {
+ // parse error
+ $this->ignored = true;
+ } else {
+ /* 1. Generate implied end tags. */
+ $this->generateImpliedEndTags();
+ /* 2. If the current node is not node, then this is a parse error. */
+ if (end($this->stack) !== $node) {
+ // parse error
+ }
+ /* 3. Remove node from the stack of open elements. */
+ array_splice($this->stack, array_search($node, $this->stack, true), 1);
+ }
+
+ break;
+
+ /* An end tag whose tag name is "p" */
+ case 'p':
+ /* If the stack of open elements has a p element in scope,
+ then generate implied end tags, except for p elements. */
+ if($this->elementInScope('p')) {
+ /* Generate implied end tags, except for elements with
+ * the same tag name as the token. */
+ $this->generateImpliedEndTags(array('p'));
+
+ /* If the current node is not a p element, then this is
+ a parse error. */
+ // XERROR: implement
+
+ /* Pop elements from the stack of open elements until
+ * an element with the same tag name as the token has
+ * been popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== 'p');
+
+ } else {
+ // parse error
+ $this->emitToken(array(
+ 'name' => 'p',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ ));
+ $this->emitToken($token);
+ }
+ break;
+
+ /* An end tag whose tag name is "li" */
+ case 'li':
+ /* If the stack of open elements does not have an element
+ * in list item scope with the same tag name as that of the
+ * token, then this is a parse error; ignore the token. */
+ if ($this->elementInScope($token['name'], self::SCOPE_LISTITEM)) {
+ /* Generate implied end tags, except for elements with the
+ * same tag name as the token. */
+ $this->generateImpliedEndTags(array($token['name']));
+ /* If the current node is not an element with the same tag
+ * name as that of the token, then this is a parse error. */
+ // XERROR: parse error
+ /* Pop elements from the stack of open elements until an
+ * element with the same tag name as the token has been
+ * popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== $token['name']);
+ } else {
+ // XERROR: parse error
+ }
+ break;
+
+ /* An end tag whose tag name is "dc", "dd", "ds", "dt" */
+ case 'dc': case 'dd': case 'ds': case 'dt':
+ if($this->elementInScope($token['name'])) {
+ $this->generateImpliedEndTags(array($token['name']));
+
+ /* If the current node is not an element with the same
+ tag name as the token, then this is a parse error. */
+ // XERROR: implement parse error
+
+ /* Pop elements from the stack of open elements until
+ * an element with the same tag name as the token has
+ * been popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== $token['name']);
+
+ } else {
+ // XERROR: parse error
+ }
+ break;
+
+ /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4",
+ "h5", "h6" */
+ case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
+ $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6');
+
+ /* If the stack of open elements has in scope an element whose
+ tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
+ generate implied end tags. */
+ if($this->elementInScope($elements)) {
+ $this->generateImpliedEndTags();
+
+ /* Now, if the current node is not an element with the same
+ tag name as that of the token, then this is a parse error. */
+ // XERROR: implement parse error
+
+ /* If the stack of open elements has in scope an element
+ whose tag name is one of "h1", "h2", "h3", "h4", "h5", or
+ "h6", then pop elements from the stack until an element
+ with one of those tag names has been popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while (!in_array($node->tagName, $elements));
+ } else {
+ // parse error
+ }
+ break;
+
+ /* An end tag whose tag name is one of: "a", "b", "big", "em",
+ "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
+ case 'a': case 'b': case 'big': case 'code': case 'em': case 'font':
+ case 'i': case 'nobr': case 's': case 'small': case 'strike':
+ case 'strong': case 'tt': case 'u':
+ // XERROR: generally speaking this needs parse error logic
+ /* 1. Let the formatting element be the last element in
+ the list of active formatting elements that:
+ * is between the end of the list and the last scope
+ marker in the list, if any, or the start of the list
+ otherwise, and
+ * has the same tag name as the token.
+ */
+ while(true) {
+ for($a = count($this->a_formatting) - 1; $a >= 0; $a--) {
+ if($this->a_formatting[$a] === self::MARKER) {
+ break;
+
+ } elseif($this->a_formatting[$a]->tagName === $token['name']) {
+ $formatting_element = $this->a_formatting[$a];
+ $in_stack = in_array($formatting_element, $this->stack, true);
+ $fe_af_pos = $a;
+ break;
+ }
+ }
+
+ /* If there is no such node, or, if that node is
+ also in the stack of open elements but the element
+ is not in scope, then this is a parse error. Abort
+ these steps. The token is ignored. */
+ if(!isset($formatting_element) || ($in_stack &&
+ !$this->elementInScope($token['name']))) {
+ $this->ignored = true;
+ break;
+
+ /* Otherwise, if there is such a node, but that node
+ is not in the stack of open elements, then this is a
+ parse error; remove the element from the list, and
+ abort these steps. */
+ } elseif(isset($formatting_element) && !$in_stack) {
+ unset($this->a_formatting[$fe_af_pos]);
+ $this->a_formatting = array_merge($this->a_formatting);
+ break;
+ }
+
+ /* Otherwise, there is a formatting element and that
+ * element is in the stack and is in scope. If the
+ * element is not the current node, this is a parse
+ * error. In any case, proceed with the algorithm as
+ * written in the following steps. */
+ // XERROR: implement me
+
+ /* 2. Let the furthest block be the topmost node in the
+ stack of open elements that is lower in the stack
+ than the formatting element, and is not an element in
+ the phrasing or formatting categories. There might
+ not be one. */
+ $fe_s_pos = array_search($formatting_element, $this->stack, true);
+ $length = count($this->stack);
+
+ for($s = $fe_s_pos + 1; $s < $length; $s++) {
+ $category = $this->getElementCategory($this->stack[$s]);
+
+ if($category !== self::PHRASING && $category !== self::FORMATTING) {
+ $furthest_block = $this->stack[$s];
+ break;
+ }
+ }
+
+ /* 3. If there is no furthest block, then the UA must
+ skip the subsequent steps and instead just pop all
+ the nodes from the bottom of the stack of open
+ elements, from the current node up to the formatting
+ element, and remove the formatting element from the
+ list of active formatting elements. */
+ if(!isset($furthest_block)) {
+ for($n = $length - 1; $n >= $fe_s_pos; $n--) {
+ array_pop($this->stack);
+ }
+
+ unset($this->a_formatting[$fe_af_pos]);
+ $this->a_formatting = array_merge($this->a_formatting);
+ break;
+ }
+
+ /* 4. Let the common ancestor be the element
+ immediately above the formatting element in the stack
+ of open elements. */
+ $common_ancestor = $this->stack[$fe_s_pos - 1];
+
+ /* 5. Let a bookmark note the position of the
+ formatting element in the list of active formatting
+ elements relative to the elements on either side
+ of it in the list. */
+ $bookmark = $fe_af_pos;
+
+ /* 6. Let node and last node be the furthest block.
+ Follow these steps: */
+ $node = $furthest_block;
+ $last_node = $furthest_block;
+
+ while(true) {
+ for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) {
+ /* 6.1 Let node be the element immediately
+ prior to node in the stack of open elements. */
+ $node = $this->stack[$n];
+
+ /* 6.2 If node is not in the list of active
+ formatting elements, then remove node from
+ the stack of open elements and then go back
+ to step 1. */
+ if(!in_array($node, $this->a_formatting, true)) {
+ array_splice($this->stack, $n, 1);
+
+ } else {
+ break;
+ }
+ }
+
+ /* 6.3 Otherwise, if node is the formatting
+ element, then go to the next step in the overall
+ algorithm. */
+ if($node === $formatting_element) {
+ break;
+
+ /* 6.4 Otherwise, if last node is the furthest
+ block, then move the aforementioned bookmark to
+ be immediately after the node in the list of
+ active formatting elements. */
+ } elseif($last_node === $furthest_block) {
+ $bookmark = array_search($node, $this->a_formatting, true) + 1;
+ }
+
+ /* 6.5 Create an element for the token for which
+ * the element node was created, replace the entry
+ * for node in the list of active formatting
+ * elements with an entry for the new element,
+ * replace the entry for node in the stack of open
+ * elements with an entry for the new element, and
+ * let node be the new element. */
+ // we don't know what the token is anymore
+ // XDOM
+ $clone = $node->cloneNode();
+ $a_pos = array_search($node, $this->a_formatting, true);
+ $s_pos = array_search($node, $this->stack, true);
+ $this->a_formatting[$a_pos] = $clone;
+ $this->stack[$s_pos] = $clone;
+ $node = $clone;
+
+ /* 6.6 Insert last node into node, first removing
+ it from its previous parent node if any. */
+ // XDOM
+ if($last_node->parentNode !== null) {
+ $last_node->parentNode->removeChild($last_node);
+ }
+
+ // XDOM
+ $node->appendChild($last_node);
+
+ /* 6.7 Let last node be node. */
+ $last_node = $node;
+
+ /* 6.8 Return to step 1 of this inner set of steps. */
+ }
+
+ /* 7. If the common ancestor node is a table, tbody,
+ * tfoot, thead, or tr element, then, foster parent
+ * whatever last node ended up being in the previous
+ * step, first removing it from its previous parent
+ * node if any. */
+ // XDOM
+ if ($last_node->parentNode) { // common step
+ $last_node->parentNode->removeChild($last_node);
+ }
+ if (in_array($common_ancestor->tagName, array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
+ $this->fosterParent($last_node);
+ /* Otherwise, append whatever last node ended up being
+ * in the previous step to the common ancestor node,
+ * first removing it from its previous parent node if
+ * any. */
+ } else {
+ // XDOM
+ $common_ancestor->appendChild($last_node);
+ }
+
+ /* 8. Create an element for the token for which the
+ * formatting element was created. */
+ // XDOM
+ $clone = $formatting_element->cloneNode();
+
+ /* 9. Take all of the child nodes of the furthest
+ block and append them to the element created in the
+ last step. */
+ // XDOM
+ while($furthest_block->hasChildNodes()) {
+ $child = $furthest_block->firstChild;
+ $furthest_block->removeChild($child);
+ $clone->appendChild($child);
+ }
+
+ /* 10. Append that clone to the furthest block. */
+ // XDOM
+ $furthest_block->appendChild($clone);
+
+ /* 11. Remove the formatting element from the list
+ of active formatting elements, and insert the new element
+ into the list of active formatting elements at the
+ position of the aforementioned bookmark. */
+ $fe_af_pos = array_search($formatting_element, $this->a_formatting, true);
+ array_splice($this->a_formatting, $fe_af_pos, 1);
+
+ $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1);
+ $af_part2 = array_slice($this->a_formatting, $bookmark);
+ $this->a_formatting = array_merge($af_part1, array($clone), $af_part2);
+
+ /* 12. Remove the formatting element from the stack
+ of open elements, and insert the new element into the stack
+ of open elements immediately below the position of the
+ furthest block in that stack. */
+ $fe_s_pos = array_search($formatting_element, $this->stack, true);
+ array_splice($this->stack, $fe_s_pos, 1);
+
+ $fb_s_pos = array_search($furthest_block, $this->stack, true);
+ $s_part1 = array_slice($this->stack, 0, $fb_s_pos + 1);
+ $s_part2 = array_slice($this->stack, $fb_s_pos + 1);
+ $this->stack = array_merge($s_part1, array($clone), $s_part2);
+
+ /* 13. Jump back to step 1 in this series of steps. */
+ unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block);
+ }
+ break;
+
+ case 'applet': case 'button': case 'marquee': case 'object':
+ /* If the stack of open elements has an element in scope whose
+ tag name matches the tag name of the token, then generate implied
+ tags. */
+ if($this->elementInScope($token['name'])) {
+ $this->generateImpliedEndTags();
+
+ /* Now, if the current node is not an element with the same
+ tag name as the token, then this is a parse error. */
+ // XERROR: implement logic
+
+ /* Pop elements from the stack of open elements until
+ * an element with the same tag name as the token has
+ * been popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== $token['name']);
+
+ /* Clear the list of active formatting elements up to the
+ * last marker. */
+ $keys = array_keys($this->a_formatting, self::MARKER, true);
+ $marker = end($keys);
+
+ for($n = count($this->a_formatting) - 1; $n > $marker; $n--) {
+ array_pop($this->a_formatting);
+ }
+ } else {
+ // parse error
+ }
+ break;
+
+ case 'br':
+ // Parse error
+ $this->emitToken(array(
+ 'name' => 'br',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ ));
+ break;
+
+ /* An end tag token not covered by the previous entries */
+ default:
+ for($n = count($this->stack) - 1; $n >= 0; $n--) {
+ /* Initialise node to be the current node (the bottommost
+ node of the stack). */
+ $node = $this->stack[$n];
+
+ /* If node has the same tag name as the end tag token,
+ then: */
+ if($token['name'] === $node->tagName) {
+ /* Generate implied end tags. */
+ $this->generateImpliedEndTags();
+
+ /* If the tag name of the end tag token does not
+ match the tag name of the current node, this is a
+ parse error. */
+ // XERROR: implement this
+
+ /* Pop all the nodes from the current node up to
+ node, including node, then stop these steps. */
+ // XSKETCHY
+ do {
+ $pop = array_pop($this->stack);
+ } while ($pop !== $node);
+ break;
+
+ } else {
+ $category = $this->getElementCategory($node);
+
+ if($category !== self::FORMATTING && $category !== self::PHRASING) {
+ /* Otherwise, if node is in neither the formatting
+ category nor the phrasing category, then this is a
+ parse error. Stop this algorithm. The end tag token
+ is ignored. */
+ $this->ignored = true;
+ break;
+ // parse error
+ }
+ }
+ /* Set node to the previous entry in the stack of open elements. Loop. */
+ }
+ break;
+ }
+ break;
+ }
+ break;
+
+ case self::IN_CDATA_RCDATA:
+ if (
+ $token['type'] === HTML5_Tokenizer::CHARACTER ||
+ $token['type'] === HTML5_Tokenizer::SPACECHARACTER
+ ) {
+ $this->insertText($token['data']);
+ } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
+ // parse error
+ /* If the current node is a script element, mark the script
+ * element as "already executed". */
+ // probably not necessary
+ array_pop($this->stack);
+ $this->mode = $this->original_mode;
+ $this->emitToken($token);
+ } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'script') {
+ array_pop($this->stack);
+ $this->mode = $this->original_mode;
+ // we're ignoring all of the execution stuff
+ } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG) {
+ array_pop($this->stack);
+ $this->mode = $this->original_mode;
+ }
+ break;
+
+ case self::IN_TABLE:
+ $clear = array('html', 'table');
+
+ /* A character token */
+ if ($token['type'] === HTML5_Tokenizer::CHARACTER ||
+ $token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Let the pending table character tokens
+ * be an empty list of tokens. */
+ $this->pendingTableCharacters = "";
+ $this->pendingTableCharactersDirty = false;
+ /* Let the original insertion mode be the current
+ * insertion mode. */
+ $this->original_mode = $this->mode;
+ /* Switch the insertion mode to
+ * "in table text" and
+ * reprocess the token. */
+ $this->mode = self::IN_TABLE_TEXT;
+ $this->emitToken($token);
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token. */
+ $this->insertComment($token['data']);
+
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // parse error
+
+ /* A start tag whose tag name is "caption" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'caption') {
+ /* Clear the stack back to a table context. */
+ $this->clearStackToTableContext($clear);
+
+ /* Insert a marker at the end of the list of active
+ formatting elements. */
+ $this->a_formatting[] = self::MARKER;
+
+ /* Insert an HTML element for the token, then switch the
+ insertion mode to "in caption". */
+ $this->insertElement($token);
+ $this->mode = self::IN_CAPTION;
+
+ /* A start tag whose tag name is "colgroup" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'colgroup') {
+ /* Clear the stack back to a table context. */
+ $this->clearStackToTableContext($clear);
+
+ /* Insert an HTML element for the token, then switch the
+ insertion mode to "in column group". */
+ $this->insertElement($token);
+ $this->mode = self::IN_COLUMN_GROUP;
+
+ /* A start tag whose tag name is "col" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'col') {
+ $this->emitToken(array(
+ 'name' => 'colgroup',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => array()
+ ));
+
+ $this->emitToken($token);
+
+ /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
+ array('tbody', 'tfoot', 'thead'))) {
+ /* Clear the stack back to a table context. */
+ $this->clearStackToTableContext($clear);
+
+ /* Insert an HTML element for the token, then switch the insertion
+ mode to "in table body". */
+ $this->insertElement($token);
+ $this->mode = self::IN_TABLE_BODY;
+
+ /* A start tag whose tag name is one of: "td", "th", "tr" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ in_array($token['name'], array('td', 'th', 'tr'))) {
+ /* Act as if a start tag token with the tag name "tbody" had been
+ seen, then reprocess the current token. */
+ $this->emitToken(array(
+ 'name' => 'tbody',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => array()
+ ));
+
+ $this->emitToken($token);
+
+ /* A start tag whose tag name is "table" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'table') {
+ /* Parse error. Act as if an end tag token with the tag name "table"
+ had been seen, then, if that token wasn't ignored, reprocess the
+ current token. */
+ $this->emitToken(array(
+ 'name' => 'table',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ if (!$this->ignored) $this->emitToken($token);
+
+ /* An end tag whose tag name is "table" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'table') {
+ /* If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse error.
+ Ignore the token. (fragment case) */
+ if(!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
+ $this->ignored = true;
+
+ /* Otherwise: */
+ } else {
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== 'table');
+
+ /* Reset the insertion mode appropriately. */
+ $this->resetInsertionMode();
+ }
+
+ /* An end tag whose tag name is one of: "body", "caption", "col",
+ "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
+ array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td',
+ 'tfoot', 'th', 'thead', 'tr'))) {
+ // Parse error. Ignore the token.
+
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ ($token['name'] === 'style' || $token['name'] === 'script')) {
+ $this->processWithRulesFor($token, self::IN_HEAD);
+
+ } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'input' &&
+ // assignment is intentional
+ /* If the token does not have an attribute with the name "type", or
+ * if it does, but that attribute's value is not an ASCII
+ * case-insensitive match for the string "hidden", then: act as
+ * described in the "anything else" entry below. */
+ ($type = $this->getAttr($token, 'type')) && strtolower($type) === 'hidden') {
+ // I.e., if its an input with the type attribute == 'hidden'
+ /* Otherwise */
+ // parse error
+ $this->insertElement($token);
+ array_pop($this->stack);
+ } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
+ /* If the current node is not the root html element, then this is a parse error. */
+ if (end($this->stack)->tagName !== 'html') {
+ // Note: It can only be the current node in the fragment case.
+ // parse error
+ }
+ /* Stop parsing. */
+ /* Anything else */
+ } else {
+ /* Parse error. Process the token as if the insertion mode was "in
+ body", with the following exception: */
+
+ $old = $this->foster_parent;
+ $this->foster_parent = true;
+ $this->processWithRulesFor($token, self::IN_BODY);
+ $this->foster_parent = $old;
+ }
+ break;
+
+ case self::IN_TABLE_TEXT:
+ /* A character token */
+ if($token['type'] === HTML5_Tokenizer::CHARACTER) {
+ /* Append the character token to the pending table
+ * character tokens list. */
+ $this->pendingTableCharacters .= $token['data'];
+ $this->pendingTableCharactersDirty = true;
+ } elseif ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ $this->pendingTableCharacters .= $token['data'];
+ /* Anything else */
+ } else {
+ if ($this->pendingTableCharacters !== '' && is_string($this->pendingTableCharacters)) {
+ /* If any of the tokens in the pending table character tokens list
+ * are character tokens that are not one of U+0009 CHARACTER
+ * TABULATION, U+000A LINE FEED (LF), U+000C FORM FEED (FF), or
+ * U+0020 SPACE, then reprocess those character tokens using the
+ * rules given in the "anything else" entry in the in table"
+ * insertion mode.*/
+ if ($this->pendingTableCharactersDirty) {
+ /* Parse error. Process the token using the rules for the
+ * "in body" insertion mode, except that if the current
+ * node is a table, tbody, tfoot, thead, or tr element,
+ * then, whenever a node would be inserted into the current
+ * node, it must instead be foster parented. */
+ // XERROR
+ $old = $this->foster_parent;
+ $this->foster_parent = true;
+ $text_token = array(
+ 'type' => HTML5_Tokenizer::CHARACTER,
+ 'data' => $this->pendingTableCharacters,
+ );
+ $this->processWithRulesFor($text_token, self::IN_BODY);
+ $this->foster_parent = $old;
+
+ /* Otherwise, insert the characters given by the pending table
+ * character tokens list into the current node. */
+ } else {
+ $this->insertText($this->pendingTableCharacters);
+ }
+ $this->pendingTableCharacters = null;
+ $this->pendingTableCharactersNull = null;
+ }
+
+ /* Switch the insertion mode to the original insertion mode and
+ * reprocess the token.
+ */
+ $this->mode = $this->original_mode;
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::IN_CAPTION:
+ /* An end tag whose tag name is "caption" */
+ if($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'caption') {
+ /* If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse error.
+ Ignore the token. (fragment case) */
+ if(!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
+ $this->ignored = true;
+ // Ignore
+
+ /* Otherwise: */
+ } else {
+ /* Generate implied end tags. */
+ $this->generateImpliedEndTags();
+
+ /* Now, if the current node is not a caption element, then this
+ is a parse error. */
+ // XERROR: implement
+
+ /* Pop elements from this stack until a caption element has
+ been popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== 'caption');
+
+ /* Clear the list of active formatting elements up to the last
+ marker. */
+ $this->clearTheActiveFormattingElementsUpToTheLastMarker();
+
+ /* Switch the insertion mode to "in table". */
+ $this->mode = self::IN_TABLE;
+ }
+
+ /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag
+ name is "table" */
+ } elseif(($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
+ array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
+ 'thead', 'tr'))) || ($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'table')) {
+ /* Parse error. Act as if an end tag with the tag name "caption"
+ had been seen, then, if that token wasn't ignored, reprocess the
+ current token. */
+ $this->emitToken(array(
+ 'name' => 'caption',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ if (!$this->ignored) $this->emitToken($token);
+
+ /* An end tag whose tag name is one of: "body", "col", "colgroup",
+ "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
+ array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th',
+ 'thead', 'tr'))) {
+ // Parse error. Ignore the token.
+ $this->ignored = true;
+
+ /* Anything else */
+ } else {
+ /* Process the token as if the insertion mode was "in body". */
+ $this->processWithRulesFor($token, self::IN_BODY);
+ }
+ break;
+
+ case self::IN_COLUMN_GROUP:
+ /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+ or U+0020 SPACE */
+ if($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Append the character to the current node. */
+ $this->insertText($token['data']);
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token. */
+ $this->insertToken($token['data']);
+
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // parse error
+
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* A start tag whose tag name is "col" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'col') {
+ /* Insert a col element for the token. Immediately pop the current
+ node off the stack of open elements. */
+ $this->insertElement($token);
+ array_pop($this->stack);
+ // XERROR: Acknowledge the token's self-closing flag, if it is set.
+
+ /* An end tag whose tag name is "colgroup" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'colgroup') {
+ /* If the current node is the root html element, then this is a
+ parse error, ignore the token. (fragment case) */
+ if(end($this->stack)->tagName === 'html') {
+ $this->ignored = true;
+
+ /* Otherwise, pop the current node (which will be a colgroup
+ element) from the stack of open elements. Switch the insertion
+ mode to "in table". */
+ } else {
+ array_pop($this->stack);
+ $this->mode = self::IN_TABLE;
+ }
+
+ /* An end tag whose tag name is "col" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'col') {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+
+ /* An end-of-file token */
+ /* If the current node is the root html element */
+ } elseif($token['type'] === HTML5_Tokenizer::EOF && end($this->stack)->tagName === 'html') {
+ /* Stop parsing */
+
+ /* Anything else */
+ } else {
+ /* Act as if an end tag with the tag name "colgroup" had been seen,
+ and then, if that token wasn't ignored, reprocess the current token. */
+ $this->emitToken(array(
+ 'name' => 'colgroup',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ if (!$this->ignored) $this->emitToken($token);
+ }
+ break;
+
+ case self::IN_TABLE_BODY:
+ $clear = array('tbody', 'tfoot', 'thead', 'html');
+
+ /* A start tag whose tag name is "tr" */
+ if($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'tr') {
+ /* Clear the stack back to a table body context. */
+ $this->clearStackToTableContext($clear);
+
+ /* Insert a tr element for the token, then switch the insertion
+ mode to "in row". */
+ $this->insertElement($token);
+ $this->mode = self::IN_ROW;
+
+ /* A start tag whose tag name is one of: "th", "td" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ ($token['name'] === 'th' || $token['name'] === 'td')) {
+ /* Parse error. Act as if a start tag with the tag name "tr" had
+ been seen, then reprocess the current token. */
+ $this->emitToken(array(
+ 'name' => 'tr',
+ 'type' => HTML5_Tokenizer::STARTTAG,
+ 'attr' => array()
+ ));
+
+ $this->emitToken($token);
+
+ /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ in_array($token['name'], array('tbody', 'tfoot', 'thead'))) {
+ /* If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse error.
+ Ignore the token. */
+ if(!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
+ // Parse error
+ $this->ignored = true;
+
+ /* Otherwise: */
+ } else {
+ /* Clear the stack back to a table body context. */
+ $this->clearStackToTableContext($clear);
+
+ /* Pop the current node from the stack of open elements. Switch
+ the insertion mode to "in table". */
+ array_pop($this->stack);
+ $this->mode = self::IN_TABLE;
+ }
+
+ /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */
+ } elseif(($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
+ array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead'))) ||
+ ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'table')) {
+ /* If the stack of open elements does not have a tbody, thead, or
+ tfoot element in table scope, this is a parse error. Ignore the
+ token. (fragment case) */
+ if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), self::SCOPE_TABLE)) {
+ // parse error
+ $this->ignored = true;
+
+ /* Otherwise: */
+ } else {
+ /* Clear the stack back to a table body context. */
+ $this->clearStackToTableContext($clear);
+
+ /* Act as if an end tag with the same tag name as the current
+ node ("tbody", "tfoot", or "thead") had been seen, then
+ reprocess the current token. */
+ $this->emitToken(array(
+ 'name' => end($this->stack)->tagName,
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ $this->emitToken($token);
+ }
+
+ /* An end tag whose tag name is one of: "body", "caption", "col",
+ "colgroup", "html", "td", "th", "tr" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
+ array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+
+ /* Anything else */
+ } else {
+ /* Process the token as if the insertion mode was "in table". */
+ $this->processWithRulesFor($token, self::IN_TABLE);
+ }
+ break;
+
+ case self::IN_ROW:
+ $clear = array('tr', 'html');
+
+ /* A start tag whose tag name is one of: "th", "td" */
+ if($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ ($token['name'] === 'th' || $token['name'] === 'td')) {
+ /* Clear the stack back to a table row context. */
+ $this->clearStackToTableContext($clear);
+
+ /* Insert an HTML element for the token, then switch the insertion
+ mode to "in cell". */
+ $this->insertElement($token);
+ $this->mode = self::IN_CELL;
+
+ /* Insert a marker at the end of the list of active formatting
+ elements. */
+ $this->a_formatting[] = self::MARKER;
+
+ /* An end tag whose tag name is "tr" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'tr') {
+ /* If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse error.
+ Ignore the token. (fragment case) */
+ if(!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
+ // Ignore.
+ $this->ignored = true;
+
+ /* Otherwise: */
+ } else {
+ /* Clear the stack back to a table row context. */
+ $this->clearStackToTableContext($clear);
+
+ /* Pop the current node (which will be a tr element) from the
+ stack of open elements. Switch the insertion mode to "in table
+ body". */
+ array_pop($this->stack);
+ $this->mode = self::IN_TABLE_BODY;
+ }
+
+ /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */
+ } elseif(($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
+ array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) ||
+ ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'table')) {
+ /* Act as if an end tag with the tag name "tr" had been seen, then,
+ if that token wasn't ignored, reprocess the current token. */
+ $this->emitToken(array(
+ 'name' => 'tr',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ if (!$this->ignored) $this->emitToken($token);
+
+ /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ in_array($token['name'], array('tbody', 'tfoot', 'thead'))) {
+ /* If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse error.
+ Ignore the token. */
+ if(!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
+ $this->ignored = true;
+
+ /* Otherwise: */
+ } else {
+ /* Otherwise, act as if an end tag with the tag name "tr" had
+ been seen, then reprocess the current token. */
+ $this->emitToken(array(
+ 'name' => 'tr',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ $this->emitToken($token);
+ }
+
+ /* An end tag whose tag name is one of: "body", "caption", "col",
+ "colgroup", "html", "td", "th" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
+ array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th'))) {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+
+ /* Anything else */
+ } else {
+ /* Process the token as if the insertion mode was "in table". */
+ $this->processWithRulesFor($token, self::IN_TABLE);
+ }
+ break;
+
+ case self::IN_CELL:
+ /* An end tag whose tag name is one of: "td", "th" */
+ if($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ ($token['name'] === 'td' || $token['name'] === 'th')) {
+ /* If the stack of open elements does not have an element in table
+ scope with the same tag name as that of the token, then this is a
+ parse error and the token must be ignored. */
+ if(!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
+ $this->ignored = true;
+
+ /* Otherwise: */
+ } else {
+ /* Generate implied end tags, except for elements with the same
+ tag name as the token. */
+ $this->generateImpliedEndTags(array($token['name']));
+
+ /* Now, if the current node is not an element with the same tag
+ name as the token, then this is a parse error. */
+ // XERROR: Implement parse error code
+
+ /* Pop elements from this stack until an element with the same
+ tag name as the token has been popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== $token['name']);
+
+ /* Clear the list of active formatting elements up to the last
+ marker. */
+ $this->clearTheActiveFormattingElementsUpToTheLastMarker();
+
+ /* Switch the insertion mode to "in row". (The current node
+ will be a tr element at this point.) */
+ $this->mode = self::IN_ROW;
+ }
+
+ /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "tbody", "td", "tfoot", "th", "thead", "tr" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
+ array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
+ 'thead', 'tr'))) {
+ /* If the stack of open elements does not have a td or th element
+ in table scope, then this is a parse error; ignore the token.
+ (fragment case) */
+ if(!$this->elementInScope(array('td', 'th'), self::SCOPE_TABLE)) {
+ // parse error
+ $this->ignored = true;
+
+ /* Otherwise, close the cell (see below) and reprocess the current
+ token. */
+ } else {
+ $this->closeCell();
+ $this->emitToken($token);
+ }
+
+ /* An end tag whose tag name is one of: "body", "caption", "col",
+ "colgroup", "html" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
+ array('body', 'caption', 'col', 'colgroup', 'html'))) {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+
+ /* An end tag whose tag name is one of: "table", "tbody", "tfoot",
+ "thead", "tr" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
+ array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
+ /* If the stack of open elements does not have a td or th element
+ in table scope, then this is a parse error; ignore the token.
+ (innerHTML case) */
+ if(!$this->elementInScope(array('td', 'th'), self::SCOPE_TABLE)) {
+ // Parse error
+ $this->ignored = true;
+
+ /* Otherwise, close the cell (see below) and reprocess the current
+ token. */
+ } else {
+ $this->closeCell();
+ $this->emitToken($token);
+ }
+
+ /* Anything else */
+ } else {
+ /* Process the token as if the insertion mode was "in body". */
+ $this->processWithRulesFor($token, self::IN_BODY);
+ }
+ break;
+
+ case self::IN_SELECT:
+ /* Handle the token as follows: */
+
+ /* A character token */
+ if(
+ $token['type'] === HTML5_Tokenizer::CHARACTER ||
+ $token['type'] === HTML5_Tokenizer::SPACECHARACTER
+ ) {
+ /* Append the token's character to the current node. */
+ $this->insertText($token['data']);
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token. */
+ $this->insertComment($token['data']);
+
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // parse error
+
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
+ $this->processWithRulesFor($token, self::INBODY);
+
+ /* A start tag token whose tag name is "option" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'option') {
+ /* If the current node is an option element, act as if an end tag
+ with the tag name "option" had been seen. */
+ if(end($this->stack)->tagName === 'option') {
+ $this->emitToken(array(
+ 'name' => 'option',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ /* A start tag token whose tag name is "optgroup" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'optgroup') {
+ /* If the current node is an option element, act as if an end tag
+ with the tag name "option" had been seen. */
+ if(end($this->stack)->tagName === 'option') {
+ $this->emitToken(array(
+ 'name' => 'option',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* If the current node is an optgroup element, act as if an end tag
+ with the tag name "optgroup" had been seen. */
+ if(end($this->stack)->tagName === 'optgroup') {
+ $this->emitToken(array(
+ 'name' => 'optgroup',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ /* An end tag token whose tag name is "optgroup" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'optgroup') {
+ /* First, if the current node is an option element, and the node
+ immediately before it in the stack of open elements is an optgroup
+ element, then act as if an end tag with the tag name "option" had
+ been seen. */
+ $elements_in_stack = count($this->stack);
+
+ if($this->stack[$elements_in_stack - 1]->tagName === 'option' &&
+ $this->stack[$elements_in_stack - 2]->tagName === 'optgroup') {
+ $this->emitToken(array(
+ 'name' => 'option',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ }
+
+ /* If the current node is an optgroup element, then pop that node
+ from the stack of open elements. Otherwise, this is a parse error,
+ ignore the token. */
+ if(end($this->stack)->tagName === 'optgroup') {
+ array_pop($this->stack);
+ } else {
+ // parse error
+ $this->ignored = true;
+ }
+
+ /* An end tag token whose tag name is "option" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'option') {
+ /* If the current node is an option element, then pop that node
+ from the stack of open elements. Otherwise, this is a parse error,
+ ignore the token. */
+ if(end($this->stack)->tagName === 'option') {
+ array_pop($this->stack);
+ } else {
+ // parse error
+ $this->ignored = true;
+ }
+
+ /* An end tag whose tag name is "select" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'select') {
+ /* If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse error.
+ Ignore the token. (fragment case) */
+ if(!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
+ $this->ignored = true;
+ // parse error
+
+ /* Otherwise: */
+ } else {
+ /* Pop elements from the stack of open elements until a select
+ element has been popped from the stack. */
+ do {
+ $node = array_pop($this->stack);
+ } while ($node->tagName !== 'select');
+
+ /* Reset the insertion mode appropriately. */
+ $this->resetInsertionMode();
+ }
+
+ /* A start tag whose tag name is "select" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'select') {
+ /* Parse error. Act as if the token had been an end tag with the
+ tag name "select" instead. */
+ $this->emitToken(array(
+ 'name' => 'select',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ ($token['name'] === 'input' || $token['name'] === 'keygen' || $token['name'] === 'textarea')) {
+ // parse error
+ $this->emitToken(array(
+ 'name' => 'select',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+ $this->emitToken($token);
+
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'script') {
+ $this->processWithRulesFor($token, self::IN_HEAD);
+
+ } elseif($token['type'] === HTML5_Tokenizer::EOF) {
+ // XERROR: If the current node is not the root html element, then this is a parse error.
+ /* Stop parsing */
+
+ /* Anything else */
+ } else {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+ }
+ break;
+
+ case self::IN_SELECT_IN_TABLE:
+
+ if($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ in_array($token['name'], array('caption', 'table', 'tbody',
+ 'tfoot', 'thead', 'tr', 'td', 'th'))) {
+ // parse error
+ $this->emitToken(array(
+ 'name' => 'select',
+ 'type' => HTML5_Tokenizer::ENDTAG,
+ ));
+ $this->emitToken($token);
+
+ /* An end tag whose tag name is one of: "caption", "table", "tbody",
+ "tfoot", "thead", "tr", "td", "th" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ in_array($token['name'], array('caption', 'table', 'tbody', 'tfoot', 'thead', 'tr', 'td', 'th'))) {
+ /* Parse error. */
+ // parse error
+
+ /* If the stack of open elements has an element in table scope with
+ the same tag name as that of the token, then act as if an end tag
+ with the tag name "select" had been seen, and reprocess the token.
+ Otherwise, ignore the token. */
+ if($this->elementInScope($token['name'], self::SCOPE_TABLE)) {
+ $this->emitToken(array(
+ 'name' => 'select',
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ $this->emitToken($token);
+ } else {
+ $this->ignored = true;
+ }
+ } else {
+ $this->processWithRulesFor($token, self::IN_SELECT);
+ }
+ break;
+
+ case self::IN_FOREIGN_CONTENT:
+ if ($token['type'] === HTML5_Tokenizer::CHARACTER ||
+ $token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ $this->insertText($token['data']);
+ } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
+ $this->insertComment($token['data']);
+ } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // XERROR: parse error
+ } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'script' && end($this->stack)->tagName === 'script' &&
+ // XDOM
+ end($this->stack)->namespaceURI === self::NS_SVG) {
+ array_pop($this->stack);
+ // a bunch of script running mumbo jumbo
+ } elseif (
+ ($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ ((
+ $token['name'] !== 'mglyph' &&
+ $token['name'] !== 'malignmark' &&
+ // XDOM
+ end($this->stack)->namespaceURI === self::NS_MATHML &&
+ in_array(end($this->stack)->tagName, array('mi', 'mo', 'mn', 'ms', 'mtext'))
+ ) ||
+ (
+ $token['name'] === 'svg' &&
+ // XDOM
+ end($this->stack)->namespaceURI === self::NS_MATHML &&
+ end($this->stack)->tagName === 'annotation-xml'
+ ) ||
+ (
+ // XDOM
+ end($this->stack)->namespaceURI === self::NS_SVG &&
+ in_array(end($this->stack)->tagName, array('foreignObject', 'desc', 'title'))
+ ) ||
+ (
+ // XSKETCHY && XDOM
+ end($this->stack)->namespaceURI === self::NS_HTML
+ ))
+ ) || $token['type'] === HTML5_Tokenizer::ENDTAG
+ ) {
+ $this->processWithRulesFor($token, $this->secondary_mode);
+ /* If, after doing so, the insertion mode is still "in foreign
+ * content", but there is no element in scope that has a namespace
+ * other than the HTML namespace, switch the insertion mode to the
+ * secondary insertion mode. */
+ if ($this->mode === self::IN_FOREIGN_CONTENT) {
+ $found = false;
+ // this basically duplicates elementInScope()
+ for ($i = count($this->stack) - 1; $i >= 0; $i--) {
+ // XDOM
+ $node = $this->stack[$i];
+ if ($node->namespaceURI !== self::NS_HTML) {
+ $found = true;
+ break;
+ } elseif (in_array($node->tagName, array('table', 'html',
+ 'applet', 'caption', 'td', 'th', 'button', 'marquee',
+ 'object')) || ($node->tagName === 'foreignObject' &&
+ $node->namespaceURI === self::NS_SVG)) {
+ break;
+ }
+ }
+ if (!$found) {
+ $this->mode = $this->secondary_mode;
+ }
+ }
+ } elseif ($token['type'] === HTML5_Tokenizer::EOF || (
+ $token['type'] === HTML5_Tokenizer::STARTTAG &&
+ (in_array($token['name'], array('b', "big", "blockquote", "body", "br",
+ "center", "code", "dc", "dd", "div", "dl", "ds", "dt", "em", "embed", "h1", "h2",
+ "h3", "h4", "h5", "h6", "head", "hr", "i", "img", "li", "listing",
+ "menu", "meta", "nobr", "ol", "p", "pre", "ruby", "s", "small",
+ "span", "strong", "strike", "sub", "sup", "table", "tt", "u", "ul",
+ "var")) || ($token['name'] === 'font' && ($this->getAttr($token, 'color') ||
+ $this->getAttr($token, 'face') || $this->getAttr($token, 'size')))))) {
+ // XERROR: parse error
+ do {
+ $node = array_pop($this->stack);
+ // XDOM
+ } while ($node->namespaceURI !== self::NS_HTML);
+ $this->stack[] = $node;
+ $this->mode = $this->secondary_mode;
+ $this->emitToken($token);
+ } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG) {
+ static $svg_lookup = array(
+ 'altglyph' => 'altGlyph',
+ 'altglyphdef' => 'altGlyphDef',
+ 'altglyphitem' => 'altGlyphItem',
+ 'animatecolor' => 'animateColor',
+ 'animatemotion' => 'animateMotion',
+ 'animatetransform' => 'animateTransform',
+ 'clippath' => 'clipPath',
+ 'feblend' => 'feBlend',
+ 'fecolormatrix' => 'feColorMatrix',
+ 'fecomponenttransfer' => 'feComponentTransfer',
+ 'fecomposite' => 'feComposite',
+ 'feconvolvematrix' => 'feConvolveMatrix',
+ 'fediffuselighting' => 'feDiffuseLighting',
+ 'fedisplacementmap' => 'feDisplacementMap',
+ 'fedistantlight' => 'feDistantLight',
+ 'feflood' => 'feFlood',
+ 'fefunca' => 'feFuncA',
+ 'fefuncb' => 'feFuncB',
+ 'fefuncg' => 'feFuncG',
+ 'fefuncr' => 'feFuncR',
+ 'fegaussianblur' => 'feGaussianBlur',
+ 'feimage' => 'feImage',
+ 'femerge' => 'feMerge',
+ 'femergenode' => 'feMergeNode',
+ 'femorphology' => 'feMorphology',
+ 'feoffset' => 'feOffset',
+ 'fepointlight' => 'fePointLight',
+ 'fespecularlighting' => 'feSpecularLighting',
+ 'fespotlight' => 'feSpotLight',
+ 'fetile' => 'feTile',
+ 'feturbulence' => 'feTurbulence',
+ 'foreignobject' => 'foreignObject',
+ 'glyphref' => 'glyphRef',
+ 'lineargradient' => 'linearGradient',
+ 'radialgradient' => 'radialGradient',
+ 'textpath' => 'textPath',
+ );
+ // XDOM
+ $current = end($this->stack);
+ if ($current->namespaceURI === self::NS_MATHML) {
+ $token = $this->adjustMathMLAttributes($token);
+ }
+ if ($current->namespaceURI === self::NS_SVG &&
+ isset($svg_lookup[$token['name']])) {
+ $token['name'] = $svg_lookup[$token['name']];
+ }
+ if ($current->namespaceURI === self::NS_SVG) {
+ $token = $this->adjustSVGAttributes($token);
+ }
+ $token = $this->adjustForeignAttributes($token);
+ $this->insertForeignElement($token, $current->namespaceURI);
+ if (isset($token['self-closing'])) {
+ array_pop($this->stack);
+ // XERROR: acknowledge self-closing flag
+ }
+ }
+ break;
+
+ case self::AFTER_BODY:
+ /* Handle the token as follows: */
+
+ /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+ or U+0020 SPACE */
+ if($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Process the token as it would be processed if the insertion mode
+ was "in body". */
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the first element in the stack of open
+ elements (the html element), with the data attribute set to the
+ data given in the comment token. */
+ // XDOM
+ $comment = $this->dom->createComment($token['data']);
+ $this->stack[0]->appendChild($comment);
+
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // parse error
+
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* An end tag with the tag name "html" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'html') {
+ /* If the parser was originally created as part of the HTML
+ * fragment parsing algorithm, this is a parse error; ignore
+ * the token. (fragment case) */
+ $this->ignored = true;
+ // XERROR: implement this
+
+ $this->mode = self::AFTER_AFTER_BODY;
+
+ } elseif($token['type'] === HTML5_Tokenizer::EOF) {
+ /* Stop parsing */
+
+ /* Anything else */
+ } else {
+ /* Parse error. Set the insertion mode to "in body" and reprocess
+ the token. */
+ $this->mode = self::IN_BODY;
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::IN_FRAMESET:
+ /* Handle the token as follows: */
+
+ /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+ U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
+ if($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Append the character to the current node. */
+ $this->insertText($token['data']);
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token. */
+ $this->insertComment($token['data']);
+
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // parse error
+
+ /* A start tag with the tag name "frameset" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'frameset') {
+ $this->insertElement($token);
+
+ /* An end tag with the tag name "frameset" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'frameset') {
+ /* If the current node is the root html element, then this is a
+ parse error; ignore the token. (fragment case) */
+ if(end($this->stack)->tagName === 'html') {
+ $this->ignored = true;
+ // Parse error
+
+ } else {
+ /* Otherwise, pop the current node from the stack of open
+ elements. */
+ array_pop($this->stack);
+
+ /* If the parser was not originally created as part of the HTML
+ * fragment parsing algorithm (fragment case), and the current
+ * node is no longer a frameset element, then switch the
+ * insertion mode to "after frameset". */
+ $this->mode = self::AFTER_FRAMESET;
+ }
+
+ /* A start tag with the tag name "frame" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'frame') {
+ /* Insert an HTML element for the token. */
+ $this->insertElement($token);
+
+ /* Immediately pop the current node off the stack of open elements. */
+ array_pop($this->stack);
+
+ // XERROR: Acknowledge the token's self-closing flag, if it is set.
+
+ /* A start tag with the tag name "noframes" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'noframes') {
+ /* Process the token using the rules for the "in head" insertion mode. */
+ $this->processwithRulesFor($token, self::IN_HEAD);
+
+ } elseif($token['type'] === HTML5_Tokenizer::EOF) {
+ // XERROR: If the current node is not the root html element, then this is a parse error.
+ /* Stop parsing */
+ /* Anything else */
+ } else {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+ }
+ break;
+
+ case self::AFTER_FRAMESET:
+ /* Handle the token as follows: */
+
+ /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+ U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
+ if($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
+ /* Append the character to the current node. */
+ $this->insertText($token['data']);
+
+ /* A comment token */
+ } elseif($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token. */
+ $this->insertComment($token['data']);
+
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE) {
+ // parse error
+
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* An end tag with the tag name "html" */
+ } elseif($token['type'] === HTML5_Tokenizer::ENDTAG &&
+ $token['name'] === 'html') {
+ $this->mode = self::AFTER_AFTER_FRAMESET;
+
+ /* A start tag with the tag name "noframes" */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG &&
+ $token['name'] === 'noframes') {
+ $this->processWithRulesFor($token, self::IN_HEAD);
+
+ } elseif($token['type'] === HTML5_Tokenizer::EOF) {
+ /* Stop parsing */
+
+ /* Anything else */
+ } else {
+ /* Parse error. Ignore the token. */
+ $this->ignored = true;
+ }
+ break;
+
+ case self::AFTER_AFTER_BODY:
+ /* A comment token */
+ if($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the Document object with the data
+ attribute set to the data given in the comment token. */
+ // XDOM
+ $comment = $this->dom->createComment($token['data']);
+ $this->dom->appendChild($comment);
+
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE ||
+ $token['type'] === HTML5_Tokenizer::SPACECHARACTER ||
+ ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html')) {
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* An end-of-file token */
+ } elseif($token['type'] === HTML5_Tokenizer::EOF) {
+ /* OMG DONE!! */
+ } else {
+ // parse error
+ $this->mode = self::IN_BODY;
+ $this->emitToken($token);
+ }
+ break;
+
+ case self::AFTER_AFTER_FRAMESET:
+ /* A comment token */
+ if($token['type'] === HTML5_Tokenizer::COMMENT) {
+ /* Append a Comment node to the Document object with the data
+ attribute set to the data given in the comment token. */
+ // XDOM
+ $comment = $this->dom->createComment($token['data']);
+ $this->dom->appendChild($comment);
+
+ } elseif($token['type'] === HTML5_Tokenizer::DOCTYPE ||
+ $token['type'] === HTML5_Tokenizer::SPACECHARACTER ||
+ ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html')) {
+ $this->processWithRulesFor($token, self::IN_BODY);
+
+ /* An end-of-file token */
+ } elseif($token['type'] === HTML5_Tokenizer::EOF) {
+ /* OMG DONE!! */
+ } elseif($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'nofrmaes') {
+ $this->processWithRulesFor($token, self::IN_HEAD);
+ } else {
+ // parse error
+ }
+ break;
+ }
+ // end funky indenting
+ }
+
+ private function insertElement($token, $append = true) {
+ $el = $this->dom->createElementNS(self::NS_HTML, $token['name']);
+
+ if (!empty($token['attr'])) {
+ foreach($token['attr'] as $attr) {
+ if(!$el->hasAttribute($attr['name'])) {
+ $el->setAttribute($attr['name'], $attr['value']);
+ }
+ }
+ }
+ if ($append) {
+ $this->appendToRealParent($el);
+ $this->stack[] = $el;
+ }
+
+ return $el;
+ }
+
+ private function insertText($data) {
+ if ($data === '') return;
+ if ($this->ignore_lf_token) {
+ if ($data[0] === "\n") {
+ $data = substr($data, 1);
+ if ($data === false) return;
+ }
+ }
+ $text = $this->dom->createTextNode($data);
+ $this->appendToRealParent($text);
+ }
+
+ private function insertComment($data) {
+ $comment = $this->dom->createComment($data);
+ $this->appendToRealParent($comment);
+ }
+
+ private function appendToRealParent($node) {
+ // this is only for the foster_parent case
+ /* If the current node is a table, tbody, tfoot, thead, or tr
+ element, then, whenever a node would be inserted into the current
+ node, it must instead be inserted into the foster parent element. */
+ if(!$this->foster_parent || !in_array(end($this->stack)->tagName,
+ array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
+ end($this->stack)->appendChild($node);
+ } else {
+ $this->fosterParent($node);
+ }
+ }
+
+ private function elementInScope($el, $scope = self::SCOPE) {
+ if(is_array($el)) {
+ foreach($el as $element) {
+ if($this->elementInScope($element, $scope)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ $leng = count($this->stack);
+
+ for($n = 0; $n < $leng; $n++) {
+ /* 1. Initialise node to be the current node (the bottommost node of
+ the stack). */
+ $node = $this->stack[$leng - 1 - $n];
+
+ if($node->tagName === $el) {
+ /* 2. If node is the target node, terminate in a match state. */
+ return true;
+
+ // We've expanded the logic for these states a little differently;
+ // Hixie's refactoring into "specific scope" is more general, but
+ // this "gets the job done"
+
+ // these are the common states for all scopes
+ } elseif($node->tagName === 'table' || $node->tagName === 'html') {
+ return false;
+
+ // these are valid for "in scope" and "in list item scope"
+ } elseif($scope !== self::SCOPE_TABLE &&
+ (in_array($node->tagName, array('applet', 'caption', 'td',
+ 'th', 'button', 'marquee', 'object')) ||
+ $node->tagName === 'foreignObject' && $node->namespaceURI === self::NS_SVG)) {
+ return false;
+
+
+ // these are valid for "in list item scope"
+ } elseif($scope === self::SCOPE_LISTITEM && in_array($node->tagName, array('ol', 'ul'))) {
+ return false;
+ }
+
+ /* Otherwise, set node to the previous entry in the stack of open
+ elements and return to step 2. (This will never fail, since the loop
+ will always terminate in the previous step if the top of the stack
+ is reached.) */
+ }
+ }
+
+ private function reconstructActiveFormattingElements() {
+ /* 1. If there are no entries in the list of active formatting elements,
+ then there is nothing to reconstruct; stop this algorithm. */
+ $formatting_elements = count($this->a_formatting);
+
+ if($formatting_elements === 0) {
+ return false;
+ }
+
+ /* 3. Let entry be the last (most recently added) element in the list
+ of active formatting elements. */
+ $entry = end($this->a_formatting);
+
+ /* 2. If the last (most recently added) entry in the list of active
+ formatting elements is a marker, or if it is an element that is in the
+ stack of open elements, then there is nothing to reconstruct; stop this
+ algorithm. */
+ if($entry === self::MARKER || in_array($entry, $this->stack, true)) {
+ return false;
+ }
+
+ for($a = $formatting_elements - 1; $a >= 0; true) {
+ /* 4. If there are no entries before entry in the list of active
+ formatting elements, then jump to step 8. */
+ if($a === 0) {
+ $step_seven = false;
+ break;
+ }
+
+ /* 5. Let entry be the entry one earlier than entry in the list of
+ active formatting elements. */
+ $a--;
+ $entry = $this->a_formatting[$a];
+
+ /* 6. If entry is neither a marker nor an element that is also in
+ thetack of open elements, go to step 4. */
+ if($entry === self::MARKER || in_array($entry, $this->stack, true)) {
+ break;
+ }
+ }
+
+ while(true) {
+ /* 7. Let entry be the element one later than entry in the list of
+ active formatting elements. */
+ if(isset($step_seven) && $step_seven === true) {
+ $a++;
+ $entry = $this->a_formatting[$a];
+ }
+
+ /* 8. Perform a shallow clone of the element entry to obtain clone. */
+ $clone = $entry->cloneNode();
+
+ /* 9. Append clone to the current node and push it onto the stack
+ of open elements so that it is the new current node. */
+ $this->appendToRealParent($clone);
+ $this->stack[] = $clone;
+
+ /* 10. Replace the entry for entry in the list with an entry for
+ clone. */
+ $this->a_formatting[$a] = $clone;
+
+ /* 11. If the entry for clone in the list of active formatting
+ elements is not the last entry in the list, return to step 7. */
+ if(end($this->a_formatting) !== $clone) {
+ $step_seven = true;
+ } else {
+ break;
+ }
+ }
+ }
+
+ private function clearTheActiveFormattingElementsUpToTheLastMarker() {
+ /* When the steps below require the UA to clear the list of active
+ formatting elements up to the last marker, the UA must perform the
+ following steps: */
+
+ while(true) {
+ /* 1. Let entry be the last (most recently added) entry in the list
+ of active formatting elements. */
+ $entry = end($this->a_formatting);
+
+ /* 2. Remove entry from the list of active formatting elements. */
+ array_pop($this->a_formatting);
+
+ /* 3. If entry was a marker, then stop the algorithm at this point.
+ The list has been cleared up to the last marker. */
+ if($entry === self::MARKER) {
+ break;
+ }
+ }
+ }
+
+ private function generateImpliedEndTags($exclude = array()) {
+ /* When the steps below require the UA to generate implied end tags,
+ * then, while the current node is a dc element, a dd element, a ds
+ * element, a dt element, an li element, an option element, an optgroup
+ * element, a p element, an rp element, or an rt element, the UA must
+ * pop the current node off the stack of open elements. */
+ $node = end($this->stack);
+ $elements = array_diff(array('dc', 'dd', 'ds', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude);
+
+ while(in_array(end($this->stack)->tagName, $elements)) {
+ array_pop($this->stack);
+ }
+ }
+
+ private function getElementCategory($node) {
+ if (!is_object($node)) debug_print_backtrace();
+ $name = $node->tagName;
+ if(in_array($name, $this->special))
+ return self::SPECIAL;
+
+ elseif(in_array($name, $this->scoping))
+ return self::SCOPING;
+
+ elseif(in_array($name, $this->formatting))
+ return self::FORMATTING;
+
+ else
+ return self::PHRASING;
+ }
+
+ private function clearStackToTableContext($elements) {
+ /* When the steps above require the UA to clear the stack back to a
+ table context, it means that the UA must, while the current node is not
+ a table element or an html element, pop elements from the stack of open
+ elements. */
+ while(true) {
+ $name = end($this->stack)->tagName;
+
+ if(in_array($name, $elements)) {
+ break;
+ } else {
+ array_pop($this->stack);
+ }
+ }
+ }
+
+ private function resetInsertionMode($context = null) {
+ /* 1. Let last be false. */
+ $last = false;
+ $leng = count($this->stack);
+
+ for($n = $leng - 1; $n >= 0; $n--) {
+ /* 2. Let node be the last node in the stack of open elements. */
+ $node = $this->stack[$n];
+
+ /* 3. If node is the first node in the stack of open elements, then
+ * set last to true and set node to the context element. (fragment
+ * case) */
+ if($this->stack[0]->isSameNode($node)) {
+ $last = true;
+ $node = $context;
+ }
+
+ /* 4. If node is a select element, then switch the insertion mode to
+ "in select" and abort these steps. (fragment case) */
+ if($node->tagName === 'select') {
+ $this->mode = self::IN_SELECT;
+ break;
+
+ /* 5. If node is a td or th element, then switch the insertion mode
+ to "in cell" and abort these steps. */
+ } elseif($node->tagName === 'td' || $node->nodeName === 'th') {
+ $this->mode = self::IN_CELL;
+ break;
+
+ /* 6. If node is a tr element, then switch the insertion mode to
+ "in row" and abort these steps. */
+ } elseif($node->tagName === 'tr') {
+ $this->mode = self::IN_ROW;
+ break;
+
+ /* 7. If node is a tbody, thead, or tfoot element, then switch the
+ insertion mode to "in table body" and abort these steps. */
+ } elseif(in_array($node->tagName, array('tbody', 'thead', 'tfoot'))) {
+ $this->mode = self::IN_TABLE_BODY;
+ break;
+
+ /* 8. If node is a caption element, then switch the insertion mode
+ to "in caption" and abort these steps. */
+ } elseif($node->tagName === 'caption') {
+ $this->mode = self::IN_CAPTION;
+ break;
+
+ /* 9. If node is a colgroup element, then switch the insertion mode
+ to "in column group" and abort these steps. (innerHTML case) */
+ } elseif($node->tagName === 'colgroup') {
+ $this->mode = self::IN_COLUMN_GROUP;
+ break;
+
+ /* 10. If node is a table element, then switch the insertion mode
+ to "in table" and abort these steps. */
+ } elseif($node->tagName === 'table') {
+ $this->mode = self::IN_TABLE;
+ break;
+
+ /* 11. If node is an element from the MathML namespace or the SVG
+ * namespace, then switch the insertion mode to "in foreign
+ * content", let the secondary insertion mode be "in body", and
+ * abort these steps. */
+ } elseif($node->namespaceURI === self::NS_SVG ||
+ $node->namespaceURI === self::NS_MATHML) {
+ $this->mode = self::IN_FOREIGN_CONTENT;
+ $this->secondary_mode = self::IN_BODY;
+ break;
+
+ /* 12. If node is a head element, then switch the insertion mode
+ to "in body" ("in body"! not "in head"!) and abort these steps.
+ (fragment case) */
+ } elseif($node->tagName === 'head') {
+ $this->mode = self::IN_BODY;
+ break;
+
+ /* 13. If node is a body element, then switch the insertion mode to
+ "in body" and abort these steps. */
+ } elseif($node->tagName === 'body') {
+ $this->mode = self::IN_BODY;
+ break;
+
+ /* 14. If node is a frameset element, then switch the insertion
+ mode to "in frameset" and abort these steps. (fragment case) */
+ } elseif($node->tagName === 'frameset') {
+ $this->mode = self::IN_FRAMESET;
+ break;
+
+ /* 15. If node is an html element, then: if the head element
+ pointer is null, switch the insertion mode to "before head",
+ otherwise, switch the insertion mode to "after head". In either
+ case, abort these steps. (fragment case) */
+ } elseif($node->tagName === 'html') {
+ $this->mode = ($this->head_pointer === null)
+ ? self::BEFORE_HEAD
+ : self::AFTER_HEAD;
+
+ break;
+
+ /* 16. If last is true, then set the insertion mode to "in body"
+ and abort these steps. (fragment case) */
+ } elseif($last) {
+ $this->mode = self::IN_BODY;
+ break;
+ }
+ }
+ }
+
+ private function closeCell() {
+ /* If the stack of open elements has a td or th element in table scope,
+ then act as if an end tag token with that tag name had been seen. */
+ foreach(array('td', 'th') as $cell) {
+ if($this->elementInScope($cell, self::SCOPE_TABLE)) {
+ $this->emitToken(array(
+ 'name' => $cell,
+ 'type' => HTML5_Tokenizer::ENDTAG
+ ));
+
+ break;
+ }
+ }
+ }
+
+ private function processWithRulesFor($token, $mode) {
+ /* "using the rules for the m insertion mode", where m is one of these
+ * modes, the user agent must use the rules described under the m
+ * insertion mode's section, but must leave the insertion mode
+ * unchanged unless the rules in m themselves switch the insertion mode
+ * to a new value. */
+ return $this->emitToken($token, $mode);
+ }
+
+ private function insertCDATAElement($token) {
+ $this->insertElement($token);
+ $this->original_mode = $this->mode;
+ $this->mode = self::IN_CDATA_RCDATA;
+ $this->content_model = HTML5_Tokenizer::CDATA;
+ }
+
+ private function insertRCDATAElement($token) {
+ $this->insertElement($token);
+ $this->original_mode = $this->mode;
+ $this->mode = self::IN_CDATA_RCDATA;
+ $this->content_model = HTML5_Tokenizer::RCDATA;
+ }
+
+ private function getAttr($token, $key) {
+ if (!isset($token['attr'])) return false;
+ $ret = false;
+ foreach ($token['attr'] as $keypair) {
+ if ($keypair['name'] === $key) $ret = $keypair['value'];
+ }
+ return $ret;
+ }
+
+ private function getCurrentTable() {
+ /* The current table is the last table element in the stack of open
+ * elements, if there is one. If there is no table element in the stack
+ * of open elements (fragment case), then the current table is the
+ * first element in the stack of open elements (the html element). */
+ for ($i = count($this->stack) - 1; $i >= 0; $i--) {
+ if ($this->stack[$i]->tagName === 'table') {
+ return $this->stack[$i];
+ }
+ }
+ return $this->stack[0];
+ }
+
+ private function getFosterParent() {
+ /* The foster parent element is the parent element of the last
+ table element in the stack of open elements, if there is a
+ table element and it has such a parent element. If there is no
+ table element in the stack of open elements (innerHTML case),
+ then the foster parent element is the first element in the
+ stack of open elements (the html element). Otherwise, if there
+ is a table element in the stack of open elements, but the last
+ table element in the stack of open elements has no parent, or
+ its parent node is not an element, then the foster parent
+ element is the element before the last table element in the
+ stack of open elements. */
+ for($n = count($this->stack) - 1; $n >= 0; $n--) {
+ if($this->stack[$n]->tagName === 'table') {
+ $table = $this->stack[$n];
+ break;
+ }
+ }
+
+ if(isset($table) && $table->parentNode !== null) {
+ return $table->parentNode;
+
+ } elseif(!isset($table)) {
+ return $this->stack[0];
+
+ } elseif(isset($table) && ($table->parentNode === null ||
+ $table->parentNode->nodeType !== XML_ELEMENT_NODE)) {
+ return $this->stack[$n - 1];
+ }
+ }
+
+ public function fosterParent($node) {
+ $foster_parent = $this->getFosterParent();
+ $table = $this->getCurrentTable(); // almost equivalent to last table element, except it can be html
+ /* When a node node is to be foster parented, the node node must be
+ * be inserted into the foster parent element. */
+ /* If the foster parent element is the parent element of the last table
+ * element in the stack of open elements, then node must be inserted
+ * immediately before the last table element in the stack of open
+ * elements in the foster parent element; otherwise, node must be
+ * appended to the foster parent element. */
+ if ($table->tagName === 'table' && $table->parentNode->isSameNode($foster_parent)) {
+ $foster_parent->insertBefore($node, $table);
+ } else {
+ $foster_parent->appendChild($node);
+ }
+ }
+
+ /**
+ * For debugging, prints the stack
+ */
+ private function printStack() {
+ $names = array();
+ foreach ($this->stack as $i => $element) {
+ $names[] = $element->tagName;
+ }
+ echo " -> stack [" . implode(', ', $names) . "]\n";
+ }
+
+ /**
+ * For debugging, prints active formatting elements
+ */
+ private function printActiveFormattingElements() {
+ if (!$this->a_formatting) return;
+ $names = array();
+ foreach ($this->a_formatting as $node) {
+ if ($node === self::MARKER) $names[] = 'MARKER';
+ else $names[] = $node->tagName;
+ }
+ echo " -> active formatting [" . implode(', ', $names) . "]\n";
+ }
+
+ public function currentTableIsTainted() {
+ return !empty($this->getCurrentTable()->tainted);
+ }
+
+ /**
+ * Sets up the tree constructor for building a fragment.
+ */
+ public function setupContext($context = null) {
+ $this->fragment = true;
+ if ($context) {
+ $context = $this->dom->createElementNS(self::NS_HTML, $context);
+ /* 4.1. Set the HTML parser's tokenization stage's content model
+ * flag according to the context element, as follows: */
+ switch ($context->tagName) {
+ case 'title': case 'textarea':
+ $this->content_model = HTML5_Tokenizer::RCDATA;
+ break;
+ case 'style': case 'script': case 'xmp': case 'iframe':
+ case 'noembed': case 'noframes':
+ $this->content_model = HTML5_Tokenizer::CDATA;
+ break;
+ case 'noscript':
+ // XSCRIPT: assuming scripting is enabled
+ $this->content_model = HTML5_Tokenizer::CDATA;
+ break;
+ case 'plaintext':
+ $this->content_model = HTML5_Tokenizer::PLAINTEXT;
+ break;
+ }
+ /* 4.2. Let root be a new html element with no attributes. */
+ $root = $this->dom->createElementNS(self::NS_HTML, 'html');
+ $this->root = $root;
+ /* 4.3 Append the element root to the Document node created above. */
+ $this->dom->appendChild($root);
+ /* 4.4 Set up the parser's stack of open elements so that it
+ * contains just the single element root. */
+ $this->stack = array($root);
+ /* 4.5 Reset the parser's insertion mode appropriately. */
+ $this->resetInsertionMode($context);
+ /* 4.6 Set the parser's form element pointer to the nearest node
+ * to the context element that is a form element (going straight up
+ * the ancestor chain, and including the element itself, if it is a
+ * form element), or, if there is no such form element, to null. */
+ $node = $context;
+ do {
+ if ($node->tagName === 'form') {
+ $this->form_pointer = $node;
+ break;
+ }
+ } while ($node = $node->parentNode);
+ }
+ }
+
+ public function adjustMathMLAttributes($token) {
+ foreach ($token['attr'] as &$kp) {
+ if ($kp['name'] === 'definitionurl') {
+ $kp['name'] = 'definitionURL';
+ }
+ }
+ return $token;
+ }
+
+ public function adjustSVGAttributes($token) {
+ static $lookup = array(
+ 'attributename' => 'attributeName',
+ 'attributetype' => 'attributeType',
+ 'basefrequency' => 'baseFrequency',
+ 'baseprofile' => 'baseProfile',
+ 'calcmode' => 'calcMode',
+ 'clippathunits' => 'clipPathUnits',
+ 'contentscripttype' => 'contentScriptType',
+ 'contentstyletype' => 'contentStyleType',
+ 'diffuseconstant' => 'diffuseConstant',
+ 'edgemode' => 'edgeMode',
+ 'externalresourcesrequired' => 'externalResourcesRequired',
+ 'filterres' => 'filterRes',
+ 'filterunits' => 'filterUnits',
+ 'glyphref' => 'glyphRef',
+ 'gradienttransform' => 'gradientTransform',
+ 'gradientunits' => 'gradientUnits',
+ 'kernelmatrix' => 'kernelMatrix',
+ 'kernelunitlength' => 'kernelUnitLength',
+ 'keypoints' => 'keyPoints',
+ 'keysplines' => 'keySplines',
+ 'keytimes' => 'keyTimes',
+ 'lengthadjust' => 'lengthAdjust',
+ 'limitingconeangle' => 'limitingConeAngle',
+ 'markerheight' => 'markerHeight',
+ 'markerunits' => 'markerUnits',
+ 'markerwidth' => 'markerWidth',
+ 'maskcontentunits' => 'maskContentUnits',
+ 'maskunits' => 'maskUnits',
+ 'numoctaves' => 'numOctaves',
+ 'pathlength' => 'pathLength',
+ 'patterncontentunits' => 'patternContentUnits',
+ 'patterntransform' => 'patternTransform',
+ 'patternunits' => 'patternUnits',
+ 'pointsatx' => 'pointsAtX',
+ 'pointsaty' => 'pointsAtY',
+ 'pointsatz' => 'pointsAtZ',
+ 'preservealpha' => 'preserveAlpha',
+ 'preserveaspectratio' => 'preserveAspectRatio',
+ 'primitiveunits' => 'primitiveUnits',
+ 'refx' => 'refX',
+ 'refy' => 'refY',
+ 'repeatcount' => 'repeatCount',
+ 'repeatdur' => 'repeatDur',
+ 'requiredextensions' => 'requiredExtensions',
+ 'requiredfeatures' => 'requiredFeatures',
+ 'specularconstant' => 'specularConstant',
+ 'specularexponent' => 'specularExponent',
+ 'spreadmethod' => 'spreadMethod',
+ 'startoffset' => 'startOffset',
+ 'stddeviation' => 'stdDeviation',
+ 'stitchtiles' => 'stitchTiles',
+ 'surfacescale' => 'surfaceScale',
+ 'systemlanguage' => 'systemLanguage',
+ 'tablevalues' => 'tableValues',
+ 'targetx' => 'targetX',
+ 'targety' => 'targetY',
+ 'textlength' => 'textLength',
+ 'viewbox' => 'viewBox',
+ 'viewtarget' => 'viewTarget',
+ 'xchannelselector' => 'xChannelSelector',
+ 'ychannelselector' => 'yChannelSelector',
+ 'zoomandpan' => 'zoomAndPan',
+ );
+ foreach ($token['attr'] as &$kp) {
+ if (isset($lookup[$kp['name']])) {
+ $kp['name'] = $lookup[$kp['name']];
+ }
+ }
+ return $token;
+ }
+
+ public function adjustForeignAttributes($token) {
+ static $lookup = array(
+ 'xlink:actuate' => array('xlink', 'actuate', self::NS_XLINK),
+ 'xlink:arcrole' => array('xlink', 'arcrole', self::NS_XLINK),
+ 'xlink:href' => array('xlink', 'href', self::NS_XLINK),
+ 'xlink:role' => array('xlink', 'role', self::NS_XLINK),
+ 'xlink:show' => array('xlink', 'show', self::NS_XLINK),
+ 'xlink:title' => array('xlink', 'title', self::NS_XLINK),
+ 'xlink:type' => array('xlink', 'type', self::NS_XLINK),
+ 'xml:base' => array('xml', 'base', self::NS_XML),
+ 'xml:lang' => array('xml', 'lang', self::NS_XML),
+ 'xml:space' => array('xml', 'space', self::NS_XML),
+ 'xmlns' => array(null, 'xmlns', self::NS_XMLNS),
+ 'xmlns:xlink' => array('xmlns', 'xlink', self::NS_XMLNS),
+ );
+ foreach ($token['attr'] as &$kp) {
+ if (isset($lookup[$kp['name']])) {
+ $kp['name'] = $lookup[$kp['name']];
+ }
+ }
+ return $token;
+ }
+
+ public function insertForeignElement($token, $namespaceURI) {
+ $el = $this->dom->createElementNS($namespaceURI, $token['name']);
+ if (!empty($token['attr'])) {
+ foreach ($token['attr'] as $kp) {
+ $attr = $kp['name'];
+ if (is_array($attr)) {
+ $ns = $attr[2];
+ $attr = $attr[1];
+ } else {
+ $ns = self::NS_HTML;
+ }
+ if (!$el->hasAttributeNS($ns, $attr)) {
+ // XSKETCHY: work around godawful libxml bug
+ if ($ns === self::NS_XLINK) {
+ $el->setAttribute('xlink:'.$attr, $kp['value']);
+ } elseif ($ns === self::NS_HTML) {
+ // Another godawful libxml bug
+ $el->setAttribute($attr, $kp['value']);
+ } else {
+ $el->setAttributeNS($ns, $attr, $kp['value']);
+ }
+ }
+ }
+ }
+ $this->appendToRealParent($el);
+ $this->stack[] = $el;
+ // XERROR: see below
+ /* If the newly created element has an xmlns attribute in the XMLNS
+ * namespace whose value is not exactly the same as the element's
+ * namespace, that is a parse error. Similarly, if the newly created
+ * element has an xmlns:xlink attribute in the XMLNS namespace whose
+ * value is not the XLink Namespace, that is a parse error. */
+ }
+
+ public function save() {
+ $this->dom->normalize();
+ if (!$this->fragment) {
+ return $this->dom;
+ } else {
+ if ($this->root) {
+ return $this->root->childNodes;
+ } else {
+ return $this->dom->childNodes;
+ }
+ }
+ }
+}
+
diff --git a/libraries/html5/named-character-references.ser b/libraries/html5/named-character-references.ser
new file mode 100644
index 0000000..e3ae050
--- /dev/null
+++ b/libraries/html5/named-character-references.ser
@@ -0,0 +1 @@
+a:52:{s:1:"A";a:16:{s:1:"E";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:198;}s:9:"codepoint";i:198;}}}}s:1:"M";a:1:{s:1:"P";a:2:{s:1:";";a:1:{s:9:"codepoint";i:38;}s:9:"codepoint";i:38;}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:193;}s:9:"codepoint";i:193;}}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:258;}}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:194;}s:9:"codepoint";i:194;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1040;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120068;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:192;}s:9:"codepoint";i:192;}}}}}s:1:"l";a:1:{s:1:"p";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:913;}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:256;}}}}}s:1:"n";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10835;}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:260;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120120;}}}}s:1:"p";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"y";a:1:{s:1:"F";a:1:{s:1:"u";a:1:{s:1:"n";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8289;}}}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:197;}s:9:"codepoint";i:197;}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119964;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8788;}}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:195;}s:9:"codepoint";i:195;}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:196;}s:9:"codepoint";i:196;}}}}s:1:"B";a:8:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"s";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}}}}}s:1:"r";a:2:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10983;}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8966;}}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1041;}}}s:1:"e";a:3:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8757;}}}}}}s:1:"r";a:1:{s:1:"n";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8492;}}}}}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:914;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120069;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120121;}}}}s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:728;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8492;}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8782;}}}}}}}s:1:"C";a:14:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1063;}}}}s:1:"O";a:1:{s:1:"P";a:1:{s:1:"Y";a:2:{s:1:";";a:1:{s:9:"codepoint";i:169;}s:9:"codepoint";i:169;}}}s:1:"a";a:3:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:262;}}}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8914;}s:1:"i";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"D";a:1:{s:1:"i";a:1:{s:1:"f";a:1:{s:1:"f";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8517;}}}}}}}}}}}}}}}}}}}s:1:"y";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"y";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8493;}}}}}}}s:1:"c";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:268;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:199;}s:9:"codepoint";i:199;}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:264;}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8752;}}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:266;}}}}s:1:"e";a:2:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:184;}}}}}}s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:183;}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8493;}}}s:1:"h";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:935;}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"l";a:1:{s:1:"e";a:4:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8857;}}}}s:1:"M";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8854;}}}}}}s:1:"P";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8853;}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8855;}}}}}}}}}}}s:1:"l";a:1:{s:1:"o";a:2:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"w";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"C";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8754;}}}}}}}}}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"C";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"l";a:1:{s:1:"y";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"Q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8221;}}}}}}}}}}}}s:1:"Q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8217;}}}}}}}}}}}}}}}s:1:"o";a:4:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8759;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10868;}}}}}s:1:"n";a:3:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8801;}}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8751;}}}}s:1:"t";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8750;}}}}}}}}}}}}}}s:1:"p";a:2:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8450;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:"u";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8720;}}}}}}}}s:1:"u";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"C";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"w";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"C";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8755;}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10799;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119966;}}}}s:1:"u";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8915;}s:1:"C";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8781;}}}}}}}s:1:"D";a:11:{s:1:"D";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8517;}s:1:"o";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"h";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10513;}}}}}}}}s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1026;}}}}s:1:"S";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1029;}}}}s:1:"Z";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1039;}}}}s:1:"a";a:3:{s:1:"g";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8225;}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8609;}}}s:1:"s";a:1:{s:1:"h";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10980;}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:270;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1044;}}}s:1:"e";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8711;}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:916;}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120071;}}}s:1:"i";a:2:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:4:{s:1:"A";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:180;}}}}}}s:1:"D";a:1:{s:1:"o";a:2:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:729;}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"A";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:733;}}}}}}}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:96;}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:732;}}}}}}}}}}}}}}s:1:"m";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8900;}}}}}}s:1:"f";a:1:{s:1:"f";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8518;}}}}}}}}}}}}}s:1:"o";a:4:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120123;}}}s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:168;}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8412;}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8784;}}}}}}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:6:{s:1:"C";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8751;}}}}}}}}}}}}}}}}s:1:"D";a:1:{s:1:"o";a:2:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:168;}}s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8659;}}}}}}}}}}s:1:"L";a:2:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:3:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8656;}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8660;}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10980;}}}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:2:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10232;}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10234;}}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10233;}}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8658;}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8872;}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8657;}}}}}}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8661;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8741;}}}}}}}}}}}}}}}}s:1:"w";a:1:{s:1:"n";a:6:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8595;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10515;}}}}s:1:"U";a:1:{s:1:"p";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8693;}}}}}}}}}}}}}s:1:"B";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:785;}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:3:{s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10576;}}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10590;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8637;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10582;}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10591;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8641;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10583;}}}}}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8868;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8615;}}}}}}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8659;}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119967;}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:272;}}}}}}}s:1:"E";a:16:{s:1:"N";a:1:{s:1:"G";a:1:{s:1:";";a:1:{s:9:"codepoint";i:330;}}}s:1:"T";a:1:{s:1:"H";a:2:{s:1:";";a:1:{s:9:"codepoint";i:208;}s:9:"codepoint";i:208;}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:201;}s:9:"codepoint";i:201;}}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:282;}}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:202;}s:9:"codepoint";i:202;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1069;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:278;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120072;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:200;}s:9:"codepoint";i:200;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8712;}}}}}}}s:1:"m";a:2:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:274;}}}}s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:2:{s:1:"S";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"S";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9723;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"y";a:1:{s:1:"S";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"S";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9643;}}}}}}}}}}}}}}}}}}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:280;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120124;}}}}s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:917;}}}}}}}s:1:"q";a:1:{s:1:"u";a:2:{s:1:"a";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10869;}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8770;}}}}}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8652;}}}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8496;}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10867;}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:919;}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:203;}s:9:"codepoint";i:203;}}}s:1:"x";a:2:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8707;}}}}}s:1:"p";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8519;}}}}}}}}}}}}}s:1:"F";a:5:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1060;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120073;}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"d";a:2:{s:1:"S";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"S";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9724;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"y";a:1:{s:1:"S";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"S";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9642;}}}}}}}}}}}}}}}}}}}}}s:1:"o";a:3:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120125;}}}s:1:"r";a:1:{s:1:"A";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8704;}}}}}s:1:"u";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8497;}}}}}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8497;}}}}}s:1:"G";a:12:{s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1027;}}}}s:1:"T";a:2:{s:1:";";a:1:{s:9:"codepoint";i:62;}s:9:"codepoint";i:62;}s:1:"a";a:1:{s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:2:{s:1:";";a:1:{s:9:"codepoint";i:915;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:988;}}}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:286;}}}}}}s:1:"c";a:3:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:290;}}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:284;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1043;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:288;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120074;}}}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8921;}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120126;}}}}s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:6:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8805;}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8923;}}}}}}}}}}s:1:"F";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8807;}}}}}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10914;}}}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8823;}}}}}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10878;}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8819;}}}}}}}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119970;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8811;}}}s:1:"H";a:8:{s:1:"A";a:1:{s:1:"R";a:1:{s:1:"D";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1066;}}}}}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:711;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:94;}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:292;}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8460;}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"b";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8459;}}}}}}}}}}}}s:1:"o";a:2:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8461;}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"z";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"L";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9472;}}}}}}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8459;}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:294;}}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"p";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"H";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8782;}}}}}}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8783;}}}}}}}}}}s:1:"I";a:14:{s:1:"E";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1045;}}}}s:1:"J";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:306;}}}}}s:1:"O";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1025;}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:205;}s:9:"codepoint";i:205;}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:206;}s:9:"codepoint";i:206;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1048;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:304;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8465;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:204;}s:9:"codepoint";i:204;}}}}}s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8465;}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:298;}}}s:1:"g";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"y";a:1:{s:1:"I";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8520;}}}}}}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8658;}}}}}}}s:1:"n";a:2:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8748;}s:1:"e";a:2:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8747;}}}}}s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8898;}}}}}}}}}}}s:1:"v";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:"C";a:1:{s:1:"o";a:1:{s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8291;}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8290;}}}}}}}}}}}}}}s:1:"o";a:3:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:302;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120128;}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:921;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8464;}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:296;}}}}}}s:1:"u";a:2:{s:1:"k";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1030;}}}}s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:207;}s:9:"codepoint";i:207;}}}}s:1:"J";a:5:{s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:308;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1049;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120077;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120129;}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119973;}}}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1032;}}}}}}s:1:"u";a:1:{s:1:"k";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1028;}}}}}}s:1:"K";a:7:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1061;}}}}s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1036;}}}}s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:922;}}}}}s:1:"c";a:2:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:310;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1050;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120078;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120130;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119974;}}}}}s:1:"L";a:11:{s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1033;}}}}s:1:"T";a:2:{s:1:";";a:1:{s:9:"codepoint";i:60;}s:9:"codepoint";i:60;}s:1:"a";a:5:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:313;}}}}}s:1:"m";a:1:{s:1:"b";a:1:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:923;}}}}}s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10218;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8466;}}}}}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8606;}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:317;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:315;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1051;}}}s:1:"e";a:2:{s:1:"f";a:1:{s:1:"t";a:10:{s:1:"A";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10216;}}}}}}}}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8592;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8676;}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8646;}}}}}}}}}}}}}}}}s:1:"C";a:1:{s:1:"e";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8968;}}}}}}}}s:1:"D";a:1:{s:1:"o";a:2:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10214;}}}}}}}}}}}}s:1:"w";a:1:{s:1:"n";a:2:{s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10593;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8643;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10585;}}}}}}}}}}}}}}s:1:"F";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8970;}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8596;}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10574;}}}}}}}}}}}}s:1:"T";a:2:{s:1:"e";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8867;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8612;}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10586;}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8882;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10703;}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8884;}}}}}}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:3:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10577;}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10592;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8639;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10584;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8636;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10578;}}}}}}}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8656;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8660;}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"s";a:6:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8922;}}}}}}}}}}}}}s:1:"F";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8806;}}}}}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8822;}}}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10913;}}}}}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10877;}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8818;}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120079;}}}s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8920;}s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8666;}}}}}}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:319;}}}}}}s:1:"o";a:3:{s:1:"n";a:1:{s:1:"g";a:4:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10229;}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10231;}}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10230;}}}}}}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10232;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10234;}}}}}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10233;}}}}}}}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120131;}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"r";a:2:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8601;}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8600;}}}}}}}}}}}}}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8466;}}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8624;}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:321;}}}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8810;}}}s:1:"M";a:8:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10501;}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1052;}}}s:1:"e";a:2:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8287;}}}}}}}}}}s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8499;}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120080;}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"P";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8723;}}}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120132;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8499;}}}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:924;}}}s:1:"N";a:9:{s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1034;}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:323;}}}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:327;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:325;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1053;}}}s:1:"e";a:3:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"v";a:1:{s:1:"e";a:3:{s:1:"M";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}}}}}s:1:"T";a:1:{s:1:"h";a:1:{s:1:"i";a:2:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}s:1:"n";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"y";a:1:{s:1:"T";a:1:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"d";a:2:{s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8811;}}}}}}}}}}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8810;}}}}}}}}}}}}}s:1:"w";a:1:{s:1:"L";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10;}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120081;}}}s:1:"o";a:4:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8288;}}}}}}s:1:"n";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"k";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:160;}}}}}}}}}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8469;}}}s:1:"t";a:11:{s:1:";";a:1:{s:9:"codepoint";i:10988;}s:1:"C";a:2:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8802;}}}}}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:"C";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8813;}}}}}}}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8742;}}}}}}}}}}}}}}}}}}s:1:"E";a:3:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8713;}}}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8800;}}}}}s:1:"x";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8708;}}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8815;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8817;}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8825;}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8821;}}}}}}}}}}}}}s:1:"L";a:1:{s:1:"e";a:2:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"T";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8938;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8940;}}}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8814;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8816;}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8824;}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8820;}}}}}}}}}}s:1:"P";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8832;}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8928;}}}}}}}}}}}}}}}}}}}s:1:"R";a:2:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"E";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8716;}}}}}}}}}}}}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"T";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8939;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8941;}}}}}}}}}}}}}}}}}}}s:1:"S";a:2:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"S";a:1:{s:1:"u";a:2:{s:1:"b";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8930;}}}}}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8931;}}}}}}}}}}}}}}}}}}}s:1:"u";a:3:{s:1:"b";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8840;}}}}}}}}}}s:1:"c";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8833;}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8929;}}}}}}}}}}}}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8841;}}}}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8769;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8772;}}}}}}s:1:"F";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8775;}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8777;}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8740;}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119977;}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:209;}s:9:"codepoint";i:209;}}}}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:925;}}}s:1:"O";a:14:{s:1:"E";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:338;}}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:211;}s:9:"codepoint";i:211;}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:212;}s:9:"codepoint";i:212;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1054;}}}s:1:"d";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:336;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120082;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:210;}s:9:"codepoint";i:210;}}}}}s:1:"m";a:3:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:332;}}}}s:1:"e";a:1:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:937;}}}}s:1:"i";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:927;}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120134;}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"C";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"l";a:1:{s:1:"y";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"Q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8220;}}}}}}}}}}}}s:1:"Q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8216;}}}}}}}}}}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10836;}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119978;}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:216;}s:9:"codepoint";i:216;}}}}}s:1:"t";a:1:{s:1:"i";a:2:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:213;}s:9:"codepoint";i:213;}}}s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10807;}}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:214;}s:9:"codepoint";i:214;}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"r";a:2:{s:1:"B";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:175;}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9182;}}s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9140;}}}}}}}}s:1:"P";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9180;}}}}}}}}}}}}}}}}s:1:"P";a:9:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8706;}}}}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1055;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120083;}}}s:1:"h";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:934;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:928;}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"M";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:177;}}}}}}}}}s:1:"o";a:2:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8460;}}}}}}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8473;}}}}s:1:"r";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10939;}s:1:"e";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8826;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10927;}}}}}}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8828;}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8830;}}}}}}}}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8243;}}}}s:1:"o";a:2:{s:1:"d";a:1:{s:1:"u";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8719;}}}}}s:1:"p";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8759;}s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8733;}}}}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119979;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:936;}}}}s:1:"Q";a:4:{s:1:"U";a:1:{s:1:"O";a:1:{s:1:"T";a:2:{s:1:";";a:1:{s:9:"codepoint";i:34;}s:9:"codepoint";i:34;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120084;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8474;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119980;}}}}}s:1:"R";a:12:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10512;}}}}}s:1:"E";a:1:{s:1:"G";a:2:{s:1:";";a:1:{s:9:"codepoint";i:174;}s:9:"codepoint";i:174;}}s:1:"a";a:3:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:340;}}}}}s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10219;}}}s:1:"r";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8608;}s:1:"t";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10518;}}}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:344;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:342;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1056;}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8476;}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:2:{s:1:"E";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8715;}}}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8651;}}}}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10607;}}}}}}}}}}}}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8476;}}}s:1:"h";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:929;}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:8:{s:1:"A";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10217;}}}}}}}}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8594;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8677;}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8644;}}}}}}}}}}}}}}}s:1:"C";a:1:{s:1:"e";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8969;}}}}}}}}s:1:"D";a:1:{s:1:"o";a:2:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10215;}}}}}}}}}}}}s:1:"w";a:1:{s:1:"n";a:2:{s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10589;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8642;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10581;}}}}}}}}}}}}}}s:1:"F";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8971;}}}}}}s:1:"T";a:2:{s:1:"e";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8866;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8614;}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10587;}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8883;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10704;}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8885;}}}}}}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:3:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10575;}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10588;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8638;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10580;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8640;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10579;}}}}}}}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8658;}}}}}}}}}}s:1:"o";a:2:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8477;}}}s:1:"u";a:1:{s:1:"n";a:1:{s:1:"d";a:1:{s:1:"I";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10608;}}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8667;}}}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8475;}}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8625;}}}s:1:"u";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"D";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"y";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10740;}}}}}}}}}}}}s:1:"S";a:13:{s:1:"H";a:2:{s:1:"C";a:1:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1065;}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1064;}}}}s:1:"O";a:1:{s:1:"F";a:1:{s:1:"T";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1068;}}}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:346;}}}}}}s:1:"c";a:5:{s:1:";";a:1:{s:9:"codepoint";i:10940;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:352;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:350;}}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:348;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1057;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120086;}}}s:1:"h";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"t";a:4:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8595;}}}}}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8592;}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8594;}}}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8593;}}}}}}}}}}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:931;}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"C";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8728;}}}}}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120138;}}}}s:1:"q";a:2:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8730;}}}s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:9633;}s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8851;}}}}}}}}}}}}}s:1:"S";a:1:{s:1:"u";a:2:{s:1:"b";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8847;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8849;}}}}}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8848;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8850;}}}}}}}}}}}}}}s:1:"U";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8852;}}}}}}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119982;}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8902;}}}}s:1:"u";a:4:{s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8912;}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8912;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8838;}}}}}}}}}}s:1:"c";a:2:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8827;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10928;}}}}}}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8829;}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8831;}}}}}}}}}}}s:1:"h";a:1:{s:1:"T";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8715;}}}}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8721;}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8913;}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8835;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8839;}}}}}}}}}}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8913;}}}}}}}s:1:"T";a:11:{s:1:"H";a:1:{s:1:"O";a:1:{s:1:"R";a:1:{s:1:"N";a:2:{s:1:";";a:1:{s:9:"codepoint";i:222;}s:9:"codepoint";i:222;}}}}s:1:"R";a:1:{s:1:"A";a:1:{s:1:"D";a:1:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8482;}}}}}s:1:"S";a:2:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1035;}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1062;}}}}s:1:"a";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:932;}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:356;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:354;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1058;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120087;}}}s:1:"h";a:2:{s:1:"e";a:2:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8756;}}}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:920;}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8201;}}}}}}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8764;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8771;}}}}}}s:1:"F";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8773;}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8776;}}}}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120139;}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8411;}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119983;}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:358;}}}}}}}s:1:"U";a:14:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:218;}s:9:"codepoint";i:218;}}}}s:1:"r";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8607;}s:1:"o";a:1:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10569;}}}}}}}}s:1:"b";a:1:{s:1:"r";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1038;}}}s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:364;}}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:219;}s:9:"codepoint";i:219;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1059;}}}s:1:"d";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:368;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120088;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:217;}s:9:"codepoint";i:217;}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:362;}}}}}s:1:"n";a:2:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"r";a:2:{s:1:"B";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:818;}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9183;}}s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9141;}}}}}}}}s:1:"P";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9181;}}}}}}}}}}}}}}}s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8899;}s:1:"P";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8846;}}}}}}}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:370;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120140;}}}}s:1:"p";a:8:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8593;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10514;}}}}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8645;}}}}}}}}}}}}}}}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8597;}}}}}}}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10606;}}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8869;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8613;}}}}}}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8657;}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8661;}}}}}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"r";a:2:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8598;}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8599;}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:978;}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:933;}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:366;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119984;}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:360;}}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:220;}s:9:"codepoint";i:220;}}}}s:1:"V";a:9:{s:1:"D";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8875;}}}}}s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10987;}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1042;}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8873;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10982;}}}}}}s:1:"e";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8897;}}s:1:"r";a:3:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8214;}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8214;}s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:4:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8739;}}}}s:1:"L";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:124;}}}}}s:1:"S";a:1:{s:1:"e";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10072;}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8768;}}}}}}}}}}}s:1:"y";a:1:{s:1:"T";a:1:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8202;}}}}}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120089;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120141;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119985;}}}}s:1:"v";a:1:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8874;}}}}}}}s:1:"W";a:5:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:372;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8896;}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120090;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120142;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119986;}}}}}s:1:"X";a:4:{s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120091;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:926;}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120143;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119987;}}}}}s:1:"Y";a:9:{s:1:"A";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1071;}}}}s:1:"I";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1031;}}}}s:1:"U";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1070;}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:221;}s:9:"codepoint";i:221;}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:374;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1067;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120092;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120144;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119988;}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:376;}}}}}s:1:"Z";a:8:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1046;}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:377;}}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:381;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1047;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:379;}}}}s:1:"e";a:2:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"W";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:918;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8488;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8484;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119989;}}}}}s:1:"a";a:16:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:225;}s:9:"codepoint";i:225;}}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:259;}}}}}}s:1:"c";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8766;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8767;}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:226;}s:9:"codepoint";i:226;}}}s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:180;}s:9:"codepoint";i:180;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1072;}}}s:1:"e";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:230;}s:9:"codepoint";i:230;}}}}s:1:"f";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8289;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120094;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:224;}s:9:"codepoint";i:224;}}}}}s:1:"l";a:2:{s:1:"e";a:2:{s:1:"f";a:1:{s:1:"s";a:1:{s:1:"y";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8501;}}}}}s:1:"p";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8501;}}}}s:1:"p";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:945;}}}}}s:1:"m";a:2:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:257;}}}s:1:"l";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10815;}}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:38;}s:9:"codepoint";i:38;}}s:1:"n";a:2:{s:1:"d";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8743;}s:1:"a";a:1:{s:1:"n";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10837;}}}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10844;}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10840;}}}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10842;}}}s:1:"g";a:7:{s:1:";";a:1:{s:9:"codepoint";i:8736;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10660;}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8736;}}}s:1:"m";a:1:{s:1:"s";a:1:{s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8737;}s:1:"a";a:8:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10664;}}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10665;}}s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10666;}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10667;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10668;}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10669;}}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10670;}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10671;}}}}}}s:1:"r";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8735;}s:1:"v";a:1:{s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8894;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10653;}}}}}}s:1:"s";a:2:{s:1:"p";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8738;}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8491;}}}s:1:"z";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9084;}}}}}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:261;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120146;}}}}s:1:"p";a:7:{s:1:";";a:1:{s:9:"codepoint";i:8776;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10864;}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10863;}}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8778;}}s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8779;}}}s:1:"o";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:39;}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8776;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8778;}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:229;}s:9:"codepoint";i:229;}}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119990;}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:42;}}s:1:"y";a:1:{s:1:"m";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8776;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8781;}}}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:227;}s:9:"codepoint";i:227;}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:228;}s:9:"codepoint";i:228;}}}s:1:"w";a:2:{s:1:"c";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8755;}}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10769;}}}}}}s:1:"b";a:16:{s:1:"N";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10989;}}}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"k";a:4:{s:1:"c";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8780;}}}}}s:1:"e";a:1:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1014;}}}}}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8245;}}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8765;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8909;}}}}}}}}s:1:"r";a:2:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8893;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8965;}s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8965;}}}}}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9141;}s:1:"t";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9142;}}}}}}}}s:1:"c";a:2:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8780;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1073;}}}s:1:"d";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8222;}}}}}s:1:"e";a:5:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"u";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8757;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8757;}}}}}}s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10672;}}}}}}s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1014;}}}}s:1:"r";a:1:{s:1:"n";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8492;}}}}}s:1:"t";a:3:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:946;}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8502;}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8812;}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120095;}}}s:1:"i";a:1:{s:1:"g";a:7:{s:1:"c";a:3:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8898;}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9711;}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8899;}}}}s:1:"o";a:3:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10752;}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10753;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10754;}}}}}}}s:1:"s";a:2:{s:1:"q";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10758;}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9733;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9661;}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9651;}}}}}}}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10756;}}}}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8897;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8896;}}}}}}}}s:1:"k";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10509;}}}}}}s:1:"l";a:3:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"k";a:3:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"z";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10731;}}}}}}}}s:1:"s";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9642;}}}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:9652;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9662;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9666;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9656;}}}}}}}}}}}}}}}}s:1:"n";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9251;}}}}s:1:"k";a:2:{i:1;a:2:{i:2;a:1:{s:1:";";a:1:{s:9:"codepoint";i:9618;}}i:4;a:1:{s:1:";";a:1:{s:9:"codepoint";i:9617;}}}i:3;a:1:{i:4;a:1:{s:1:";";a:1:{s:9:"codepoint";i:9619;}}}}s:1:"o";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9608;}}}}}s:1:"n";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8976;}}}}s:1:"o";a:4:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120147;}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8869;}s:1:"t";a:1:{s:1:"o";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8869;}}}}}s:1:"w";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8904;}}}}}s:1:"x";a:12:{s:1:"D";a:4:{s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9559;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9556;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9558;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9555;}}}s:1:"H";a:5:{s:1:";";a:1:{s:9:"codepoint";i:9552;}s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9574;}}s:1:"U";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9577;}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9572;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9575;}}}s:1:"U";a:4:{s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9565;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9562;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9564;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9561;}}}s:1:"V";a:7:{s:1:";";a:1:{s:9:"codepoint";i:9553;}s:1:"H";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9580;}}s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9571;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9568;}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9579;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9570;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9567;}}}s:1:"b";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10697;}}}}s:1:"d";a:4:{s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9557;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9554;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9488;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9484;}}}s:1:"h";a:5:{s:1:";";a:1:{s:9:"codepoint";i:9472;}s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9573;}}s:1:"U";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9576;}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9516;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9524;}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8863;}}}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8862;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8864;}}}}}}s:1:"u";a:4:{s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9563;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9560;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9496;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9492;}}}s:1:"v";a:7:{s:1:";";a:1:{s:9:"codepoint";i:9474;}s:1:"H";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9578;}}s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9569;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9566;}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9532;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9508;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9500;}}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8245;}}}}}}s:1:"r";a:2:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:728;}}}}s:1:"v";a:1:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:166;}s:9:"codepoint";i:166;}}}}}s:1:"s";a:4:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119991;}}}s:1:"e";a:1:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8271;}}}}s:1:"i";a:1:{s:1:"m";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8765;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8909;}}}}s:1:"o";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:92;}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10693;}}}}}s:1:"u";a:2:{s:1:"l";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8226;}s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8226;}}}}}s:1:"m";a:1:{s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8782;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10926;}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8783;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8783;}}}}}}}s:1:"c";a:15:{s:1:"a";a:3:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:263;}}}}}s:1:"p";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8745;}s:1:"a";a:1:{s:1:"n";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10820;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10825;}}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10827;}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10823;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10816;}}}}}s:1:"r";a:2:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8257;}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:711;}}}}}s:1:"c";a:4:{s:1:"a";a:2:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10829;}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:269;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:231;}s:9:"codepoint";i:231;}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:265;}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10828;}s:1:"s";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10832;}}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:267;}}}}s:1:"e";a:3:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:184;}s:9:"codepoint";i:184;}}}s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10674;}}}}}}s:1:"n";a:1:{s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:162;}s:9:"codepoint";i:162;s:1:"e";a:1:{s:1:"r";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:183;}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120096;}}}s:1:"h";a:3:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1095;}}}s:1:"e";a:1:{s:1:"c";a:1:{s:1:"k";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10003;}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10003;}}}}}}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:967;}}}s:1:"i";a:1:{s:1:"r";a:7:{s:1:";";a:1:{s:9:"codepoint";i:9675;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10691;}}s:1:"c";a:3:{s:1:";";a:1:{s:9:"codepoint";i:710;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8791;}}}s:1:"l";a:1:{s:1:"e";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8634;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8635;}}}}}}}}}}}s:1:"d";a:5:{s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:174;}}s:1:"S";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9416;}}s:1:"a";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8859;}}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8858;}}}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8861;}}}}}}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8791;}}s:1:"f";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10768;}}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10991;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10690;}}}}}}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9827;}s:1:"u";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9827;}}}}}}}}s:1:"o";a:4:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:58;}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8788;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8788;}}}}}}s:1:"m";a:2:{s:1:"m";a:1:{s:1:"a";a:2:{s:1:";";a:1:{s:9:"codepoint";i:44;}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64;}}}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8705;}s:1:"f";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8728;}}}s:1:"l";a:1:{s:1:"e";a:2:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8705;}}}}}s:1:"x";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8450;}}}}}}}}s:1:"n";a:2:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8773;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10861;}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8750;}}}}}s:1:"p";a:3:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120148;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8720;}}}}s:1:"y";a:3:{s:1:";";a:1:{s:9:"codepoint";i:169;}s:9:"codepoint";i:169;s:1:"s";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8471;}}}}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8629;}}}}s:1:"o";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10007;}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119992;}}}s:1:"u";a:2:{s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10959;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10961;}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10960;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10962;}}}}}s:1:"t";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8943;}}}}}s:1:"u";a:7:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:2:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10552;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10549;}}}}}}s:1:"e";a:2:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8926;}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8927;}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8630;}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10557;}}}}}}s:1:"p";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8746;}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10824;}}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10822;}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10826;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8845;}}}}s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10821;}}}}s:1:"r";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8631;}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10556;}}}}}s:1:"l";a:1:{s:1:"y";a:3:{s:1:"e";a:1:{s:1:"q";a:2:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8926;}}}}}s:1:"s";a:1:{s:1:"u";a:1:{s:1:"c";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8927;}}}}}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8910;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8911;}}}}}}}}s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:164;}s:9:"codepoint";i:164;}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8630;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8631;}}}}}}}}}}}}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8910;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8911;}}}}}s:1:"w";a:2:{s:1:"c";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8754;}}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8753;}}}}}s:1:"y";a:1:{s:1:"l";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9005;}}}}}}}s:1:"d";a:19:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8659;}}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10597;}}}}s:1:"a";a:4:{s:1:"g";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8224;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8504;}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8595;}}}s:1:"s";a:1:{s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8208;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8867;}}}}}s:1:"b";a:2:{s:1:"k";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10511;}}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:733;}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:271;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1076;}}}s:1:"d";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8518;}s:1:"a";a:2:{s:1:"g";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8225;}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8650;}}}}s:1:"o";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10871;}}}}}}}s:1:"e";a:3:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:176;}s:9:"codepoint";i:176;}s:1:"l";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:948;}}}}s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10673;}}}}}}}s:1:"f";a:2:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10623;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120097;}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8643;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8642;}}}}}s:1:"i";a:5:{s:1:"a";a:1:{s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8900;}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8900;}s:1:"s";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9830;}}}}}}}}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9830;}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:168;}}s:1:"g";a:1:{s:1:"a";a:1:{s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:989;}}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8946;}}}}s:1:"v";a:3:{s:1:";";a:1:{s:9:"codepoint";i:247;}s:1:"i";a:1:{s:1:"d";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:247;}s:9:"codepoint";i:247;s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8903;}}}}}}}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8903;}}}}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1106;}}}}s:1:"l";a:1:{s:1:"c";a:2:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8990;}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8973;}}}}}}s:1:"o";a:5:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:36;}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120149;}}}s:1:"t";a:5:{s:1:";";a:1:{s:9:"codepoint";i:729;}s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8784;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8785;}}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8760;}}}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8724;}}}}}s:1:"s";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8865;}}}}}}}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8966;}}}}}}}}}}}}}s:1:"w";a:1:{s:1:"n";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8595;}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8650;}}}}}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8643;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8642;}}}}}}}}}}}}}}}}s:1:"r";a:2:{s:1:"b";a:1:{s:1:"k";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10512;}}}}}}}s:1:"c";a:2:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8991;}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8972;}}}}}}s:1:"s";a:3:{s:1:"c";a:2:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119993;}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1109;}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10742;}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:273;}}}}}}s:1:"t";a:2:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8945;}}}}s:1:"r";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9663;}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9662;}}}}}s:1:"u";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8693;}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10607;}}}}}s:1:"w";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10662;}}}}}}}s:1:"z";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1119;}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10239;}}}}}}}}}s:1:"e";a:18:{s:1:"D";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10871;}}}}s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8785;}}}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:233;}s:9:"codepoint";i:233;}}}}s:1:"s";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10862;}}}}}}s:1:"c";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:283;}}}}}s:1:"i";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8790;}s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:234;}s:9:"codepoint";i:234;}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8789;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1101;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:279;}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8519;}}s:1:"f";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8786;}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120098;}}}s:1:"g";a:3:{s:1:";";a:1:{s:9:"codepoint";i:10906;}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:232;}s:9:"codepoint";i:232;}}}}s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10902;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10904;}}}}}}s:1:"l";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10905;}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9191;}}}}}}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8467;}}s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10901;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10903;}}}}}}s:1:"m";a:3:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:275;}}}}s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8709;}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8709;}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8709;}}}}}s:1:"s";a:1:{s:1:"p";a:2:{i:1;a:2:{i:3;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8196;}}i:4;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8197;}}}s:1:";";a:1:{s:9:"codepoint";i:8195;}}}}s:1:"n";a:2:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:331;}}s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8194;}}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:281;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120150;}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8917;}s:1:"s";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10723;}}}}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10865;}}}}s:1:"s";a:1:{s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:1013;}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:949;}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:949;}}}}}s:1:"q";a:4:{s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8790;}}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8789;}}}}}}s:1:"s";a:2:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8770;}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:2:{s:1:"g";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10902;}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10901;}}}}}}}}}}s:1:"u";a:3:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:61;}}}}s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8799;}}}}s:1:"i";a:1:{s:1:"v";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8801;}s:1:"D";a:1:{s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10872;}}}}}}s:1:"v";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10725;}}}}}}}}s:1:"r";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8787;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10609;}}}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8495;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8784;}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8770;}}}}s:1:"t";a:2:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:951;}}s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:240;}s:9:"codepoint";i:240;}}s:1:"u";a:2:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:235;}s:9:"codepoint";i:235;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8364;}}}}s:1:"x";a:3:{s:1:"c";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:33;}}}s:1:"i";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8707;}}}}s:1:"p";a:2:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8496;}}}}}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8519;}}}}}}}}}}}}}s:1:"f";a:11:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8786;}}}}}}}}}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1092;}}}s:1:"e";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9792;}}}}}}s:1:"f";a:3:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64259;}}}}}s:1:"l";a:2:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64256;}}}s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64260;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120099;}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64257;}}}}}s:1:"l";a:3:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9837;}}}s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64258;}}}}s:1:"t";a:1:{s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9649;}}}}}s:1:"n";a:1:{s:1:"o";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:402;}}}}s:1:"o";a:2:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120151;}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8704;}}}}s:1:"k";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8916;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10969;}}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10765;}}}}}}}}s:1:"r";a:2:{s:1:"a";a:2:{s:1:"c";a:6:{i:1;a:6:{i:2;a:2:{s:1:";";a:1:{s:9:"codepoint";i:189;}s:9:"codepoint";i:189;}i:3;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8531;}}i:4;a:2:{s:1:";";a:1:{s:9:"codepoint";i:188;}s:9:"codepoint";i:188;}i:5;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8533;}}i:6;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8537;}}i:8;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8539;}}}i:2;a:2:{i:3;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8532;}}i:5;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8534;}}}i:3;a:3:{i:4;a:2:{s:1:";";a:1:{s:9:"codepoint";i:190;}s:9:"codepoint";i:190;}i:5;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8535;}}i:8;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8540;}}}i:4;a:1:{i:5;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8536;}}}i:5;a:2:{i:6;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8538;}}i:8;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8541;}}}i:7;a:1:{i:8;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8542;}}}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8260;}}}}s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8994;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119995;}}}}}s:1:"g";a:16:{s:1:"E";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8807;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10892;}}}s:1:"a";a:3:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:501;}}}}}s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:2:{s:1:";";a:1:{s:9:"codepoint";i:947;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:989;}}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10886;}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:287;}}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:285;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1075;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:289;}}}}s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8805;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8923;}}s:1:"q";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8805;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8807;}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10878;}}}}}}}s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10878;}s:1:"c";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10921;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10880;}s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10882;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10884;}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10900;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120100;}}}s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8811;}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8921;}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8503;}}}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1107;}}}}s:1:"l";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8823;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10898;}}s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10917;}}s:1:"j";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10916;}}}s:1:"n";a:4:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8809;}}s:1:"a";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10890;}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10890;}}}}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10888;}s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10888;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8809;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8935;}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120152;}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:96;}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8458;}}}s:1:"i";a:1:{s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8819;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10894;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10896;}}}}}s:1:"t";a:7:{s:1:";";a:1:{s:9:"codepoint";i:62;}s:9:"codepoint";i:62;s:1:"c";a:2:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10919;}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10874;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8919;}}}}s:1:"l";a:1:{s:1:"P";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10645;}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10876;}}}}}}s:1:"r";a:5:{s:1:"a";a:2:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10886;}}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10616;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8919;}}}}s:1:"e";a:1:{s:1:"q";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8923;}}}}}s:1:"q";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10892;}}}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8823;}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8819;}}}}}}}s:1:"h";a:10:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8660;}}}}s:1:"a";a:4:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8202;}}}}}s:1:"l";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:189;}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8459;}}}}}s:1:"r";a:2:{s:1:"d";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1098;}}}}s:1:"r";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8596;}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10568;}}}}s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8621;}}}}}s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8463;}}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:293;}}}}}s:1:"e";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9829;}s:1:"u";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9829;}}}}}}}}s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8230;}}}}}s:1:"r";a:1:{s:1:"c";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8889;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120101;}}}s:1:"k";a:1:{s:1:"s";a:2:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10533;}}}}}}s:1:"w";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10534;}}}}}}}}s:1:"o";a:5:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8703;}}}}s:1:"m";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8763;}}}}}s:1:"o";a:1:{s:1:"k";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8617;}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8618;}}}}}}}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120153;}}}s:1:"r";a:1:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8213;}}}}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119997;}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8463;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:295;}}}}}}s:1:"y";a:2:{s:1:"b";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8259;}}}}}s:1:"p";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8208;}}}}}}}s:1:"i";a:15:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:237;}s:9:"codepoint";i:237;}}}}}s:1:"c";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8291;}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:238;}s:9:"codepoint";i:238;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1080;}}}s:1:"e";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1077;}}}s:1:"x";a:1:{s:1:"c";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:161;}s:9:"codepoint";i:161;}}}}s:1:"f";a:2:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8660;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120102;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:236;}s:9:"codepoint";i:236;}}}}}s:1:"i";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8520;}s:1:"i";a:2:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10764;}}}}s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8749;}}}}s:1:"n";a:1:{s:1:"f";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10716;}}}}}s:1:"o";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8489;}}}}}s:1:"j";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:307;}}}}}s:1:"m";a:3:{s:1:"a";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:299;}}}s:1:"g";a:3:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8465;}}s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8464;}}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8465;}}}}}}s:1:"t";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:305;}}}}s:1:"o";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8887;}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:437;}}}}}s:1:"n";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8712;}s:1:"c";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8453;}}}}}s:1:"f";a:1:{s:1:"i";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8734;}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10717;}}}}}}}s:1:"o";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:305;}}}}}s:1:"t";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8747;}s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8890;}}}}s:1:"e";a:2:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8484;}}}}}s:1:"r";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8890;}}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10775;}}}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10812;}}}}}}}s:1:"o";a:4:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1105;}}}s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:303;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120154;}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:953;}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10812;}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:191;}s:9:"codepoint";i:191;}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119998;}}}s:1:"i";a:1:{s:1:"n";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8712;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8953;}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8949;}}}}s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8948;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8947;}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8712;}}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8290;}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:297;}}}}}}s:1:"u";a:2:{s:1:"k";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1110;}}}}s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:239;}s:9:"codepoint";i:239;}}}}s:1:"j";a:6:{s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:309;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1081;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120103;}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:567;}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120155;}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119999;}}}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1112;}}}}}}s:1:"u";a:1:{s:1:"k";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1108;}}}}}}s:1:"k";a:8:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"a";a:2:{s:1:";";a:1:{s:9:"codepoint";i:954;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1008;}}}}}}s:1:"c";a:2:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:311;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1082;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120104;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:312;}}}}}}s:1:"h";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1093;}}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1116;}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120156;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120000;}}}}}s:1:"l";a:22:{s:1:"A";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8666;}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8656;}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10523;}}}}}}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10510;}}}}}s:1:"E";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8806;}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10891;}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10594;}}}}s:1:"a";a:9:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:314;}}}}}s:1:"e";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10676;}}}}}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8466;}}}}}s:1:"m";a:1:{s:1:"b";a:1:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:955;}}}}}s:1:"n";a:1:{s:1:"g";a:3:{s:1:";";a:1:{s:9:"codepoint";i:10216;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10641;}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10216;}}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10885;}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:171;}s:9:"codepoint";i:171;}}}s:1:"r";a:1:{s:1:"r";a:8:{s:1:";";a:1:{s:9:"codepoint";i:8592;}s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8676;}s:1:"f";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10527;}}}}s:1:"f";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10525;}}}s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8617;}}}s:1:"l";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8619;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10553;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10611;}}}}s:1:"t";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8610;}}}}}s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:10923;}s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10521;}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10925;}}}}s:1:"b";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10508;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10098;}}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:123;}}s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:91;}}}}s:1:"k";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10635;}}s:1:"s";a:1:{s:1:"l";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10639;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10637;}}}}}}}s:1:"c";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:318;}}}}}s:1:"e";a:2:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:316;}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8968;}}}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:123;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1083;}}}s:1:"d";a:4:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10550;}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8220;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8222;}}}}}s:1:"r";a:2:{s:1:"d";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10599;}}}}}s:1:"u";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10571;}}}}}}}s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8626;}}}}s:1:"e";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8804;}s:1:"f";a:1:{s:1:"t";a:5:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8592;}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8610;}}}}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8637;}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8636;}}}}}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8647;}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8596;}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8646;}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8651;}}}}}}}}}s:1:"s";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8621;}}}}}}}}}}}}}}}}s:1:"t";a:1:{s:1:"h";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8907;}}}}}}}}}}}}}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8922;}}s:1:"q";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8804;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8806;}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10877;}}}}}}}s:1:"s";a:5:{s:1:";";a:1:{s:9:"codepoint";i:10877;}s:1:"c";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10920;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10879;}s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10881;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10883;}}}}}}s:1:"g";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10899;}}}}s:1:"s";a:5:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10885;}}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8918;}}}}s:1:"e";a:1:{s:1:"q";a:2:{s:1:"g";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8922;}}}}s:1:"q";a:1:{s:1:"g";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10891;}}}}}}}s:1:"g";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8822;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8818;}}}}}}}s:1:"f";a:3:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10620;}}}}}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8970;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120105;}}}s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8822;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10897;}}}s:1:"h";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8637;}}s:1:"u";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8636;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10602;}}}}}s:1:"b";a:1:{s:1:"l";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9604;}}}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1113;}}}}s:1:"l";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8810;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8647;}}}}s:1:"c";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8990;}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10603;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9722;}}}}}s:1:"m";a:2:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:320;}}}}}s:1:"o";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9136;}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9136;}}}}}}}}}}s:1:"n";a:4:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8808;}}s:1:"a";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10889;}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10889;}}}}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10887;}s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10887;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8808;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8934;}}}}}s:1:"o";a:8:{s:1:"a";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10220;}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8701;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10214;}}}}s:1:"n";a:1:{s:1:"g";a:3:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10229;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10231;}}}}}}}}}}}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10236;}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10230;}}}}}}}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8619;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8620;}}}}}}}}}}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10629;}}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120157;}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10797;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10804;}}}}}}s:1:"w";a:2:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8727;}}}}s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:95;}}}}}s:1:"z";a:3:{s:1:";";a:1:{s:9:"codepoint";i:9674;}s:1:"e";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9674;}}}}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10731;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:40;}s:1:"l";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10643;}}}}}}s:1:"r";a:5:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8646;}}}}s:1:"c";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8991;}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8651;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10605;}}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8206;}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8895;}}}}}s:1:"s";a:6:{s:1:"a";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8249;}}}}}s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120001;}}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8624;}}s:1:"i";a:1:{s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8818;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10893;}}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10895;}}}}s:1:"q";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:91;}}s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8216;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8218;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:322;}}}}}}s:1:"t";a:9:{s:1:";";a:1:{s:9:"codepoint";i:60;}s:9:"codepoint";i:60;s:1:"c";a:2:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10918;}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10873;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8918;}}}}s:1:"h";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8907;}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8905;}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10614;}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10875;}}}}}}s:1:"r";a:2:{s:1:"P";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10646;}}}}s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:9667;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8884;}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9666;}}}}}s:1:"u";a:1:{s:1:"r";a:2:{s:1:"d";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10570;}}}}}}s:1:"u";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10598;}}}}}}}}s:1:"m";a:14:{s:1:"D";a:1:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8762;}}}}}s:1:"a";a:4:{s:1:"c";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:175;}s:9:"codepoint";i:175;}}s:1:"l";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9794;}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10016;}s:1:"e";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10016;}}}}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8614;}s:1:"s";a:1:{s:1:"t";a:1:{s:1:"o";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8614;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8615;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8612;}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8613;}}}}}}}s:1:"r";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9646;}}}}}}s:1:"c";a:2:{s:1:"o";a:1:{s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10793;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1084;}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8212;}}}}}s:1:"e";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8737;}}}}}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120106;}}}s:1:"h";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8487;}}}s:1:"i";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:181;}s:9:"codepoint";i:181;}}}s:1:"d";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8739;}s:1:"a";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:42;}}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10992;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:183;}s:9:"codepoint";i:183;}}}}s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8722;}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8863;}}s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8760;}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10794;}}}}}}}s:1:"l";a:2:{s:1:"c";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10971;}}}s:1:"d";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8230;}}}}s:1:"n";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8723;}}}}}}s:1:"o";a:2:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8871;}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120158;}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8723;}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120002;}}}s:1:"t";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8766;}}}}}}s:1:"u";a:3:{s:1:";";a:1:{s:9:"codepoint";i:956;}s:1:"l";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8888;}}}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8888;}}}}}}s:1:"n";a:23:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8653;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8654;}}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8655;}}}}}}}}}}}s:1:"V";a:2:{s:1:"D";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8879;}}}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8878;}}}}}}s:1:"a";a:4:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8711;}}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:324;}}}}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8777;}s:1:"o";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:329;}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8777;}}}}}}s:1:"t";a:1:{s:1:"u";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9838;}s:1:"a";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9838;}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8469;}}}}}}}}s:1:"b";a:1:{s:1:"s";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:160;}s:9:"codepoint";i:160;}}}s:1:"c";a:5:{s:1:"a";a:2:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10819;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:328;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:326;}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8775;}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10818;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1085;}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8211;}}}}}s:1:"e";a:6:{s:1:";";a:1:{s:9:"codepoint";i:8800;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8663;}}}}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10532;}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8599;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8599;}}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8802;}}}}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10536;}}}}}s:1:"x";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8708;}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8708;}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120107;}}}s:1:"g";a:3:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8817;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8817;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8821;}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8815;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8815;}}}}s:1:"h";a:3:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8654;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8622;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10994;}}}}}s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8715;}s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8956;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8954;}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8715;}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1114;}}}}s:1:"l";a:6:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8653;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8602;}}}}s:1:"d";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8229;}}}s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8816;}s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8602;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8622;}}}}}}}}}}}}}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8816;}}s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8814;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8820;}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8814;}s:1:"r";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8938;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8940;}}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8740;}}}}s:1:"o";a:2:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120159;}}}s:1:"t";a:4:{s:1:";";a:1:{s:9:"codepoint";i:172;}s:9:"codepoint";i:172;s:1:"i";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8713;}s:1:"v";a:3:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8713;}}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8951;}}s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8950;}}}}}s:1:"n";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8716;}s:1:"v";a:3:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8716;}}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8958;}}s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8957;}}}}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8742;}s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8742;}}}}}}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10772;}}}}}}s:1:"r";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8832;}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8928;}}}}s:1:"e";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8832;}}}}}s:1:"r";a:4:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8655;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8603;}}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8603;}}}}}}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8939;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8941;}}}}}}s:1:"s";a:7:{s:1:"c";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8833;}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8929;}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120003;}}}s:1:"h";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"t";a:2:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8740;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8742;}}}}}}}}}}}}}s:1:"i";a:1:{s:1:"m";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8769;}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8772;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8772;}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8740;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8742;}}}}s:1:"q";a:1:{s:1:"s";a:1:{s:1:"u";a:2:{s:1:"b";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8930;}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8931;}}}}}}s:1:"u";a:3:{s:1:"b";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8836;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8840;}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8840;}}}}}}}s:1:"c";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8833;}}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8837;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8841;}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8841;}}}}}}}}}s:1:"t";a:4:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8825;}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:241;}s:9:"codepoint";i:241;}}}}s:1:"l";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8824;}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8938;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8940;}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8939;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8941;}}}}}}}}}}}}}}}}s:1:"u";a:2:{s:1:";";a:1:{s:9:"codepoint";i:957;}s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:35;}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8470;}}}}s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8199;}}}}}s:1:"v";a:6:{s:1:"D";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8877;}}}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10500;}}}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8876;}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"f";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10718;}}}}}}s:1:"l";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10498;}}}}}s:1:"r";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10499;}}}}}}s:1:"w";a:3:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8662;}}}}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10531;}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8598;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8598;}}}}}}s:1:"n";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10535;}}}}}}}s:1:"o";a:18:{s:1:"S";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9416;}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:243;}s:9:"codepoint";i:243;}}}}s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8859;}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8858;}s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:244;}s:9:"codepoint";i:244;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1086;}}}s:1:"d";a:5:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8861;}}}}s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:337;}}}}}s:1:"i";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10808;}}}s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8857;}}}s:1:"s";a:1:{s:1:"o";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10684;}}}}}}s:1:"e";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:339;}}}}}s:1:"f";a:2:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10687;}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120108;}}}s:1:"g";a:3:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:731;}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:242;}s:9:"codepoint";i:242;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10689;}}}s:1:"h";a:2:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10677;}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8486;}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8750;}}}}s:1:"l";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8634;}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10686;}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10683;}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8254;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10688;}}}s:1:"m";a:3:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:333;}}}}s:1:"e";a:1:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:969;}}}}s:1:"i";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:959;}}}}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10678;}}s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8854;}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120160;}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10679;}}}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10681;}}}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8853;}}}}}s:1:"r";a:7:{s:1:";";a:1:{s:9:"codepoint";i:8744;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8635;}}}}s:1:"d";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10845;}s:1:"e";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8500;}s:1:"o";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8500;}}}}}s:1:"f";a:2:{s:1:";";a:1:{s:9:"codepoint";i:170;}s:9:"codepoint";i:170;}s:1:"m";a:2:{s:1:";";a:1:{s:9:"codepoint";i:186;}s:9:"codepoint";i:186;}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8886;}}}}}s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10838;}}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10839;}}}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10843;}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8500;}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:248;}s:9:"codepoint";i:248;}}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8856;}}}}s:1:"t";a:1:{s:1:"i";a:2:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:245;}s:9:"codepoint";i:245;}}}s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8855;}s:1:"a";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10806;}}}}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:246;}s:9:"codepoint";i:246;}}}s:1:"v";a:1:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9021;}}}}}}s:1:"p";a:12:{s:1:"a";a:1:{s:1:"r";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8741;}s:1:"a";a:3:{s:1:";";a:1:{s:9:"codepoint";i:182;}s:9:"codepoint";i:182;s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8741;}}}}}}s:1:"s";a:2:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10995;}}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:11005;}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8706;}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1087;}}}s:1:"e";a:1:{s:1:"r";a:5:{s:1:"c";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:37;}}}}s:1:"i";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:46;}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8240;}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8869;}}s:1:"t";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8241;}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120109;}}}s:1:"h";a:3:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:966;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:966;}}}s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8499;}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9742;}}}}}s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:960;}s:1:"t";a:1:{s:1:"c";a:1:{s:1:"h";a:1:{s:1:"f";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8916;}}}}}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:982;}}}s:1:"l";a:2:{s:1:"a";a:1:{s:1:"n";a:2:{s:1:"c";a:1:{s:1:"k";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8463;}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8462;}}}}s:1:"k";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8463;}}}}}s:1:"u";a:1:{s:1:"s";a:9:{s:1:";";a:1:{s:9:"codepoint";i:43;}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10787;}}}}}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8862;}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10786;}}}}s:1:"d";a:2:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8724;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10789;}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10866;}}s:1:"m";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:177;}s:9:"codepoint";i:177;}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10790;}}}}s:1:"t";a:1:{s:1:"w";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10791;}}}}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:177;}}s:1:"o";a:3:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10773;}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120161;}}}s:1:"u";a:1:{s:1:"n";a:1:{s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:163;}s:9:"codepoint";i:163;}}}}s:1:"r";a:10:{s:1:";";a:1:{s:9:"codepoint";i:8826;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10931;}}s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10935;}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8828;}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10927;}s:1:"c";a:6:{s:1:";";a:1:{s:9:"codepoint";i:8826;}s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10935;}}}}}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"l";a:1:{s:1:"y";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8828;}}}}}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10927;}}}s:1:"n";a:3:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10937;}}}}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10933;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8936;}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8830;}}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8242;}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8473;}}}}}s:1:"n";a:3:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10933;}}s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10937;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8936;}}}}}s:1:"o";a:3:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8719;}}s:1:"f";a:3:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9006;}}}}}s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8978;}}}}}s:1:"s";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8979;}}}}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8733;}s:1:"t";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8733;}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8830;}}}}s:1:"u";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8880;}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120005;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:968;}}}s:1:"u";a:1:{s:1:"n";a:1:{s:1:"c";a:1:{s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8200;}}}}}}}s:1:"q";a:6:{s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120110;}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10764;}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120162;}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8279;}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120006;}}}}s:1:"u";a:3:{s:1:"a";a:1:{s:1:"t";a:2:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8461;}}}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10774;}}}}}}s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:63;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8799;}}}}}}s:1:"o";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:34;}s:9:"codepoint";i:34;}}}}s:1:"r";a:21:{s:1:"A";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8667;}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8658;}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10524;}}}}}}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10511;}}}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10596;}}}}s:1:"a";a:7:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10714;}}s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:341;}}}}}s:1:"d";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8730;}}}}s:1:"e";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10675;}}}}}}}s:1:"n";a:1:{s:1:"g";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10217;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10642;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10661;}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10217;}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:187;}s:9:"codepoint";i:187;}}}s:1:"r";a:1:{s:1:"r";a:11:{s:1:";";a:1:{s:9:"codepoint";i:8594;}s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10613;}}}s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8677;}s:1:"f";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10528;}}}}s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10547;}}s:1:"f";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10526;}}}s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8618;}}}s:1:"l";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8620;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10565;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10612;}}}}s:1:"t";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8611;}}}s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8605;}}}}s:1:"t";a:2:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10522;}}}}s:1:"i";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8758;}s:1:"n";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8474;}}}}}}}}}s:1:"b";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10509;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10099;}}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:125;}}s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:93;}}}}s:1:"k";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10636;}}s:1:"s";a:1:{s:1:"l";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10638;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10640;}}}}}}}s:1:"c";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:345;}}}}}s:1:"e";a:2:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:343;}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8969;}}}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:125;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1088;}}}s:1:"d";a:4:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10551;}}}s:1:"l";a:1:{s:1:"d";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10601;}}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8221;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8221;}}}}}s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8627;}}}}s:1:"e";a:3:{s:1:"a";a:1:{s:1:"l";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8476;}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8475;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8476;}}}}}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8477;}}}}s:1:"c";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9645;}}}s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:174;}s:9:"codepoint";i:174;}}s:1:"f";a:3:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10621;}}}}}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8971;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120111;}}}s:1:"h";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8641;}}s:1:"u";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8640;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10604;}}}}}s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:961;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1009;}}}}s:1:"i";a:3:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:6:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8594;}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8611;}}}}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8641;}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8640;}}}}}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8644;}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8652;}}}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8649;}}}}}}}}}}}}s:1:"s";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8605;}}}}}}}}}}}s:1:"t";a:1:{s:1:"h";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8908;}}}}}}}}}}}}}}s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:730;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8787;}}}}}}}}}}}}s:1:"l";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8644;}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8652;}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8207;}}}s:1:"m";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9137;}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9137;}}}}}}}}}}s:1:"n";a:1:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10990;}}}}}s:1:"o";a:4:{s:1:"a";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10221;}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8702;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10215;}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10630;}}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120163;}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10798;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10805;}}}}}}}s:1:"p";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:41;}s:1:"g";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10644;}}}}}s:1:"p";a:1:{s:1:"o";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10770;}}}}}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8649;}}}}}s:1:"s";a:4:{s:1:"a";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8250;}}}}}s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120007;}}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8625;}}s:1:"q";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:93;}}s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8217;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8217;}}}}}}s:1:"t";a:3:{s:1:"h";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8908;}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8906;}}}}}s:1:"r";a:1:{s:1:"i";a:4:{s:1:";";a:1:{s:9:"codepoint";i:9657;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8885;}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9656;}}s:1:"l";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10702;}}}}}}}}s:1:"u";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10600;}}}}}}}s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8478;}}}s:1:"s";a:19:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:347;}}}}}}s:1:"b";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8218;}}}}}s:1:"c";a:10:{s:1:";";a:1:{s:9:"codepoint";i:8827;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10932;}}s:1:"a";a:2:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10936;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:353;}}}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8829;}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10928;}s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:351;}}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:349;}}}}s:1:"n";a:3:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10934;}}s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10938;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8937;}}}}}s:1:"p";a:1:{s:1:"o";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10771;}}}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8831;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1089;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8901;}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8865;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10854;}}}}}s:1:"e";a:7:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8664;}}}}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10533;}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8600;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8600;}}}}}}s:1:"c";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:167;}s:9:"codepoint";i:167;}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:59;}}}s:1:"s";a:1:{s:1:"w";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10537;}}}}}s:1:"t";a:1:{s:1:"m";a:2:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}}s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}s:1:"x";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10038;}}}}s:1:"f";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:120112;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8994;}}}}}}s:1:"h";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9839;}}}}s:1:"c";a:2:{s:1:"h";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1097;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1096;}}}s:1:"o";a:1:{s:1:"r";a:1:{s:1:"t";a:2:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8739;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8741;}}}}}}}}}}}}s:1:"y";a:2:{s:1:";";a:1:{s:9:"codepoint";i:173;}s:9:"codepoint";i:173;}}s:1:"i";a:2:{s:1:"g";a:1:{s:1:"m";a:1:{s:1:"a";a:3:{s:1:";";a:1:{s:9:"codepoint";i:963;}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:962;}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:962;}}}}}s:1:"m";a:8:{s:1:";";a:1:{s:9:"codepoint";i:8764;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10858;}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8771;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8771;}}}s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10910;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10912;}}}s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10909;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10911;}}}s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8774;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10788;}}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10610;}}}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8592;}}}}}s:1:"m";a:4:{s:1:"a";a:2:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}}}}}}}}s:1:"s";a:1:{s:1:"h";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10803;}}}}}s:1:"e";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10724;}}}}}}}s:1:"i";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8739;}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8995;}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10922;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10924;}}}}s:1:"o";a:3:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1100;}}}}}s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:47;}s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10692;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9023;}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120164;}}}}s:1:"p";a:1:{s:1:"a";a:2:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9824;}s:1:"u";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9824;}}}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8741;}}}}s:1:"q";a:3:{s:1:"c";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8851;}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8852;}}}}s:1:"s";a:1:{s:1:"u";a:2:{s:1:"b";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8847;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8849;}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8847;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8849;}}}}}}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8848;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8850;}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8848;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8850;}}}}}}}}}s:1:"u";a:3:{s:1:";";a:1:{s:9:"codepoint";i:9633;}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9633;}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9642;}}}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9642;}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8594;}}}}}s:1:"s";a:4:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120008;}}}s:1:"e";a:1:{s:1:"t";a:1:{s:1:"m";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8995;}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8902;}}}}}}s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9734;}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9733;}}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:"e";a:1:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1013;}}}}}}}}s:1:"p";a:1:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:981;}}}}}}}}}s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:175;}}}}}s:1:"u";a:5:{s:1:"b";a:9:{s:1:";";a:1:{s:9:"codepoint";i:8834;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10949;}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10941;}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8838;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10947;}}}}}s:1:"m";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10945;}}}}}s:1:"n";a:2:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10955;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8842;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10943;}}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10617;}}}}}s:1:"s";a:3:{s:1:"e";a:1:{s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8834;}s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8838;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10949;}}}}s:1:"n";a:1:{s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8842;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10955;}}}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10951;}}}s:1:"u";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10965;}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10963;}}}}}s:1:"c";a:1:{s:1:"c";a:6:{s:1:";";a:1:{s:9:"codepoint";i:8827;}s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10936;}}}}}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"l";a:1:{s:1:"y";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8829;}}}}}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10928;}}}s:1:"n";a:3:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10938;}}}}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10934;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8937;}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8831;}}}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8721;}}s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9834;}}}s:1:"p";a:13:{i:1;a:2:{s:1:";";a:1:{s:9:"codepoint";i:185;}s:9:"codepoint";i:185;}i:2;a:2:{s:1:";";a:1:{s:9:"codepoint";i:178;}s:9:"codepoint";i:178;}i:3;a:2:{s:1:";";a:1:{s:9:"codepoint";i:179;}s:9:"codepoint";i:179;}s:1:";";a:1:{s:9:"codepoint";i:8835;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10950;}}s:1:"d";a:2:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10942;}}}s:1:"s";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10968;}}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8839;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10948;}}}}}s:1:"h";a:1:{s:1:"s";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10967;}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10619;}}}}}s:1:"m";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10946;}}}}}s:1:"n";a:2:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10956;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8843;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10944;}}}}}s:1:"s";a:3:{s:1:"e";a:1:{s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8835;}s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8839;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10950;}}}}s:1:"n";a:1:{s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8843;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10956;}}}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10952;}}}s:1:"u";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10964;}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10966;}}}}}}s:1:"w";a:3:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8665;}}}}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10534;}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8601;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8601;}}}}}}s:1:"n";a:1:{s:1:"w";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10538;}}}}}}s:1:"z";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:223;}s:9:"codepoint";i:223;}}}}}s:1:"t";a:13:{s:1:"a";a:2:{s:1:"r";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8982;}}}}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:964;}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9140;}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:357;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:355;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1090;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8411;}}}}s:1:"e";a:1:{s:1:"l";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8981;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120113;}}}s:1:"h";a:4:{s:1:"e";a:2:{s:1:"r";a:1:{s:1:"e";a:2:{i:4;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8756;}}s:1:"f";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8756;}}}}}}}s:1:"t";a:1:{s:1:"a";a:3:{s:1:";";a:1:{s:9:"codepoint";i:952;}s:1:"s";a:1:{s:1:"y";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:977;}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:977;}}}}}s:1:"i";a:2:{s:1:"c";a:1:{s:1:"k";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8776;}}}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8764;}}}}}}s:1:"n";a:1:{s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8201;}}}}}s:1:"k";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8776;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8764;}}}}}s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:254;}s:9:"codepoint";i:254;}}}}s:1:"i";a:3:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:732;}}}}s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:215;}s:9:"codepoint";i:215;s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8864;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10801;}}}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10800;}}}}}s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8749;}}}}s:1:"o";a:3:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10536;}}}s:1:"p";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8868;}s:1:"b";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9014;}}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10993;}}}}s:1:"f";a:2:{s:1:";";a:1:{s:9:"codepoint";i:120165;}s:1:"o";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10970;}}}}}}s:1:"s";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10537;}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8244;}}}}}}s:1:"r";a:3:{s:1:"a";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8482;}}}}s:1:"i";a:7:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:5:{s:1:";";a:1:{s:9:"codepoint";i:9653;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9663;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9667;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8884;}}}}}}}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8796;}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9657;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8885;}}}}}}}}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9708;}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8796;}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10810;}}}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10809;}}}}}s:1:"s";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10701;}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10811;}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"z";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9186;}}}}}}}}s:1:"s";a:3:{s:1:"c";a:2:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120009;}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1094;}}}s:1:"h";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1115;}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:359;}}}}}}s:1:"w";a:2:{s:1:"i";a:1:{s:1:"x";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8812;}}}}s:1:"o";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"d";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8606;}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8608;}}}}}}}}}}}}}}}}}}s:1:"u";a:18:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8657;}}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10595;}}}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:250;}s:9:"codepoint";i:250;}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8593;}}}}s:1:"b";a:1:{s:1:"r";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1118;}}}s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:365;}}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:251;}s:9:"codepoint";i:251;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1091;}}}s:1:"d";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8645;}}}}s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:369;}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10606;}}}}}s:1:"f";a:2:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10622;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120114;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:249;}s:9:"codepoint";i:249;}}}}}s:1:"h";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8639;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8638;}}}}s:1:"b";a:1:{s:1:"l";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9600;}}}}}s:1:"l";a:2:{s:1:"c";a:2:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8988;}s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8988;}}}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8975;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9720;}}}}}s:1:"m";a:2:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:363;}}}}s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:168;}s:9:"codepoint";i:168;}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:371;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120166;}}}}s:1:"p";a:6:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8593;}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8597;}}}}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8639;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8638;}}}}}}}}}}}}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8846;}}}}s:1:"s";a:1:{s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:965;}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:978;}}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:965;}}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8648;}}}}}}}}}}s:1:"r";a:3:{s:1:"c";a:2:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8989;}s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8989;}}}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8974;}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:367;}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9721;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120010;}}}}s:1:"t";a:3:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8944;}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:361;}}}}}s:1:"r";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9653;}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9652;}}}}}s:1:"u";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8648;}}}}s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:252;}s:9:"codepoint";i:252;}}}s:1:"w";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10663;}}}}}}}}s:1:"v";a:14:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8661;}}}}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10984;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10985;}}}}}s:1:"D";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8872;}}}}}s:1:"a";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10652;}}}}}s:1:"r";a:7:{s:1:"e";a:1:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:949;}}}}}}}}s:1:"k";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1008;}}}}}}s:1:"n";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8709;}}}}}}}}s:1:"p";a:3:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:966;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:982;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8733;}}}}}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8597;}s:1:"h";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1009;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:962;}}}}}}s:1:"t";a:2:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:977;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8882;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8883;}}}}}}}}}}}}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1074;}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8866;}}}}}s:1:"e";a:3:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8744;}s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8891;}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8794;}}}}s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8942;}}}}}s:1:"r";a:2:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:124;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:124;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120115;}}}s:1:"l";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8882;}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120167;}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8733;}}}}}s:1:"r";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8883;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120011;}}}}s:1:"z";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"z";a:1:{s:1:"a";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10650;}}}}}}}}s:1:"w";a:7:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:373;}}}}}s:1:"e";a:2:{s:1:"d";a:2:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10847;}}}}s:1:"g";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8743;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8793;}}}}}s:1:"i";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8472;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120116;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120168;}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8472;}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8768;}s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8768;}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120012;}}}}}s:1:"x";a:14:{s:1:"c";a:3:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8898;}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9711;}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8899;}}}}s:1:"d";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9661;}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120117;}}}s:1:"h";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10234;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10231;}}}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:958;}}s:1:"l";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10232;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10229;}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10236;}}}}s:1:"n";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8955;}}}}s:1:"o";a:3:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10752;}}}}s:1:"p";a:2:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120169;}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10753;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10754;}}}}}}s:1:"r";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10233;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10230;}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120013;}}}s:1:"q";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10758;}}}}}}s:1:"u";a:2:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10756;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9651;}}}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8897;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8896;}}}}}}}s:1:"y";a:8:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:253;}s:9:"codepoint";i:253;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1103;}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:375;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1099;}}}s:1:"e";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:165;}s:9:"codepoint";i:165;}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120118;}}}s:1:"i";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1111;}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120170;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120014;}}}}s:1:"u";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1102;}}}s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:255;}s:9:"codepoint";i:255;}}}}s:1:"z";a:10:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:378;}}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:382;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1079;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:380;}}}}s:1:"e";a:2:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8488;}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:950;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120119;}}}s:1:"h";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1078;}}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8669;}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120171;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120015;}}}}s:1:"w";a:2:{s:1:"j";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8205;}}s:1:"n";a:1:{s:1:"j";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8204;}}}}}}
\ No newline at end of file
diff --git a/libraries/humble-http-agent/HumbleHttpAgent.php b/libraries/humble-http-agent/HumbleHttpAgent.php
index 7e5834a..0b30599 100644
--- a/libraries/humble-http-agent/HumbleHttpAgent.php
+++ b/libraries/humble-http-agent/HumbleHttpAgent.php
@@ -7,8 +7,8 @@
* For environments which do not have these options, it reverts to standard sequential
* requests (using file_get_contents())
*
- * @version 1.0
- * @date 2012-02-09
+ * @version 1.1
+ * @date 2012-08-20
* @see http://php.net/HttpRequestPool
* @author Keyvan Minoukadeh
* @copyright 2011-2012 Keyvan Minoukadeh
@@ -32,9 +32,10 @@ class HumbleHttpAgent
protected $cache = null; //TODO
protected $httpContext;
protected $minimiseMemoryUse = false; //TODO
- protected $debug = false;
protected $method;
protected $cookieJar;
+ public $debug = false;
+ public $debugVerbose = false;
public $rewriteHashbangFragment = true; // see http://code.google.com/web/ajaxcrawling/docs/specification.html
public $maxRedirects = 5;
public $userAgentMap = array();
@@ -50,7 +51,10 @@ class HumbleHttpAgent
// URLs ending with one of these extensions will
// prompt Humble HTTP Agent to send a HEAD request first
// to see if returned content type matches $headerOnlyTypes.
- public $headerOnlyClues = array('pdf','mp3','zip','exe','gif','gzip','gz','jpeg','jpg','mpg','mpeg','png','ppt','mov');
+ public $headerOnlyClues = array('pdf','mp3','zip','exe','gif','gzip','gz','jpeg','jpg','mpg','mpeg','png','ppt','mov');
+ // AJAX triggers to search for.
+ // for AJAX sites, e.g. Blogger with its dynamic views templates.
+ public $ajaxTriggers = array("debugVerbose) echo ' - mem used: ',$mem," (peak: $memPeak)";
+ echo "\n";
ob_flush();
flush();
}
@@ -150,6 +155,27 @@ class HumbleHttpAgent
return $iri->get_iri();
}
+ public function getUglyURL($url, $html) {
+ if ($html == '') return false;
+ $found = false;
+ foreach ($this->ajaxTriggers as $string) {
+ if (stripos($html, $string)) {
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) return false;
+ $iri = new SimplePie_IRI($url);
+ if (isset($iri->query)) {
+ parse_str($iri->query, $query);
+ } else {
+ $query = array();
+ }
+ $query['_escaped_fragment_'] = '';
+ $iri->query = str_replace('%2F', '/', http_build_query($query)); // needed for some sites
+ return $iri->get_iri();
+ }
+
public function removeFragment($url) {
$pos = strpos($url, '#');
if ($pos === false) {
@@ -308,6 +334,17 @@ class HumbleHttpAgent
$this->debug('Wrong guess at content-type, queing GET request');
$this->requests[$orig]['wrongGuess'] = true;
$this->redirectQueue[$orig] = $this->requests[$orig]['effective_url'];
+ } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) {
+ // check for
+ // for AJAX sites, e.g. Blogger with its dynamic views templates.
+ // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification
+ if (isset($this->requests[$orig]['body'])) {
+ $redirectURL = $this->getUglyURL($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000));
+ if ($redirectURL) {
+ $this->debug('AJAX trigger (meta name="fragment" content="!") found. Queueing '.$redirectURL);
+ $this->redirectQueue[$orig] = $redirectURL;
+ }
+ }
}
//die($url.' -multi- '.$request->getResponseInfo('effective_url'));
$pool->detach($request);
@@ -416,12 +453,23 @@ class HumbleHttpAgent
$this->debug('Redirect detected. Invalid URL: '.$redirectURL);
}
} elseif (!$_header_only_type && $this->requests[$orig]['method'] == 'HEAD') {
- // the response content-type did not match our 'header only' types,
- // but we'd issues a HEAD request because we assumed it would. So
- // let's queue a proper GET request for this item...
- $this->debug('Wrong guess at content-type, queing GET request');
- $this->requests[$orig]['wrongGuess'] = true;
- $this->redirectQueue[$orig] = $this->requests[$orig]['effective_url'];
+ // the response content-type did not match our 'header only' types,
+ // but we'd issues a HEAD request because we assumed it would. So
+ // let's queue a proper GET request for this item...
+ $this->debug('Wrong guess at content-type, queing GET request');
+ $this->requests[$orig]['wrongGuess'] = true;
+ $this->redirectQueue[$orig] = $this->requests[$orig]['effective_url'];
+ } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) {
+ // check for
+ // for AJAX sites, e.g. Blogger with its dynamic views templates.
+ // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification
+ if (isset($this->requests[$orig]['body'])) {
+ $redirectURL = $this->getUglyURL($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000));
+ if ($redirectURL) {
+ $this->debug('AJAX trigger (meta name="fragment" content="!") found. Queueing '.$redirectURL);
+ $this->redirectQueue[$orig] = $redirectURL;
+ }
+ }
}
// die($url.' -multi- '.$request->getResponseInfo('effective_url'));
unset($this->requests[$orig]['httpRequest'], $this->requests[$orig]['method']);
@@ -481,7 +529,7 @@ class HumbleHttpAgent
$this->requests[$orig]['status_code'] = $status_code = (int)$match[1];
unset($match);
// handle redirect
- if (preg_match('/^Location:(.*?)$/m', $this->requests[$orig]['headers'], $match)) {
+ if (preg_match('/^Location:(.*?)$/mi', $this->requests[$orig]['headers'], $match)) {
$this->requests[$orig]['location'] = trim($match[1]);
}
if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && isset($this->requests[$orig]['location'])) {
@@ -498,6 +546,17 @@ class HumbleHttpAgent
} else {
$this->debug('Redirect detected. Invalid URL: '.$redirectURL);
}
+ } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) {
+ // check for
+ // for AJAX sites, e.g. Blogger with its dynamic views templates.
+ // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification
+ if (isset($this->requests[$orig]['body'])) {
+ $redirectURL = $this->getUglyURL($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000));
+ if ($redirectURL) {
+ $this->debug('AJAX trigger (meta name="fragment" content="!") found. Queueing '.$redirectURL);
+ $this->redirectQueue[$orig] = $redirectURL;
+ }
+ }
}
}
} else {
@@ -520,7 +579,7 @@ class HumbleHttpAgent
$this->requests[$orig]['method'] = $request->method;
$this->requests[$orig]['effective_url'] = $info['url'];
$this->requests[$orig]['status_code'] = (int)$info['http_code'];
- if (preg_match('/^Location:(.*?)$/m', $this->requests[$orig]['headers'], $match)) {
+ if (preg_match('/^Location:(.*?)$/mi', $this->requests[$orig]['headers'], $match)) {
$this->requests[$orig]['location'] = trim($match[1]);
}
}
diff --git a/libraries/readability/Readability.php b/libraries/readability/Readability.php
index 5e1fd95..232a251 100644
--- a/libraries/readability/Readability.php
+++ b/libraries/readability/Readability.php
@@ -10,7 +10,7 @@
* More information: http://fivefilters.org/content-only/
* License: Apache License, Version 2.0
* Requires: PHP5
-* Date: 2011-07-22
+* Date: 2012-08-27
*
* Differences between the PHP port and the original
* ------------------------------------------------------
@@ -105,19 +105,24 @@ class Readability
* Create instance of Readability
* @param string UTF-8 encoded string
* @param string (optional) URL associated with HTML (used for footnotes)
+ * @param string which parser to use for turning raw HTML into a DOMDocument (either 'libxml' or 'html5lib')
*/
- function __construct($html, $url=null)
+ function __construct($html, $url=null, $parser='libxml')
{
+ $this->url = $url;
/* Turn all double br's into p's */
$html = preg_replace($this->regexps['replaceBrs'], '
', $html);
$html = preg_replace($this->regexps['replaceFonts'], '<$1span>', $html);
$html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8");
- $this->dom = new DOMDocument();
- $this->dom->preserveWhiteSpace = false;
- $this->dom->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
if (trim($html) == '') $html = '';
- @$this->dom->loadHTML($html);
- $this->url = $url;
+ if ($parser=='html5lib' && ($this->dom = HTML5_Parser::parse($html))) {
+ // all good
+ } else {
+ $this->dom = new DOMDocument();
+ $this->dom->preserveWhiteSpace = false;
+ @$this->dom->loadHTML($html);
+ }
+ $this->dom->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
}
/**
diff --git a/makefulltextfeed.php b/makefulltextfeed.php
index 782c90e..01a710b 100644
--- a/makefulltextfeed.php
+++ b/makefulltextfeed.php
@@ -1,10 +1,10 @@
'simplepie/simplepie.class.php',
- // 'SimplePie_Misc' => 'simplepie/simplepie.class.php',
- // 'SimplePie_HTTP_Parser' => 'simplepie/simplepie.class.php',
- // 'SimplePie_File' => 'simplepie/simplepie.class.php',
// Include FeedCreator for RSS/Atom creation
'FeedWriter' => 'feedwriter/FeedWriter.php',
'FeedItem' => 'feedwriter/FeedItem.php',
@@ -58,33 +55,33 @@ function autoload($class_name) {
'HumbleHttpAgent' => 'humble-http-agent/HumbleHttpAgent.php',
'SimplePie_HumbleHttpAgent' => 'humble-http-agent/SimplePie_HumbleHttpAgent.php',
'CookieJar' => 'humble-http-agent/CookieJar.php',
- // Include IRI class for resolving relative URLs
- // 'IRI' => 'iri/iri.php',
// Include Zend Cache to improve performance (cache results)
'Zend_Cache' => 'Zend/Cache.php',
- // Include Zend CSS to XPath for dealing with custom patterns
- 'Zend_Dom_Query_Css2Xpath' => 'Zend/Dom/Query/Css2Xpath.php',
// Language detect
- 'Text_LanguageDetect' => 'language-detect/LanguageDetect.php'
+ 'Text_LanguageDetect' => 'language-detect/LanguageDetect.php',
+ // HTML5 Lib
+ 'HTML5_Parser' => 'html5/Parser.php',
+ // htmLawed - used if XSS filter is enabled (xss_filter)
+ 'htmLawed' => 'htmLawed/htmLawed.php'
);
if (isset($mapping[$class_name])) {
- //echo "Loading $class_name\n ";
- require_once $mapping[$class_name];
+ debug("** Loading class $class_name ({$mapping[$class_name]})");
+ require $dir.$mapping[$class_name];
return true;
} else {
return false;
}
}
spl_autoload_register('autoload');
-require_once 'libraries/simplepie/SimplePieAutoloader.php';
+require dirname(__FILE__).'/libraries/simplepie/SimplePieAutoloader.php';
// always include Simplepie_Core as it defines constants which other SimplePie components
// assume will always be available.
-require_once 'libraries/simplepie/SimplePie/Core.php';
+require dirname(__FILE__).'/libraries/simplepie/SimplePie/Core.php';
////////////////////////////////
// Load config file
////////////////////////////////
-require_once(dirname(__FILE__).'/config.php');
+require dirname(__FILE__).'/config.php';
////////////////////////////////
// Prevent indexing/following by search engines because:
@@ -103,6 +100,44 @@ if (!$options->enabled) {
die('The full-text RSS service is currently disabled');
}
+////////////////////////////////
+// Debug mode?
+// See the config file for debug options.
+////////////////////////////////
+$debug_mode = false;
+if (isset($_GET['debug'])) {
+ if ($options->debug === true || $options->debug == 'user') {
+ $debug_mode = true;
+ } elseif ($options->debug == 'admin') {
+ session_start();
+ $debug_mode = (@$_SESSION['auth'] == 1);
+ }
+ if ($debug_mode) {
+ header('Content-Type: text/plain; charset=utf-8');
+ } else {
+ if ($options->debug == 'admin') {
+ die('You must be logged in to the admin area to see debug output.');
+ } else {
+ die('Debugging is disabled.');
+ }
+ }
+}
+
+////////////////////////////////
+// Check for APC
+////////////////////////////////
+$options->apc = $options->apc && function_exists('apc_add');
+if ($options->apc) {
+ debug('APC is enabled and available on server');
+} else {
+ debug('APC is disabled or not available on server');
+}
+
+////////////////////////////////
+// Check for smart cache
+////////////////////////////////
+$options->smart_cache = $options->smart_cache && function_exists('apc_inc');
+
////////////////////////////////
// Check for feed URL
////////////////////////////////
@@ -129,23 +164,7 @@ if ($test !== false && $test !== null && preg_match('!^https?://!', $url)) {
} else {
die('Invalid URL supplied');
}
-
-////////////////////////////////
-// Redirect to alternative URL?
-////////////////////////////////
-if ($options->alternative_url != '' && !isset($_GET['redir']) && mt_rand(0, 100) > 50) {
- $redirect = $options->alternative_url.'?redir=true&url='.urlencode($url);
- if (isset($_GET['html'])) $redirect .= '&html='.urlencode($_GET['html']);
- if (isset($_GET['key'])) $redirect .= '&key='.urlencode($_GET['key']);
- if (isset($_GET['max'])) $redirect .= '&max='.(int)$_GET['max'];
- if (isset($_GET['links'])) $redirect .= '&links='.$_GET['links'];
- if (isset($_GET['exc'])) $redirect .= '&exc='.$_GET['exc'];
- if (isset($_GET['what'])) $redirect .= '&what='.$_GET['what'];
- if (isset($_GET['format'])) $redirect .= '&format='.$_GET['format'];
- if (isset($_GET['l'])) $redirect .= '&format='.$_GET['l'];
- header("Location: $redirect");
- exit;
-}
+debug("Supplied URL: $url");
/////////////////////////////////
// Redirect to hide API key
@@ -153,16 +172,19 @@ if ($options->alternative_url != '' && !isset($_GET['redir']) && mt_rand(0, 100)
if (isset($_GET['key']) && ($key_index = array_search($_GET['key'], $options->api_keys)) !== false) {
$host = $_SERVER['HTTP_HOST'];
$path = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
- $redirect = 'http://'.htmlspecialchars($host.$path).'/makefulltextfeed.php?url='.urlencode($url);
+ $_qs_url = (strtolower(substr($url, 0, 7)) == 'http://') ? substr($url, 7) : $url;
+ $redirect = 'http://'.htmlspecialchars($host.$path).'/makefulltextfeed.php?url='.urlencode($_qs_url);
$redirect .= '&key='.$key_index;
$redirect .= '&hash='.urlencode(sha1($_GET['key'].$url));
if (isset($_GET['html'])) $redirect .= '&html='.urlencode($_GET['html']);
if (isset($_GET['max'])) $redirect .= '&max='.(int)$_GET['max'];
if (isset($_GET['links'])) $redirect .= '&links='.urlencode($_GET['links']);
if (isset($_GET['exc'])) $redirect .= '&exc='.urlencode($_GET['exc']);
- if (isset($_GET['what'])) $redirect .= '&what='.urlencode($_GET['what']);
if (isset($_GET['format'])) $redirect .= '&format='.urlencode($_GET['format']);
+ if (isset($_GET['callback'])) $redirect .= '&callback='.urlencode($_GET['callback']);
if (isset($_GET['l'])) $redirect .= '&l='.urlencode($_GET['l']);
+ if (isset($_GET['xss'])) $redirect .= '&xss';
+ if (isset($_GET['debug'])) $redirect .= '&debug';
header("Location: $redirect");
exit;
}
@@ -226,7 +248,7 @@ if (isset($_GET['max'])) {
///////////////////////////////////////////////
// Link handling
///////////////////////////////////////////////
-if (($valid_key || !$options->restrict) && isset($_GET['links']) && in_array($_GET['links'], array('preserve', 'footnotes', 'remove'))) {
+if (isset($_GET['links']) && in_array($_GET['links'], array('preserve', 'footnotes', 'remove'))) {
$links = $_GET['links'];
} else {
$links = 'preserve';
@@ -235,7 +257,7 @@ if (($valid_key || !$options->restrict) && isset($_GET['links']) && in_array($_G
///////////////////////////////////////////////
// Exclude items if extraction fails
///////////////////////////////////////////////
-if ($options->exclude_items_on_fail == 'user') {
+if ($options->exclude_items_on_fail === 'user') {
$exclude_on_fail = (isset($_GET['exc']) && ($_GET['exc'] == '1'));
} else {
$exclude_on_fail = $options->exclude_items_on_fail;
@@ -244,7 +266,7 @@ if ($options->exclude_items_on_fail == 'user') {
///////////////////////////////////////////////
// Detect language
///////////////////////////////////////////////
-if ((string)$options->detect_language == 'user') {
+if ($options->detect_language === 'user') {
if (isset($_GET['l'])) {
$detect_language = (int)$_GET['l'];
} else {
@@ -265,41 +287,6 @@ if ($detect_language >= 2) {
}
$use_cld = extension_loaded('cld') && (version_compare(PHP_VERSION, '5.3.0') >= 0);
-///////////////////////////////////////////////
-// Extraction pattern
-///////////////////////////////////////////////
-$auto_extract = true;
-if ($options->extraction_pattern == 'user') {
- $extract_pattern = (isset($_GET['what']) ? trim($_GET['what']) : 'auto');
-} else {
- $extract_pattern = trim($options->extraction_pattern);
-}
-if (($extract_pattern != '') && ($extract_pattern != 'auto')) {
- // split pattern by space (currently only descendants of 'auto' are recognised)
- $extract_pattern = preg_split('/\s+/', $extract_pattern, 2);
- if ($extract_pattern[0] == 'auto') { // parent selector is 'auto'
- $extract_pattern = $extract_pattern[1];
- } else {
- $extract_pattern = implode(' ', $extract_pattern);
- $auto_extract = false;
- }
- // Convert CSS to XPath
- // Borrowed from Symfony's cssToXpath() function: https://github.com/fabpot/symfony/blob/master/src/Symfony/Component/CssSelector/Parser.php
- // (Itself based on Python's lxml library)
- if (preg_match('#^\w+\s*$#u', $extract_pattern, $match)) {
- $extract_pattern = '//'.trim($match[0]);
- } elseif (preg_match('~^(\w*)#(\w+)\s*$~u', $extract_pattern, $match)) {
- $extract_pattern = sprintf("%s%s[@id = '%s']", '//', $match[1] ? $match[1] : '*', $match[2]);
- } elseif (preg_match('#^(\w*)\.(\w+)\s*$#u', $extract_pattern, $match)) {
- $extract_pattern = sprintf("%s%s[contains(concat(' ', normalize-space(@class), ' '), ' %s ')]", '//', $match[1] ? $match[1] : '*', $match[2]);
- } else {
- // if the patterns above do not match, invoke Zend's CSS to Xpath function
- $extract_pattern = Zend_Dom_Query_Css2Xpath::transform($extract_pattern);
- }
-} else {
- $extract_pattern = false;
-}
-
/////////////////////////////////////
// Check for valid format
// (stick to RSS (or RSS as JSON) for the time being)
@@ -310,57 +297,96 @@ if (isset($_GET['format']) && $_GET['format'] == 'json') {
$format = 'rss';
}
+/////////////////////////////////////
+// Should we do XSS filtering?
+/////////////////////////////////////
+if ($options->xss_filter === 'user') {
+ $xss_filter = isset($_GET['xss']);
+} else {
+ $xss_filter = $options->xss_filter;
+}
+if (!$xss_filter && isset($_GET['xss'])) {
+ die('XSS filtering is disabled in config');
+}
+
+/////////////////////////////////////
+// Check for JSONP
+// Regex from https://gist.github.com/1217080
+/////////////////////////////////////
+$callback = null;
+if ($format =='json' && isset($_GET['callback'])) {
+ $callback = trim($_GET['callback']);
+ foreach (explode('.', $callback) as $_identifier) {
+ if (!preg_match('/^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/', $_identifier)) {
+ die('Invalid JSONP callback');
+ }
+ }
+ debug("JSONP callback: $callback");
+}
+
+//////////////////////////////////
+// Enable Cross-Origin Resource Sharing (CORS)
+//////////////////////////////////
+if ($options->cors) header('Access-Control-Allow-Origin: *');
+
//////////////////////////////////
// Check for cached copy
//////////////////////////////////
if ($options->caching) {
- $frontendOptions = array(
- 'lifetime' => ($valid_key || !$options->restrict) ? 10*60 : 20*60, // cache lifetime of 10 or 20 minutes
- 'automatic_serialization' => false,
- 'write_control' => false,
- 'automatic_cleaning_factor' => $options->cache_cleanup,
- 'ignore_user_abort' => false
- );
- $backendOptions = array(
- 'cache_dir' => ($valid_key) ? $options->cache_dir.'/rss-with-key/' : $options->cache_dir.'/rss/', // directory where to put the cache files
- 'file_locking' => false,
- 'read_control' => true,
- 'read_control_type' => 'strlen',
- 'hashed_directory_level' => $options->cache_directory_level,
- 'hashed_directory_umask' => 0777,
- 'cache_file_umask' => 0664,
- 'file_name_prefix' => 'ff'
- );
-
- // getting a Zend_Cache_Core object
- $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
- $cache_id = md5($max.$url.$valid_key.$links.$exclude_on_fail.$auto_extract.$extract_pattern.$format.(int)isset($_GET['l']).(int)isset($_GET['pubsub']));
-
- if ($data = $cache->load($cache_id)) {
- if ($format == 'json') {
- header("Content-type: application/json; charset=UTF-8");
+ debug('Caching is enabled...');
+ $cache_id = md5($max.$url.$valid_key.$links.$xss_filter.$exclude_on_fail.$format.(int)isset($_GET['l']).(int)isset($_GET['pubsub']));
+ $check_cache = true;
+ if ($options->apc && $options->smart_cache) {
+ apc_add("cache.$cache_id", 0, 10*60);
+ $apc_cache_hits = (int)apc_fetch("cache.$cache_id");
+ $check_cache = ($apc_cache_hits >= 2);
+ apc_inc("cache.$cache_id");
+ if ($check_cache) {
+ debug('Cache key found in APC, we\'ll try to load cache file from disk');
} else {
- header("Content-type: text/xml; charset=UTF-8");
+ debug('Cache key not found in APC');
+ }
+ }
+ if ($check_cache) {
+ $cache = get_cache();
+ if ($data = $cache->load($cache_id)) {
+ if ($debug_mode) {
+ debug('Loaded cached copy');
+ exit;
+ }
+ if ($format == 'json') {
+ if ($callback === null) {
+ header('Content-type: application/json; charset=UTF-8');
+ } else {
+ header('Content-type: application/javascript; charset=UTF-8');
+ }
+ } else {
+ header('Content-type: text/xml; charset=UTF-8');
+ header('X-content-type-options: nosniff');
+ }
+ if (headers_sent()) die('Some data has already been output, can\'t send RSS file');
+ if ($callback) {
+ echo "$callback($data);";
+ } else {
+ echo $data;
+ }
+ exit;
}
- if (headers_sent()) die('Some data has already been output, can\'t send RSS file');
- echo $data;
- exit;
}
}
//////////////////////////////////
// Set Expires header
//////////////////////////////////
-if ($valid_key) {
+if (!$debug_mode) {
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+(60*10)) . ' GMT');
-} else {
- header('Expires: ' . gmdate('D, d M Y H:i:s', time()+(60*20)) . ' GMT');
}
//////////////////////////////////
// Set up HTTP agent
//////////////////////////////////
$http = new HumbleHttpAgent();
+$http->debug = $debug_mode;
$http->userAgentMap = $options->user_agents;
$http->headerOnlyTypes = array_keys($options->content_type_exc);
$http->rewriteUrls = $options->rewrite_url;
@@ -369,36 +395,18 @@ $http->rewriteUrls = $options->rewrite_url;
// Set up Content Extractor
//////////////////////////////////
$extractor = new ContentExtractor(dirname(__FILE__).'/site_config/custom', dirname(__FILE__).'/site_config/standard');
+$extractor->debug = $debug_mode;
+SiteConfig::$debug = $debug_mode;
+SiteConfig::use_apc($options->apc);
$extractor->fingerprints = $options->fingerprints;
-
-/*
-if ($options->caching) {
- $frontendOptions = array(
- 'lifetime' => 30*60, // cache lifetime of 30 minutes
- 'automatic_serialization' => true,
- 'write_control' => false,
- 'automatic_cleaning_factor' => $options->cache_cleanup,
- 'ignore_user_abort' => false
- );
- $backendOptions = array(
- 'cache_dir' => $options->cache_dir.'/http-responses/', // directory where to put the cache files
- 'file_locking' => false,
- 'read_control' => true,
- 'read_control_type' => 'strlen',
- 'hashed_directory_level' => $options->cache_directory_level,
- 'hashed_directory_umask' => 0777,
- 'cache_file_umask' => 0664,
- 'file_name_prefix' => 'ff'
- );
- $httpCache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
- $http->useCache($httpCache);
-}
-*/
+$extractor->allowedParsers = $options->allowed_parsers;
////////////////////////////////
// Get RSS/Atom feed
////////////////////////////////
if (!$html_only) {
+ debug('--------');
+ debug("Attempting to process URL as feed");
// Send user agent header showing PHP (prevents a HTML response from feedburner)
$http->userAgentDefault = HumbleHttpAgent::UA_PHP;
// configure SimplePie HTTP extension class to use our HumbleHttpAgent instance
@@ -434,6 +442,8 @@ if (!$html_only) {
////////////////////////////////////////////////////////////////////////////////
$isDummyFeed = false;
if ($html_only || !$result) {
+ debug('--------');
+ debug("Constructing a single-item feed from URL");
$isDummyFeed = true;
unset($feed, $result);
// create single item dummy feed object
@@ -478,12 +488,6 @@ $output->setLink($feed->get_link()); // Google Reader uses this for pulling in f
if ($img_url = $feed->get_image_url()) {
$output->setImage($feed->get_title(), $feed->get_link(), $img_url);
}
-/*
-if ($format == 'atom') {
- $output->setChannelElement('updated', date(DATE_ATOM));
- $output->setChannelElement('author', array('name'=>'Five Filters', 'uri'=>'http://fivefilters.org'));
-}
-*/
////////////////////////////////////////////
// Loop through feed items
@@ -504,6 +508,8 @@ foreach ($items as $key => $item) {
}
$urls[$key] = $permalink;
}
+debug('--------');
+debug('Fetching feed items');
$http->fetchAll($urls_sanitized);
//$http->cacheAll();
@@ -511,10 +517,13 @@ $http->fetchAll($urls_sanitized);
$item_count = 0;
foreach ($items as $key => $item) {
+ debug('--------');
+ debug('Processing feed item '.($item_count+1));
$do_content_extraction = true;
$extract_result = false;
$text_sample = null;
$permalink = $urls[$key];
+ debug("Item URL: $permalink");
$newitem = $output->createNewItem();
$newitem->setTitle(htmlspecialchars_decode($item->get_title()));
if ($valid_key && isset($_GET['pubsub'])) { // used only on fivefilters.org at the moment
@@ -537,57 +546,91 @@ foreach ($items as $key => $item) {
$effective_url = $response['effective_url'];
if (!url_allowed($effective_url)) continue;
// check if action defined for returned Content-Type
- $type = null;
- if (preg_match('!^Content-Type:\s*(([-\w]+)/([-\w\+]+))!im', $response['headers'], $match)) {
- // look for full mime type (e.g. image/jpeg) or just type (e.g. image)
- $match[1] = strtolower(trim($match[1]));
- $match[2] = strtolower(trim($match[2]));
- foreach (array($match[1], $match[2]) as $_mime) {
- if (isset($options->content_type_exc[$_mime])) {
- $type = $match[1];
- $_act = $options->content_type_exc[$_mime]['action'];
- $_name = $options->content_type_exc[$_mime]['name'];
- if ($_act == 'exclude') {
- continue 2; // skip this feed item entry
- } elseif ($_act == 'link') {
- if ($match[2] == 'image') {
- $html = "";
- } else {
- $html = "Download $_name";
- }
- $title = $_name;
- $do_content_extraction = false;
- break;
- }
+ $mime_info = get_mime_action_info($response['headers']);
+ if (isset($mime_info['action'])) {
+ if ($mime_info['action'] == 'exclude') {
+ continue; // skip this feed item entry
+ } elseif ($mime_info['action'] == 'link') {
+ if ($mime_info['type'] == 'image') {
+ $html = "";
+ } else {
+ $html = "Download {$mime_info['name']}";
}
+ $title = $mime_info['name'];
+ $do_content_extraction = false;
}
- unset($_mime, $_act, $_name, $match);
}
if ($do_content_extraction) {
$html = $response['body'];
// remove strange things
$html = str_replace('[>', '', $html);
$html = convert_to_utf8($html, $response['headers']);
- if ($auto_extract) {
- // check site config for single page URL - fetch it if found
- if ($single_page_response = getSinglePage($item, $html, $effective_url)) {
- $html = $single_page_response['body'];
- // remove strange things
- $html = str_replace('[>', '', $html);
- $html = convert_to_utf8($html, $single_page_response['headers']);
- $effective_url = $single_page_response['effective_url'];
- unset($single_page_response);
+ // check site config for single page URL - fetch it if found
+ $is_single_page = false;
+ if ($single_page_response = getSinglePage($item, $html, $effective_url)) {
+ $is_single_page = true;
+ $html = $single_page_response['body'];
+ // remove strange things
+ $html = str_replace('[>', '', $html);
+ $html = convert_to_utf8($html, $single_page_response['headers']);
+ $effective_url = $single_page_response['effective_url'];
+ debug("Retrieved single-page view from $effective_url");
+ unset($single_page_response);
+ }
+ debug('--------');
+ debug('Attempting to extract content');
+ $extract_result = $extractor->process($html, $effective_url);
+ $readability = $extractor->readability;
+ $content_block = ($extract_result) ? $extractor->getContent() : null;
+ $title = ($extract_result) ? $extractor->getTitle() : '';
+ // Deal with multi-page articles
+ //die('Next: '.$extractor->getNextPageUrl());
+ $is_multi_page = (!$is_single_page && $extract_result && $extractor->getNextPageUrl());
+ if ($options->multipage && $is_multi_page) {
+ debug('--------');
+ debug('Attempting to process multi-page article');
+ $multi_page_urls = array();
+ $multi_page_content = array();
+ while ($next_page_url = $extractor->getNextPageUrl()) {
+ debug('--------');
+ debug('Processing next page: '.$next_page_url);
+ // If we've got URL, resolve against $url
+ if ($next_page_url = makeAbsoluteStr($effective_url, $next_page_url)) {
+ // check it's not what we have already!
+ if (!in_array($next_page_url, $multi_page_urls)) {
+ // it's not, so let's attempt to fetch it
+ $multi_page_urls[] = $next_page_url;
+ $_prev_ref = $http->referer;
+ if (($response = $http->get($next_page_url, true)) && $response['status_code'] < 300) {
+ // make sure mime type is not something with a different action associated
+ $page_mime_info = get_mime_action_info($response['headers']);
+ if (!isset($page_mime_info['action'])) {
+ $html = $response['body'];
+ // remove strange things
+ $html = str_replace('[>', '', $html);
+ $html = convert_to_utf8($html, $response['headers']);
+ if ($extractor->process($html, $next_page_url)) {
+ $multi_page_content[] = $extractor->getContent();
+ continue;
+ } else { debug('Failed to extract content'); }
+ } else { debug('MIME type requires different action'); }
+ } else { debug('Failed to fetch URL'); }
+ } else { debug('URL already processed'); }
+ } else { debug('Failed to resolve against '.$effective_url); }
+ // failed to process next_page_url, so cancel further requests
+ $multi_page_content = array();
+ break;
}
- $extract_result = $extractor->process($html, $effective_url);
- $readability = $extractor->readability;
- $content_block = ($extract_result) ? $extractor->getContent() : null;
- $title = ($extract_result) ? $extractor->getTitle() : '';
- } else {
- $readability = new Readability($html, $effective_url);
- // content block is entire document (for now...)
- $content_block = $readability->dom;
- //TODO: get title
- $title = '';
+ // did we successfully deal with this multi-page article?
+ if (empty($multi_page_content)) {
+ debug('Failed to extract all parts of multi-page article, so not going to include them');
+ $multi_page_content[] = $readability->dom->createElement('p')->innerHTML = 'This article appears to continue on subsequent pages which we could not extract';
+ }
+ foreach ($multi_page_content as $_page) {
+ $_page = $content_block->ownerDocument->importNode($_page, true);
+ $content_block->appendChild($_page);
+ }
+ unset($multi_page_urls, $multi_page_content, $page_mime_info, $next_page_url);
}
}
// use extracted title for both feed and item title if we're using single-item dummy feed
@@ -595,32 +638,16 @@ foreach ($items as $key => $item) {
$output->setTitle($title);
$newitem->setTitle($title);
}
- if ($do_content_extraction) {
- if ($extract_pattern && isset($content_block)) {
- $xpath = new DOMXPath($readability->dom);
- $elems = @$xpath->query($extract_pattern, $content_block);
- // check if our custom extraction pattern matched
- if ($elems && $elems->length > 0) {
- $extract_result = true;
- // get the first matched element
- $content_block = $elems->item(0);
- // clean it up
- $readability->removeScripts($content_block);
- $readability->prepArticle($content_block);
- }
- }
- }
}
if ($do_content_extraction) {
// if we failed to extract content...
if (!$extract_result) {
- if ($exclude_on_fail) continue; // skip this and move to next item
- //TODO: get text sample for language detection
- if (!$valid_key) {
- $html = $options->error_message;
- } else {
- $html = $options->error_message_with_key;
+ if ($exclude_on_fail) {
+ debug('Failed to extract, so skipping (due to exclude on fail parameter)');
+ continue; // skip this and move to next item
}
+ //TODO: get text sample for language detection
+ $html = $options->error_message;
// keep the original item description
$html .= $item->get_description();
} else {
@@ -630,16 +657,21 @@ foreach ($items as $key => $item) {
if (($links == 'footnotes') && (strpos($effective_url, 'wikipedia.org') === false)) {
$readability->addFootnotes($content_block);
}
- if ($extract_pattern) {
- // get outerHTML
- $html = $content_block->ownerDocument->saveXML($content_block);
- } else {
- if ($content_block->childNodes->length == 1 && $content_block->firstChild->nodeType === XML_ELEMENT_NODE) {
- $html = $content_block->firstChild->innerHTML;
- } else {
- $html = $content_block->innerHTML;
- }
+ // remove nesting:
test
=
test
+ while ($content_block->childNodes->length == 1 && $content_block->firstChild->nodeType === XML_ELEMENT_NODE) {
+ // only follow these tag names
+ if (!in_array(strtolower($content_block->tagName), array('div', 'article', 'section', 'header', 'footer'))) break;
+ //$html = $content_block->firstChild->innerHTML; // FTR 2.9.5
+ $content_block = $content_block->firstChild;
}
+ // convert content block to HTML string
+ // Need to preserve things like body: //img[@id='feature']
+ if (in_array(strtolower($content_block->tagName), array('div', 'article', 'section', 'header', 'footer'))) {
+ $html = $content_block->innerHTML;
+ } else {
+ $html = $content_block->ownerDocument->saveXML($content_block); // essentially outerHTML
+ }
+ unset($content_block);
// post-processing cleanup
$html = preg_replace('!
[\s\h\v]*
!u', '', $html);
if ($links == 'remove') {
@@ -647,29 +679,21 @@ foreach ($items as $key => $item) {
}
// get text sample for language detection
$text_sample = strip_tags(substr($html, 0, 500));
- if (!$valid_key) {
- $html = make_substitutions($options->message_to_prepend).$html;
- $html .= make_substitutions($options->message_to_append);
- } else {
- $html = make_substitutions($options->message_to_prepend_with_key).$html;
- $html .= make_substitutions($options->message_to_append_with_key);
- }
+ $html = make_substitutions($options->message_to_prepend).$html;
+ $html .= make_substitutions($options->message_to_append);
}
}
- /*
- if ($format == 'atom') {
- $newitem->addElement('content', $html);
- $newitem->setDate((int)$item->get_date('U'));
- if ($author = $item->get_author()) {
- $newitem->addElement('author', array('name'=>$author->get_name()));
- }
- } else {
- */
+
if ($valid_key && isset($_GET['pubsub'])) { // used only on fivefilters.org at the moment
$newitem->addElement('guid', 'http://fivefilters.org/content-only/redirect.php?url='.urlencode($item->get_permalink()), array('isPermaLink'=>'false'));
} else {
$newitem->addElement('guid', $item->get_permalink(), array('isPermaLink'=>'true'));
}
+ // filter xss?
+ if ($xss_filter) {
+ debug('Filtering HTML to remove XSS');
+ $html = htmLawed::hl($html, array('safe'=>1, 'deny_attribute'=>'style', 'comment'=>1, 'cdata'=>1));
+ }
$newitem->setDescription($html);
// set date
@@ -687,8 +711,11 @@ foreach ($items as $key => $item) {
} elseif ($authors = $extractor->getAuthors()) {
//TODO: make sure the list size is reasonable
foreach ($authors as $author) {
- //TODO: addElement replaces this element each time
+ //TODO: addElement replaces this element each time.
+ // xpath often selects authors from other articles linked from the page.
+ // for now choose first item
$newitem->addElement('dc:creator', $author);
+ break;
}
}
@@ -727,7 +754,7 @@ foreach ($items as $key => $item) {
}
// add MIME type (if it appeared in our exclusions lists)
- if (isset($type)) $newitem->addElement('dc:format', $type);
+ if (isset($mime_info['mime'])) $newitem->addElement('dc:format', $mime_info['mime']);
// add effective URL (URL after redirects)
if (isset($effective_url)) {
//TODO: ensure $effective_url is valid witout - sometimes it causes problems, e.g.
@@ -769,20 +796,50 @@ foreach ($items as $key => $item) {
}
// output feed
-if ($format == 'json') $output->setFormat(JSON);
-if ($options->caching) {
- ob_start();
- $output->genarateFeed();
- $output = ob_get_contents();
- ob_end_clean();
- if ($html_only && $item_count == 0) {
- // do not cache - in case of temporary server glitch at source URL
- } else {
- $cache->save($output, $cache_id);
+debug('Done!');
+/*
+if ($debug_mode) {
+ $_apc_data = apc_cache_info('user');
+ var_dump($_apc_data); exit;
+}
+*/
+if (!$debug_mode) {
+ if ($callback) echo "$callback("; // if $callback is set, $format also == 'json'
+ if ($format == 'json') $output->setFormat(($callback === null) ? JSON : JSONP);
+ $add_to_cache = $options->caching;
+ // is smart cache mode enabled?
+ if ($add_to_cache && $options->apc && $options->smart_cache) {
+ // yes, so only cache if this is the second request for this URL
+ $add_to_cache = ($apc_cache_hits >= 2);
+ // purge cache
+ if ($options->cache_cleanup > 0) {
+ if (rand(1, $options->cache_cleanup) == 1) {
+ // apc purge code adapted from from http://www.thimbleopensource.com/tutorials-snippets/php-apc-expunge-script
+ $_apc_data = apc_cache_info('user');
+ foreach ($_apc_data['cache_list'] as $_apc_item) {
+ if ($_apc_item['ttl'] > 0 && ($_apc_item['ttl'] + $_apc_item['creation_time'] < time())) {
+ apc_delete($_apc_item['info']);
+ }
+ }
+ }
+ }
}
- echo $output;
-} else {
- $output->genarateFeed();
+ if ($add_to_cache) {
+ ob_start();
+ $output->genarateFeed();
+ $output = ob_get_contents();
+ ob_end_clean();
+ if ($html_only && $item_count == 0) {
+ // do not cache - in case of temporary server glitch at source URL
+ } else {
+ $cache = get_cache();
+ if ($add_to_cache) $cache->save($output, $cache_id);
+ }
+ echo $output;
+ } else {
+ $output->genarateFeed();
+ }
+ if ($callback) echo ');';
}
///////////////////////////////
@@ -822,6 +879,7 @@ function convert_to_utf8($html, $header=null)
if (is_array($header)) $header = implode("\n", $header);
if (!$header || !preg_match_all('/^Content-Type:\s+([^;]+)(?:;\s*charset=["\']?([^;"\'\n]*))?/im', $header, $match, PREG_SET_ORDER)) {
// error parsing the response
+ debug('Could not find Content-Type header in HTTP response');
} else {
$match = end($match); // get last matched element (in case of redirects)
if (isset($match[2])) $encoding = trim($match[2], "\"' \r\n\0\x0B\t");
@@ -831,8 +889,10 @@ function convert_to_utf8($html, $header=null)
// For now we'll check for invalid encoding types returned by some sites, e.g. 'none'
// Problem URL: http://facta.co.jp/blog/archives/20111026001026.html
if (!$encoding || $encoding == 'none') {
- // search for encoding in HTML - only look at the first 35000 characters
- $html_head = substr($html, 0, 40000);
+ // search for encoding in HTML - only look at the first 50000 characters
+ // Why 50000? See, for example, http://www.lemonde.fr/festival-de-cannes/article/2012/05/23/deux-cretes-en-goguette-sur-la-croisette_1705732_766360.html
+ // TODO: improve this so it looks at smaller chunks first
+ $html_head = substr($html, 0, 50000);
if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $html_head, $match)) {
$encoding = trim($match[1], '"\'');
} elseif (preg_match('/]+)/i', $html_head, $match)) {
@@ -876,11 +936,14 @@ function convert_to_utf8($html, $header=null)
$trans[chr(156)] = 'œ'; // Latin Small Ligature OE
$trans[chr(159)] = 'Ÿ'; // Latin Capital Letter Y With Diaeresis
$html = strtr($html, $trans);
- }
+ }
if (!$encoding) {
+ debug('No character encoding found, so treating as UTF-8');
$encoding = 'utf-8';
} else {
+ debug('Character encoding: '.$encoding);
if (strtolower($encoding) != 'utf-8') {
+ debug('Converting to UTF-8');
$html = SimplePie_Misc::change_encoding($html, $encoding, 'utf-8');
/*
if (function_exists('iconv')) {
@@ -941,19 +1004,8 @@ function makeAbsoluteStr($base, $url) {
// returns single page response, or false if not found
function getSinglePage($item, $html, $url) {
global $http, $extractor;
- $host = @parse_url($url, PHP_URL_HOST);
- $site_config = SiteConfig::build($host);
- if ($site_config === false) {
- // check for fingerprints
- if (!empty($extractor->fingerprints) && ($_fphost = $extractor->findHostUsingFingerprints($html))) {
- $site_config = SiteConfig::build($_fphost);
- }
- if ($site_config === false) $site_config = new SiteConfig();
- SiteConfig::add_to_cache($host, $site_config);
- return false;
- } else {
- SiteConfig::add_to_cache($host, $site_config);
- }
+ debug('Looking for site config files to see if single page link exists');
+ $site_config = $extractor->buildSiteConfig($url, $html);
$splink = null;
if (!empty($site_config->single_page_link)) {
$splink = $site_config->single_page_link;
@@ -978,10 +1030,10 @@ function getSinglePage($item, $html, $url) {
foreach ($elems as $item) {
if ($item instanceof DOMElement && $item->hasAttribute('href')) {
$single_page_url = $item->getAttribute('href');
- break;
+ break 2;
} elseif ($item instanceof DOMAttr && $item->value) {
$single_page_url = $item->value;
- break;
+ break 2;
}
}
}
@@ -1004,6 +1056,33 @@ function getSinglePage($item, $html, $url) {
return false;
}
+// based on content-type http header, decide what to do
+// param: HTTP headers string
+// return: array with keys: 'mime', 'type', 'subtype', 'action', 'name'
+// e.g. array('mime'=>'image/jpeg', 'type'=>'image', 'subtype'=>'jpeg', 'action'=>'link', 'name'=>'Image')
+function get_mime_action_info($headers) {
+ global $options;
+ // check if action defined for returned Content-Type
+ $info = array();
+ if (preg_match('!^Content-Type:\s*(([-\w]+)/([-\w\+]+))!im', $headers, $match)) {
+ // look for full mime type (e.g. image/jpeg) or just type (e.g. image)
+ // match[1] = full mime type, e.g. image/jpeg
+ // match[2] = first part, e.g. image
+ // match[3] = last part, e.g. jpeg
+ $info['mime'] = strtolower(trim($match[1]));
+ $info['type'] = strtolower(trim($match[2]));
+ $info['subtype'] = strtolower(trim($match[3]));
+ foreach (array($info['mime'], $info['type']) as $_mime) {
+ if (isset($options->content_type_exc[$_mime])) {
+ $info['action'] = $options->content_type_exc[$_mime]['action'];
+ $info['name'] = $options->content_type_exc[$_mime]['name'];
+ break;
+ }
+ }
+ }
+ return $info;
+}
+
function remove_url_cruft($url) {
// remove google analytics for the time being
// regex adapted from http://navitronic.co.uk/2010/12/removing-google-analytics-cruft-from-urls/
@@ -1018,4 +1097,39 @@ function make_substitutions($string) {
$string = str_replace('{effective-url}', htmlspecialchars($effective_url), $string);
return $string;
}
-?>
\ No newline at end of file
+
+function get_cache() {
+ global $options, $valid_key;
+ static $cache = null;
+ if ($cache === null) {
+ $frontendOptions = array(
+ 'lifetime' => 10*60, // cache lifetime of 10 minutes
+ 'automatic_serialization' => false,
+ 'write_control' => false,
+ 'automatic_cleaning_factor' => $options->cache_cleanup,
+ 'ignore_user_abort' => false
+ );
+ $backendOptions = array(
+ 'cache_dir' => ($valid_key) ? $options->cache_dir.'/rss-with-key/' : $options->cache_dir.'/rss/', // directory where to put the cache files
+ 'file_locking' => false,
+ 'read_control' => true,
+ 'read_control_type' => 'strlen',
+ 'hashed_directory_level' => $options->cache_directory_level,
+ 'hashed_directory_perm' => 0777,
+ 'cache_file_perm' => 0664,
+ 'file_name_prefix' => 'ff'
+ );
+ // getting a Zend_Cache_Core object
+ $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
+ }
+ return $cache;
+}
+
+function debug($msg) {
+ global $debug_mode;
+ if ($debug_mode) {
+ echo '* ',$msg,"\n";
+ ob_flush();
+ flush();
+ }
+}
\ No newline at end of file
diff --git a/manifest.yml b/manifest.yml
new file mode 100644
index 0000000..551d916
--- /dev/null
+++ b/manifest.yml
@@ -0,0 +1,14 @@
+---
+applications:
+ .:
+ name: full-text-rss
+ framework:
+ name: php
+ info:
+ mem: 512M
+ description: PHP Application
+ exec:
+ infra: aws
+ url: ${name}.${target-base}
+ mem: 512M
+ instances: 1
diff --git a/site_config/standard/version.php b/site_config/standard/version.php
index e61807e..34a8735 100644
--- a/site_config/standard/version.php
+++ b/site_config/standard/version.php
@@ -1,2 +1 @@
-
\ No newline at end of file
diff --git a/site_config/standard/version.txt b/site_config/standard/version.txt
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/site_config/standard/version.txt
@@ -0,0 +1 @@
+4
\ No newline at end of file