Repository: Dither/full-text-rss Branch: master Commit: c155b628eba2 Files: 100 Total size: 1.6 MB Directory structure: gitextract_1gv9702v/ ├── README.md ├── README.txt ├── UPDATING.txt ├── admin/ │ ├── apc.php │ ├── codemirror/ │ │ ├── codemirror.css │ │ ├── codemirror.js │ │ └── properties.js │ ├── edit-pattern.php │ ├── index.php │ ├── login.php │ ├── require_login.php │ ├── template.php │ └── update.php ├── cache/ │ ├── index.php │ ├── rss/ │ │ └── index.php │ └── urls/ │ └── index.php ├── changelog.txt ├── cleancache.php ├── config.php ├── css/ │ ├── feed.css │ └── feed.xsl ├── custom_config.php.demo ├── ftr_compatibility_test.php ├── index.php ├── js/ │ ├── bootstrap-popover.js │ ├── bootstrap-tab.js │ └── bootstrap-tooltip.js ├── libraries/ │ ├── Zend/ │ │ ├── Cache/ │ │ │ ├── Backend/ │ │ │ │ ├── ExtendedInterface.php │ │ │ │ ├── File.php │ │ │ │ └── Interface.php │ │ │ ├── Backend.php │ │ │ ├── Core.php │ │ │ └── Exception.php │ │ ├── Cache.php │ │ └── Exception.php │ ├── content-extractor/ │ │ ├── ContentExtractor.php │ │ └── SiteConfig.php │ ├── feedwriter/ │ │ ├── FeedItem.php │ │ └── FeedWriter.php │ ├── htmLawed/ │ │ └── htmLawed2.php │ ├── html5/ │ │ ├── Data.php │ │ ├── InputStream.php │ │ ├── Parser.php │ │ ├── Tokenizer.php │ │ ├── TreeBuilder.php │ │ └── named-character-references.ser │ ├── humble-http-agent/ │ │ ├── CookieJar.php │ │ ├── HumbleHttpAgent.php │ │ ├── RollingCurl.php │ │ └── SimplePie_HumbleHttpAgent.php │ ├── language-detect/ │ │ ├── LanguageDetect/ │ │ │ ├── Exception.php │ │ │ ├── ISO639.php │ │ │ └── Parser.php │ │ └── LanguageDetect.php │ ├── readability/ │ │ ├── ImageCaching.php │ │ ├── JSLikeHTMLElement.php │ │ └── Readability.php │ └── simplepie/ │ ├── LICENSE.txt │ ├── autoloader.php │ └── library/ │ ├── SimplePie/ │ │ ├── Author.php │ │ ├── Cache/ │ │ │ ├── Base.php │ │ │ ├── DB.php │ │ │ ├── File.php │ │ │ ├── Memcache.php │ │ │ └── MySQL.php │ │ ├── Cache.php │ │ ├── Caption.php │ │ ├── Category.php │ │ ├── Content/ │ │ │ └── Type/ │ │ │ └── Sniffer.php │ │ ├── Copyright.php │ │ ├── Core.php │ │ ├── Credit.php │ │ ├── Decode/ │ │ │ └── HTML/ │ │ │ └── Entities.php │ │ ├── Enclosure.php │ │ ├── Exception.php │ │ ├── File.php │ │ ├── HTTP/ │ │ │ └── Parser.php │ │ ├── IRI.php │ │ ├── Item.php │ │ ├── Locator.php │ │ ├── Misc.php │ │ ├── Net/ │ │ │ └── IPv6.php │ │ ├── Parse/ │ │ │ └── Date.php │ │ ├── Parser.php │ │ ├── Rating.php │ │ ├── Registry.php │ │ ├── Restriction.php │ │ ├── Sanitize.php │ │ ├── Source.php │ │ ├── XML/ │ │ │ └── Declaration/ │ │ │ └── Parser.php │ │ └── gzdecode.php │ └── SimplePie.php ├── license.txt ├── makefulltextfeed.php ├── manifest.yml └── site_config/ ├── README.txt ├── custom/ │ └── index.php ├── index.php └── standard/ ├── .wikipedia.org.txt └── version.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ Full-Text RSS ============= ### NOTE This is a our public version of Full-Text RSS available to download for free from . For best extraction results, and to help us sustain the project, you can purchase the most up-to-date version at - so if you like this free version, please consider supporting us by purchasing the latest release. If you have no need for the latest release, but would still like to contribute something, you can donate via [Gittip](https://www.gittip.com/fivefilters/) or [Flattr](https://flattr.com/profile/k1m). ### About See for a description of the code. ### Installation 1. Extract the files in this ZIP archive to a folder on your computer. 2. FTP the files up to your server 3. Access index.php through your browser. E.g. http://example.org/full-text-rss/index.php 4. Enter a URL in the form field to test the code 5. If you get an RSS feed with full-text content, all is working well. :) ### Configuration (optional) 1. Save a copy of config.php as custom_config.php and edit custom_config.php 2. If you decide to enable caching, make sure the cache folder (and its 2 sub folders) is writable. (You might need to change the permissions of these folders to 777 through your FTP client.) ### Site-specific extraction rules This free version does not contain the site config files we include with purchased copies, but these are now all available [online](https://github.com/fivefilters/ftr-site-config). If you'd like to keep yours up to date using Git, follow the steps below: 1. Change into the site_config/standard/ folder 2. Delete everything in there 3. Using the command line, enter: `git clone https://github.com/fivefilters/ftr-site-config.git .` 4. Git should now download the latest site config files for you. 5. To update the site config files again, you can simply run `git pull` from the directory. ### Code example If you're developing an application which requires content extraction, you can call Full-Text RSS as a web service from within your application. Here's how to do it in PHP: rss->channel->item); $title = $json->rss->channel->item->title; // Note: this works when you're processing an article. // If the input URL is a feed, ->item will be an array. echo $title; ### Different language? Although we don't have examples in other programming languages, the essential steps should be: 1. Construct the request URL using URL where you installed Full-Text RSS and the article or feed URL (see $ftr, $article, $request in example above). 2. Fetch the resulting URL using an HTTP GET request. 3. Parse the HTTP response body as JSON and grab what you need. ================================================ FILE: README.txt ================================================ Full-Text RSS ============= About ----- See http://fivefilters.org/content-only/ for a description of the code. Installation ------------ 1. Extract the files in this ZIP archive to a folder on your computer. 2. FTP the files up to your server 3. Access index.php through your browser. E.g. http://example.org/full-text-rss/index.php 4. Enter a URL in the form field to test the code 5. If you get an RSS feed with full-text content, all is working well. :) Configuration (optional) ------------------------ 1. Save a copy of config.php as custom_config.php and edit custom_config.php 2. If you decide to enable caching, make sure the cache folder (and its 2 sub folders) is writable. (You might need to change the permissions of these folders to 777 through your FTP client.) 3. If you want to use the admin area to edit/update your site config files, make sure the site_config folder (and its 2 sub folders) is writable. (You might need to change the permissions of these folders to 777 through your FTP client.) Help ---- Please visit http://help.fivefilters.org ================================================ FILE: UPDATING.txt ================================================ Updating Full-Text RSS ====================== To update your copy of Full-Text RSS to ensure feeds continue to be processed as they were before, we suggest the following steps: 1. Keep your current installation in place for now (we'll deal with it later) 2. Extract this updated package to a new folder -- for example, if the last version is in a folder called 'full-text-rss', extract this version to a new folder called 'full-text-rss-updated' 3. Upload the new folder to your server 4. Access index.php in the new folder through your browser -- for example http://example.org/full-text-rss-updated/index.php 5. Enter a URL in the form field to test the updated code 6. If you'd configured the last version, copy custom_config.php from your old version to the new installation. 7. If you'd added custom site config files (in site_config/custom/), copy those over to the new installation. 8. If you'd enabled caching, make sure the cache folder (and its 2 sub folders) is writable. (You might need to change the permissions of these folders to 777 through your FTP client.) 9. Test the new copy again to make sure the config values are now applied to the new version. 10. Now simply rename the folder with your old copy to 'full-text-rss-old' and then rename the folder with the new copy to 'full-text-rss' (or whatever name you'd given the original folder). That's all that's needed. Your feeds should continue to work as they did before. Let us know if you have any trouble: help@fivefilters.org. Updating Site Patterns ====================== Site patterns are used by Full-Text RSS to improve extraction for certain sites. These are simple text files which are updated more frequently than the Full-Text RSS software itself. To make sure your copy of Full-Text RSS is using the latest site patterns, We've created a simple tool to help you get the latest copy of these site patterns from FiveFilters.org. To use it, access the admin/ folder in your browser and follow the instructions. Alternatively, you can download the latest set of site config files via GitHub: https://github.com/fivefilters/ftr-site-config ================================================ FILE: admin/apc.php ================================================ | | Rasmus Lerdorf | | Ilia Alshanetsky | +----------------------------------------------------------------------+ All other licensing and usage conditions are those of the PHP Group. */ $VERSION='$Id: apc.php 307048 2011-01-03 23:53:17Z kalle $'; ////////// READ OPTIONAL CONFIGURATION FILE //////////// if (file_exists("apc.conf.php")) include("apc.conf.php"); //////////////////////////////////////////////////////// //////////////////////////////// // Load config file //////////////////////////////// $admin_page = 'apc'; require_once('../config.php'); require_once('require_login.php'); require_once('template.php'); if (!isset($_REQUEST['IMG'])) tpl_header('APC'); ////////// BEGIN OF DEFAULT CONFIG AREA /////////////////////////////////////////////////////////// defaults('USE_AUTHENTICATION',0); // Use (internal) authentication - best choice if // no other authentication is available // If set to 0: // There will be no further authentication. You // will have to handle this by yourself! // If set to 1: // You need to change ADMIN_PASSWORD to make // this work! //defaults('ADMIN_USERNAME','admin'); // Admin Username //defaults('ADMIN_PASSWORD',''); // Admin Password - CHANGE THIS TO ENABLE!!! // (beckerr) I'm using a clear text password here, because I've no good idea how to let // users generate a md5 or crypt password in a easy way to fill it in above //defaults('DATE_FORMAT', "d.m.Y H:i:s"); // German defaults('DATE_FORMAT', 'Y/m/d H:i:s'); // US defaults('GRAPH_SIZE',200); // Image size //defaults('PROXY', 'tcp://127.0.0.1:8080'); ////////// END OF DEFAULT CONFIG AREA ///////////////////////////////////////////////////////////// // "define if not defined" function defaults($d,$v) { if (!defined($d)) define($d,$v); // or just @define(...) } // rewrite $PHP_SELF to block XSS attacks // $PHP_SELF= isset($_SERVER['PHP_SELF']) ? htmlentities(strip_tags($_SERVER['PHP_SELF'],''), ENT_QUOTES, 'UTF-8') : ''; $time = time(); $host = php_uname('n'); if($host) { $host = '('.$host.')'; } if (isset($_SERVER['SERVER_ADDR'])) { $host .= ' ('.$_SERVER['SERVER_ADDR'].')'; } // operation constants define('OB_HOST_STATS',1); define('OB_SYS_CACHE',2); define('OB_USER_CACHE',3); define('OB_SYS_CACHE_DIR',4); define('OB_VERSION_CHECK',9); // check validity of input variables $vardom=array( 'OB' => '/^\d+$/', // operational mode switch 'CC' => '/^[01]$/', // clear cache requested 'DU' => '/^.*$/', // Delete User Key 'SH' => '/^[a-z0-9]+$/', // shared object description 'IMG' => '/^[123]$/', // image to generate 'LO' => '/^1$/', // login requested 'COUNT' => '/^\d+$/', // number of line displayed in list 'SCOPE' => '/^[AD]$/', // list view scope 'SORT1' => '/^[AHSMCDTZ]$/', // first sort key 'SORT2' => '/^[DA]$/', // second sort key 'AGGR' => '/^\d+$/', // aggregation by dir level 'SEARCH' => '~^[a-zA-Z0-1/_.-]*$~' // aggregation by dir level ); // default cache mode $cache_mode='opcode'; // cache scope $scope_list=array( 'A' => 'cache_list', 'D' => 'deleted_list' ); // handle POST and GET requests if (empty($_REQUEST)) { if (!empty($_GET) && !empty($_POST)) { $_REQUEST = array_merge($_GET, $_POST); } else if (!empty($_GET)) { $_REQUEST = $_GET; } else if (!empty($_POST)) { $_REQUEST = $_POST; } else { $_REQUEST = array(); } } // check parameter syntax foreach($vardom as $var => $dom) { if (!isset($_REQUEST[$var])) { $MYREQUEST[$var]=NULL; } else if (!is_array($_REQUEST[$var]) && preg_match($dom.'D',$_REQUEST[$var])) { $MYREQUEST[$var]=$_REQUEST[$var]; } else { $MYREQUEST[$var]=$_REQUEST[$var]=NULL; } } // check parameter sematics if (empty($MYREQUEST['SCOPE'])) $MYREQUEST['SCOPE']="A"; if (empty($MYREQUEST['SORT1'])) $MYREQUEST['SORT1']="H"; if (empty($MYREQUEST['SORT2'])) $MYREQUEST['SORT2']="D"; if (empty($MYREQUEST['OB'])) $MYREQUEST['OB']=OB_HOST_STATS; if (!isset($MYREQUEST['COUNT'])) $MYREQUEST['COUNT']=20; if (!isset($scope_list[$MYREQUEST['SCOPE']])) $MYREQUEST['SCOPE']='A'; $MY_SELF= "$PHP_SELF". "?SCOPE=".$MYREQUEST['SCOPE']. "&SORT1=".$MYREQUEST['SORT1']. "&SORT2=".$MYREQUEST['SORT2']. "&COUNT=".$MYREQUEST['COUNT']; $MY_SELF_WO_SORT= "$PHP_SELF". "?SCOPE=".$MYREQUEST['SCOPE']. "&COUNT=".$MYREQUEST['COUNT']; // authentication needed? // if (!USE_AUTHENTICATION) { $AUTHENTICATED=1; } else { $AUTHENTICATED=0; if (ADMIN_PASSWORD!='password' && ($MYREQUEST['LO'] == 1 || isset($_SERVER['PHP_AUTH_USER']))) { if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']) || $_SERVER['PHP_AUTH_USER'] != ADMIN_USERNAME || $_SERVER['PHP_AUTH_PW'] != ADMIN_PASSWORD) { Header("WWW-Authenticate: Basic realm=\"APC Login\""); Header("HTTP/1.0 401 Unauthorized"); echo <<

Rejected!

Wrong Username or Password!
 
  Continue... EOB; exit; } else { $AUTHENTICATED=1; } } } // select cache mode if ($AUTHENTICATED && $MYREQUEST['OB'] == OB_USER_CACHE) { $cache_mode='user'; } // clear cache if ($AUTHENTICATED && isset($MYREQUEST['CC']) && $MYREQUEST['CC']) { apc_clear_cache($cache_mode); } if ($AUTHENTICATED && !empty($MYREQUEST['DU'])) { apc_delete($MYREQUEST['DU']); } if(!function_exists('apc_cache_info') || !($cache=@apc_cache_info($cache_mode))) { echo "No cache info available. APC does not appear to be running."; exit; } $cache_user = apc_cache_info('user', 1); $mem=apc_sma_info(); if(!$cache['num_hits']) { $cache['num_hits']=1; $time++; } // Avoid division by 0 errors on a cache clear // don't cache this page // header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1 header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); // HTTP/1.0 function duration($ts) { global $time; $years = (int)((($time - $ts)/(7*86400))/52.177457); $rem = (int)(($time-$ts)-($years * 52.177457 * 7 * 86400)); $weeks = (int)(($rem)/(7*86400)); $days = (int)(($rem)/86400) - $weeks*7; $hours = (int)(($rem)/3600) - $days*24 - $weeks*7*24; $mins = (int)(($rem)/60) - $hours*60 - $days*24*60 - $weeks*7*24*60; $str = ''; if($years==1) $str .= "$years year, "; if($years>1) $str .= "$years years, "; if($weeks==1) $str .= "$weeks week, "; if($weeks>1) $str .= "$weeks weeks, "; if($days==1) $str .= "$days day,"; if($days>1) $str .= "$days days,"; if($hours == 1) $str .= " $hours hour and"; if($hours>1) $str .= " $hours hours and"; if($mins == 1) $str .= " 1 minute"; else $str .= " $mins minutes"; return $str; } // create graphics // function graphics_avail() { return extension_loaded('gd'); } if (isset($MYREQUEST['IMG'])) { if (!graphics_avail()) { exit(0); } function fill_arc($im, $centerX, $centerY, $diameter, $start, $end, $color1,$color2,$text='',$placeindex=0) { $r=$diameter/2; $w=deg2rad((360+$start+($end-$start)/2)%360); if (function_exists("imagefilledarc")) { // exists only if GD 2.0.1 is avaliable imagefilledarc($im, $centerX+1, $centerY+1, $diameter, $diameter, $start, $end, $color1, IMG_ARC_PIE); imagefilledarc($im, $centerX, $centerY, $diameter, $diameter, $start, $end, $color2, IMG_ARC_PIE); imagefilledarc($im, $centerX, $centerY, $diameter, $diameter, $start, $end, $color1, IMG_ARC_NOFILL|IMG_ARC_EDGED); } else { imagearc($im, $centerX, $centerY, $diameter, $diameter, $start, $end, $color2); imageline($im, $centerX, $centerY, $centerX + cos(deg2rad($start)) * $r, $centerY + sin(deg2rad($start)) * $r, $color2); imageline($im, $centerX, $centerY, $centerX + cos(deg2rad($start+1)) * $r, $centerY + sin(deg2rad($start)) * $r, $color2); imageline($im, $centerX, $centerY, $centerX + cos(deg2rad($end-1)) * $r, $centerY + sin(deg2rad($end)) * $r, $color2); imageline($im, $centerX, $centerY, $centerX + cos(deg2rad($end)) * $r, $centerY + sin(deg2rad($end)) * $r, $color2); imagefill($im,$centerX + $r*cos($w)/2, $centerY + $r*sin($w)/2, $color2); } if ($text) { if ($placeindex>0) { imageline($im,$centerX + $r*cos($w)/2, $centerY + $r*sin($w)/2,$diameter, $placeindex*12,$color1); imagestring($im,4,$diameter, $placeindex*12,$text,$color1); } else { imagestring($im,4,$centerX + $r*cos($w)/2, $centerY + $r*sin($w)/2,$text,$color1); } } } function text_arc($im, $centerX, $centerY, $diameter, $start, $end, $color1,$text,$placeindex=0) { $r=$diameter/2; $w=deg2rad((360+$start+($end-$start)/2)%360); if ($placeindex>0) { imageline($im,$centerX + $r*cos($w)/2, $centerY + $r*sin($w)/2,$diameter, $placeindex*12,$color1); imagestring($im,4,$diameter, $placeindex*12,$text,$color1); } else { imagestring($im,4,$centerX + $r*cos($w)/2, $centerY + $r*sin($w)/2,$text,$color1); } } function fill_box($im, $x, $y, $w, $h, $color1, $color2,$text='',$placeindex='') { global $col_black; $x1=$x+$w-1; $y1=$y+$h-1; imagerectangle($im, $x, $y1, $x1+1, $y+1, $col_black); if($y1>$y) imagefilledrectangle($im, $x, $y, $x1, $y1, $color2); else imagefilledrectangle($im, $x, $y1, $x1, $y, $color2); imagerectangle($im, $x, $y1, $x1, $y, $color1); if ($text) { if ($placeindex>0) { if ($placeindex<16) { $px=5; $py=$placeindex*12+6; imagefilledrectangle($im, $px+90, $py+3, $px+90-4, $py-3, $color2); imageline($im,$x,$y+$h/2,$px+90,$py,$color2); imagestring($im,2,$px,$py-6,$text,$color1); } else { if ($placeindex<31) { $px=$x+40*2; $py=($placeindex-15)*12+6; } else { $px=$x+40*2+100*intval(($placeindex-15)/15); $py=($placeindex%15)*12+6; } imagefilledrectangle($im, $px, $py+3, $px-4, $py-3, $color2); imageline($im,$x+$w,$y+$h/2,$px,$py,$color2); imagestring($im,2,$px+2,$py-6,$text,$color1); } } else { imagestring($im,4,$x+5,$y1-16,$text,$color1); } } } $size = GRAPH_SIZE; // image size if ($MYREQUEST['IMG']==3) $image = imagecreate(2*$size+150, $size+10); else $image = imagecreate($size+50, $size+10); $col_white = imagecolorallocate($image, 0xFF, 0xFF, 0xFF); $col_red = imagecolorallocate($image, 0xD0, 0x60, 0x30); $col_green = imagecolorallocate($image, 0x60, 0xF0, 0x60); $col_black = imagecolorallocate($image, 0, 0, 0); imagecolortransparent($image,$col_white); switch ($MYREQUEST['IMG']) { case 1: $s=$mem['num_seg']*$mem['seg_size']; $a=$mem['avail_mem']; $x=$y=$size/2; $fuzz = 0.000001; // This block of code creates the pie chart. It is a lot more complex than you // would expect because we try to visualize any memory fragmentation as well. $angle_from = 0; $string_placement=array(); for($i=0; $i<$mem['num_seg']; $i++) { $ptr = 0; $free = $mem['block_lists'][$i]; uasort($free, 'block_sort'); foreach($free as $block) { if($block['offset']!=$ptr) { // Used block $angle_to = $angle_from+($block['offset']-$ptr)/$s; if(($angle_to+$fuzz)>1) $angle_to = 1; if( ($angle_to*360) - ($angle_from*360) >= 1) { fill_arc($image,$x,$y,$size,$angle_from*360,$angle_to*360,$col_black,$col_red); if (($angle_to-$angle_from)>0.05) { array_push($string_placement, array($angle_from,$angle_to)); } } $angle_from = $angle_to; } $angle_to = $angle_from+($block['size'])/$s; if(($angle_to+$fuzz)>1) $angle_to = 1; if( ($angle_to*360) - ($angle_from*360) >= 1) { fill_arc($image,$x,$y,$size,$angle_from*360,$angle_to*360,$col_black,$col_green); if (($angle_to-$angle_from)>0.05) { array_push($string_placement, array($angle_from,$angle_to)); } } $angle_from = $angle_to; $ptr = $block['offset']+$block['size']; } if ($ptr < $mem['seg_size']) { // memory at the end $angle_to = $angle_from + ($mem['seg_size'] - $ptr)/$s; if(($angle_to+$fuzz)>1) $angle_to = 1; fill_arc($image,$x,$y,$size,$angle_from*360,$angle_to*360,$col_black,$col_red); if (($angle_to-$angle_from)>0.05) { array_push($string_placement, array($angle_from,$angle_to)); } } } foreach ($string_placement as $angle) { text_arc($image,$x,$y,$size,$angle[0]*360,$angle[1]*360,$col_black,bsize($s*($angle[1]-$angle[0]))); } break; case 2: $s=$cache['num_hits']+$cache['num_misses']; $a=$cache['num_hits']; fill_box($image, 30,$size,50,-$a*($size-21)/$s,$col_black,$col_green,sprintf("%.1f%%",$cache['num_hits']*100/$s)); fill_box($image,130,$size,50,-max(4,($s-$a)*($size-21)/$s),$col_black,$col_red,sprintf("%.1f%%",$cache['num_misses']*100/$s)); break; case 3: $s=$mem['num_seg']*$mem['seg_size']; $a=$mem['avail_mem']; $x=130; $y=1; $j=1; // This block of code creates the bar chart. It is a lot more complex than you // would expect because we try to visualize any memory fragmentation as well. for($i=0; $i<$mem['num_seg']; $i++) { $ptr = 0; $free = $mem['block_lists'][$i]; uasort($free, 'block_sort'); foreach($free as $block) { if($block['offset']!=$ptr) { // Used block $h=(GRAPH_SIZE-5)*($block['offset']-$ptr)/$s; if ($h>0) { $j++; if($j<75) fill_box($image,$x,$y,50,$h,$col_black,$col_red,bsize($block['offset']-$ptr),$j); else fill_box($image,$x,$y,50,$h,$col_black,$col_red); } $y+=$h; } $h=(GRAPH_SIZE-5)*($block['size'])/$s; if ($h>0) { $j++; if($j<75) fill_box($image,$x,$y,50,$h,$col_black,$col_green,bsize($block['size']),$j); else fill_box($image,$x,$y,50,$h,$col_black,$col_green); } $y+=$h; $ptr = $block['offset']+$block['size']; } if ($ptr < $mem['seg_size']) { // memory at the end $h = (GRAPH_SIZE-5) * ($mem['seg_size'] - $ptr) / $s; if ($h > 0) { fill_box($image,$x,$y,50,$h,$col_black,$col_red,bsize($mem['seg_size']-$ptr),$j++); } } } break; case 4: $s=$cache['num_hits']+$cache['num_misses']; $a=$cache['num_hits']; fill_box($image, 30,$size,50,-$a*($size-21)/$s,$col_black,$col_green,sprintf("%.1f%%",$cache['num_hits']*100/$s)); fill_box($image,130,$size,50,-max(4,($s-$a)*($size-21)/$s),$col_black,$col_red,sprintf("%.1f%%",$cache['num_misses']*100/$s)); break; } header("Content-type: image/png"); imagepng($image); exit; } // pretty printer for byte values // function bsize($s) { foreach (array('','K','M','G') as $i => $k) { if ($s < 1024) break; $s/=1024; } return sprintf("%5.1f %sBytes",$s,$k); } // sortable table header in "scripts for this host" view function sortheader($key,$name,$extra='') { global $MYREQUEST, $MY_SELF_WO_SORT; if ($MYREQUEST['SORT1']==$key) { $MYREQUEST['SORT2'] = $MYREQUEST['SORT2']=='A' ? 'D' : 'A'; } return "$name"; } // create menu entry function menu_entry($ob,$title) { global $MYREQUEST,$MY_SELF; if ($MYREQUEST['OB']!=$ob) { return "
  • $title
  • "; } else if (empty($MYREQUEST['SH'])) { return "
  • $title
  • "; } else { return "
  • $title
  • "; } } function put_login_link($s="Login") { global $MY_SELF,$MYREQUEST,$AUTHENTICATED; // needs ADMIN_PASSWORD to be changed! // if (!USE_AUTHENTICATION) { return; } else if (ADMIN_PASSWORD=='password') { print <<$s EOB; } else if ($AUTHENTICATED) { print <<$s EOB; } } function block_sort($array1, $array2) { if ($array1['offset'] > $array2['offset']) { return 1; } else { return -1; } } ?> APC INFO <?php echo $host ?> */ ?>

    Opcode Cache


  • Refresh Data
  • EOB; echo menu_entry(1,'View Host Stats'), menu_entry(2,'System Cache Entries'); if ($AUTHENTICATED) { echo menu_entry(4,'Per-Directory Entries'); } echo menu_entry(3,'User Cache Entries'), menu_entry(9,'Version Check'); echo << EOB; if ($AUTHENTICATED) { echo <<Clear $cache_mode Cache EOB; } // CONTENT echo << EOB; // MAIN SWITCH STATEMENT switch ($MYREQUEST['OB']) { // ----------------------------------------------- // Host Stats // ----------------------------------------------- case OB_HOST_STATS: $mem_size = $mem['num_seg']*$mem['seg_size']; $mem_avail= $mem['avail_mem']; $mem_used = $mem_size-$mem_avail; $seg_size = bsize($mem['seg_size']); $req_rate = sprintf("%.2f",($cache['num_hits']+$cache['num_misses'])/($time-$cache['start_time'])); $hit_rate = sprintf("%.2f",($cache['num_hits'])/($time-$cache['start_time'])); $miss_rate = sprintf("%.2f",($cache['num_misses'])/($time-$cache['start_time'])); $insert_rate = sprintf("%.2f",($cache['num_inserts'])/($time-$cache['start_time'])); $req_rate_user = sprintf("%.2f",($cache_user['num_hits']+$cache_user['num_misses'])/($time-$cache_user['start_time'])); $hit_rate_user = sprintf("%.2f",($cache_user['num_hits'])/($time-$cache_user['start_time'])); $miss_rate_user = sprintf("%.2f",($cache_user['num_misses'])/($time-$cache_user['start_time'])); $insert_rate_user = sprintf("%.2f",($cache_user['num_inserts'])/($time-$cache_user['start_time'])); $apcversion = phpversion('apc'); $phpversion = phpversion(); $number_files = $cache['num_entries']; $size_files = bsize($cache['mem_size']); $number_vars = $cache_user['num_entries']; $size_vars = bsize($cache_user['mem_size']); $i=0; echo <<< EOB

    General Cache Information

    EOB; if(!empty($_SERVER['SERVER_NAME'])) echo "\n"; if(!empty($_SERVER['SERVER_SOFTWARE'])) echo "\n"; echo << EOB; echo ''; echo ''; echo ''; echo <<
    APC Version$apcversion
    PHP Version$phpversion
    APC Host{$_SERVER['SERVER_NAME']} $host
    Server Software{$_SERVER['SERVER_SOFTWARE']}
    Shared Memory{$mem['num_seg']} Segment(s) with $seg_size
    ({$cache['memory_type']} memory, {$cache['locking_type']} locking)
    Start Time',date(DATE_FORMAT,$cache['start_time']),'
    Uptime',duration($cache['start_time']),'
    File Upload Support',$cache['file_upload_progress'],'

    File Cache Information

    Cached Files$number_files ($size_files)
    Hits{$cache['num_hits']}
    Misses{$cache['num_misses']}
    Request Rate (hits, misses)$req_rate cache requests/second
    Hit Rate$hit_rate cache requests/second
    Miss Rate$miss_rate cache requests/second
    Insert Rate$insert_rate cache requests/second
    Cache full count{$cache['expunges']}

    User Cache Information

    Cached Variables$number_vars ($size_vars)
    Hits{$cache_user['num_hits']}
    Misses{$cache_user['num_misses']}
    Request Rate (hits, misses)$req_rate_user cache requests/second
    Hit Rate$hit_rate_user cache requests/second
    Miss Rate$miss_rate_user cache requests/second
    Insert Rate$insert_rate_user cache requests/second
    Cache full count{$cache_user['expunges']}

    Runtime Settings

    EOB; $j = 0; foreach (ini_get_all('apc') as $k => $v) { echo "\n"; $j = 1 - $j; } if($mem['num_seg']>1 || $mem['num_seg']==1 && count($mem['block_lists'][0])>1) $mem_note = "Memory Usage
    (multiple slices indicate fragments)"; else $mem_note = "Memory Usage"; echo <<< EOB
    ",$k,"",str_replace(',',',
    ',$v['local_value']),"

    Host Status Diagrams

    EOB; $size='width='.(GRAPH_SIZE+50).' height='.(GRAPH_SIZE+10); echo << EOB; echo graphics_avail() ? ''. "". "\n" : "", '', '\n", '\n", '', '', '\n", '\n"; echo <<< EOB
    $mem_note Hits & Misses
    \"\"\"\"
     Free: ',bsize($mem_avail).sprintf(" (%.1f%%)",$mem_avail*100/$mem_size)," Hits: ',$cache['num_hits'].sprintf(" (%.1f%%)",$cache['num_hits']*100/($cache['num_hits']+$cache['num_misses'])),"
     Used: ',bsize($mem_used ).sprintf(" (%.1f%%)",$mem_used *100/$mem_size)," Misses: ',$cache['num_misses'].sprintf(" (%.1f%%)",$cache['num_misses']*100/($cache['num_hits']+$cache['num_misses'])),"

    Detailed Memory Usage and Fragmentation

    EOB; if(isset($mem['adist'])) { foreach($mem['adist'] as $i=>$v) { $cur = pow(2,$i); $nxt = pow(2,$i+1)-1; if($i==0) $range = "1"; else $range = "$cur - $nxt"; echo "\n"; } } echo <<

    EOB; // Fragementation: (freeseg - 1) / total_seg $nseg = $freeseg = $fragsize = $freetotal = 0; for($i=0; $i<$mem['num_seg']; $i++) { $ptr = 0; foreach($mem['block_lists'][$i] as $block) { if ($block['offset'] != $ptr) { ++$nseg; } $ptr = $block['offset'] + $block['size']; /* Only consider blocks <5M for the fragmentation % */ if($block['size']<(5*1024*1024)) $fragsize+=$block['size']; $freetotal+=$block['size']; } $freeseg += count($mem['block_lists'][$i]); } if ($freeseg > 1) { $frag = sprintf("%.2f%% (%s out of %s in %d fragments)", ($fragsize/$freetotal)*100,bsize($fragsize),bsize($freetotal),$freeseg); } else { $frag = "0%"; } if (graphics_avail()) { $size='width='.(2*GRAPH_SIZE+150).' height='.(GRAPH_SIZE+10); echo << EOB; } echo <<Fragmentation: $frag
    $range$v
    EOB; break; // ----------------------------------------------- // User Cache Entries // ----------------------------------------------- case OB_USER_CACHE: if (!$AUTHENTICATED) { echo '
    You need to login to see the user values here!
     
    '; put_login_link("Login now!"); echo '
    '; break; } $fieldname='info'; $fieldheading='User Entry Label'; $fieldkey='info'; // ----------------------------------------------- // System Cache Entries // ----------------------------------------------- case OB_SYS_CACHE: if (!isset($fieldname)) { $fieldname='filename'; $fieldheading='Script Filename'; if(ini_get("apc.stat")) $fieldkey='inode'; else $fieldkey='filename'; } if (!empty($MYREQUEST['SH'])) { echo <<< EOB
    EOB; $m=0; foreach($scope_list as $j => $list) { foreach($cache[$list] as $i => $entry) { if (md5($entry[$fieldkey])!=$MYREQUEST['SH']) continue; foreach($entry as $k => $value) { if (!$AUTHENTICATED) { // hide all path entries if not logged in $value=preg_replace('/^.*(\\/|\\\\)/','<hidden>/',$value); } if ($k == "num_hits") { $value=sprintf("%s (%.2f%%)",$value,$value*100/$cache['num_hits']); } if ($k == 'deletion_time') { if(!$entry['deletion_time']) $value = "None"; } echo "", "", "", ""; $m=1-$m; } if($fieldkey=='info') { echo "\n"; } break; } } echo <<
    AttributeValue
    ",ucwords(preg_replace("/_/"," ",$k)),"",(preg_match("/time/",$k) && $value!='None') ? date(DATE_FORMAT,$value) : htmlspecialchars($value, ENT_QUOTES, 'UTF-8'),"
    Stored Value
    ";
    					$output = var_export(apc_fetch($entry[$fieldkey]),true);
    					echo htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
    					echo "
    EOB; break; } $cols=6; echo <<
    Scope: ", " Sorting: ', ' ', '', '
    ', 'Search: ', ' ', '
    '; if (isset($MYREQUEST['SEARCH'])) { // Don't use preg_quote because we want the user to be able to specify a // regular expression subpattern. $MYREQUEST['SEARCH'] = '/'.str_replace('/', '\\/', $MYREQUEST['SEARCH']).'/i'; if (preg_match($MYREQUEST['SEARCH'], 'test') === false) { echo '
    Error: enter a valid regular expression as a search query.
    '; break; } } echo '
    ', '', '', '', '', '', '', ''; if($fieldname=='info') { $cols+=2; echo ''; } echo ''; // builds list with alpha numeric sortable keys // $list = array(); foreach($cache[$scope_list[$MYREQUEST['SCOPE']]] as $i => $entry) { switch($MYREQUEST['SORT1']) { case 'A': $k=sprintf('%015d-',$entry['access_time']); break; case 'H': $k=sprintf('%015d-',$entry['num_hits']); break; case 'Z': $k=sprintf('%015d-',$entry['mem_size']); break; case 'M': $k=sprintf('%015d-',$entry['mtime']); break; case 'C': $k=sprintf('%015d-',$entry['creation_time']); break; case 'T': $k=sprintf('%015d-',$entry['ttl']); break; case 'D': $k=sprintf('%015d-',$entry['deletion_time']); break; case 'S': $k=''; break; } if (!$AUTHENTICATED) { // hide all path entries if not logged in $list[$k.$entry[$fieldname]]=preg_replace('/^.*(\\/|\\\\)/','*hidden*/',$entry); } else { $list[$k.$entry[$fieldname]]=$entry; } } if ($list) { // sort list // switch ($MYREQUEST['SORT2']) { case "A": krsort($list); break; case "D": ksort($list); break; } // output list $i=0; foreach($list as $k => $entry) { if(!$MYREQUEST['SEARCH'] || preg_match($MYREQUEST['SEARCH'], $entry[$fieldname]) != 0) { $field_value = htmlentities(strip_tags($entry[$fieldname],''), ENT_QUOTES, 'UTF-8'); echo '', "', '', '', '', '', ''; if($fieldname=='info') { if($entry['ttl']) echo ''; else echo ''; } if ($entry['deletion_time']) { echo ''; } else if ($MYREQUEST['OB'] == OB_USER_CACHE) { echo ''; } else { echo ''; } echo ''; $i++; if ($i == $MYREQUEST['COUNT']) break; } } } else { echo ''; } echo <<< EOB
    ',sortheader('S',$fieldheading, "&OB=".$MYREQUEST['OB']),'',sortheader('H','Hits', "&OB=".$MYREQUEST['OB']),'',sortheader('Z','Size', "&OB=".$MYREQUEST['OB']),'',sortheader('A','Last accessed',"&OB=".$MYREQUEST['OB']),'',sortheader('M','Last modified',"&OB=".$MYREQUEST['OB']),'',sortheader('C','Created at', "&OB=".$MYREQUEST['OB']),'',sortheader('T','Timeout',"&OB=".$MYREQUEST['OB']),'',sortheader('D','Deleted at',"&OB=".$MYREQUEST['OB']),'
    ",$field_value,'',$entry['num_hits'],'',$entry['mem_size'],'',date(DATE_FORMAT,$entry['access_time']),'',date(DATE_FORMAT,$entry['mtime']),'',date(DATE_FORMAT,$entry['creation_time']),''.$entry['ttl'].' secondsNone', date(DATE_FORMAT,$entry['deletion_time']), ''; echo '[Delete Now]'; echo '  
    No data
    EOB; if ($list && $i < count($list)) { echo "",count($list)-$i,' more available...'; } echo <<< EOB
    EOB; break; // ----------------------------------------------- // Per-Directory System Cache Entries // ----------------------------------------------- case OB_SYS_CACHE_DIR: if (!$AUTHENTICATED) { break; } echo <<
    Scope: ", " Sorting: ', ' ', ' ', "Group By Dir Level: ', ' ', '
    ', '
    ', '', '', '', '', '', '', '', ''; // builds list with alpha numeric sortable keys // $tmp = $list = array(); foreach($cache[$scope_list[$MYREQUEST['SCOPE']]] as $entry) { $n = dirname($entry['filename']); if ($MYREQUEST['AGGR'] > 0) { $n = preg_replace("!^(/?(?:[^/\\\\]+[/\\\\]){".($MYREQUEST['AGGR']-1)."}[^/\\\\]*).*!", "$1", $n); } if (!isset($tmp[$n])) { $tmp[$n] = array('hits'=>0,'size'=>0,'ents'=>0); } $tmp[$n]['hits'] += $entry['num_hits']; $tmp[$n]['size'] += $entry['mem_size']; ++$tmp[$n]['ents']; } foreach ($tmp as $k => $v) { switch($MYREQUEST['SORT1']) { case 'A': $kn=sprintf('%015d-',$v['size'] / $v['ents']);break; case 'T': $kn=sprintf('%015d-',$v['ents']); break; case 'H': $kn=sprintf('%015d-',$v['hits']); break; case 'Z': $kn=sprintf('%015d-',$v['size']); break; case 'C': $kn=sprintf('%015d-',$v['hits'] / $v['ents']);break; case 'S': $kn = $k; break; } $list[$kn.$k] = array($k, $v['ents'], $v['hits'], $v['size']); } if ($list) { // sort list // switch ($MYREQUEST['SORT2']) { case "A": krsort($list); break; case "D": ksort($list); break; } // output list $i = 0; foreach($list as $entry) { echo '', "', '', '', '', '', '', ''; if (++$i == $MYREQUEST['COUNT']) break; } } else { echo ''; } echo <<< EOB
    ',sortheader('S','Directory Name', "&OB=".$MYREQUEST['OB']),'',sortheader('T','Number of Files',"&OB=".$MYREQUEST['OB']),'',sortheader('H','Total Hits', "&OB=".$MYREQUEST['OB']),'',sortheader('Z','Total Size', "&OB=".$MYREQUEST['OB']),'',sortheader('C','Avg. Hits', "&OB=".$MYREQUEST['OB']),'',sortheader('A','Avg. Size', "&OB=".$MYREQUEST['OB']),'
    ",$entry[0],'',$entry[1],'',$entry[2],'',$entry[3],'',round($entry[2] / $entry[1]),'',round($entry[3] / $entry[1]),'
    No data
    EOB; if ($list && $i < count($list)) { echo "",count($list)-$i,' more available...'; } echo <<< EOB
    EOB; break; // ----------------------------------------------- // Version check // ----------------------------------------------- case OB_VERSION_CHECK: echo <<

    APC Version Information

    EOB; if (defined('PROXY')) { $ctxt = stream_context_create( array( 'http' => array( 'proxy' => PROXY, 'request_fulluri' => True ) ) ); $rss = @file_get_contents("http://pecl.php.net/feeds/pkg_apc.rss", False, $ctxt); } else { $rss = @file_get_contents("http://pecl.php.net/feeds/pkg_apc.rss"); } if (!$rss) { echo ''; } else { $apcversion = phpversion('apc'); preg_match('!APC ([0-9.]+)!', $rss, $match); echo ''; echo ''; } echo <<< EOB
    Unable to fetch version information.
    '; if (version_compare($apcversion, $match[1], '>=')) { echo '
    You are running the latest version of APC ('.$apcversion.')
    '; $i = 3; } else { echo '
    You are running an older version of APC ('.$apcversion.'), newer version '.$match[1].' is available at http://pecl.php.net/package/APC/'.$match[1].'
    '; $i = -1; } echo '

    Change Log:


    '; preg_match_all('!<(title|description)>([^<]+)!', $rss, $match); next($match[2]); next($match[2]); while (list(,$v) = each($match[2])) { list(,$ver) = explode(' ', $v, 2); if ($i < 0 && version_compare($apcversion, $ver, '>=')) { break; } else if (!$i--) { break; } echo "".htmlspecialchars($v, ENT_QUOTES, 'UTF-8')."
    "; echo nl2br(htmlspecialchars(current($match[2]), ENT_QUOTES, 'UTF-8'))."
    "; next($match[2]); } echo '
    EOB; break; } echo <<< EOB EOB; ?> */ ================================================ FILE: admin/codemirror/codemirror.css ================================================ .CodeMirror { line-height: 1em; font-family: monospace; /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */ position: relative; /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */ overflow: hidden; } .CodeMirror-scroll { overflow-x: auto; overflow-y: hidden; height: 300px; /* This is needed to prevent an IE[67] bug where the scrolled content is visible outside of the scrolling box. */ position: relative; outline: none; } /* Vertical scrollbar */ .CodeMirror-scrollbar { float: right; overflow-x: hidden; overflow-y: scroll; /* This corrects for the 1px gap introduced to the left of the scrollbar by the rule for .CodeMirror-scrollbar-inner. */ margin-left: -1px; } .CodeMirror-scrollbar-inner { /* This needs to have a nonzero width in order for the scrollbar to appear in Firefox and IE9. */ width: 1px; } .CodeMirror-scrollbar.cm-sb-overlap { /* Ensure that the scrollbar appears in Lion, and that it overlaps the content rather than sitting to the right of it. */ position: absolute; z-index: 1; float: none; right: 0; min-width: 12px; } .CodeMirror-scrollbar.cm-sb-nonoverlap { min-width: 12px; } .CodeMirror-scrollbar.cm-sb-ie7 { min-width: 18px; } .CodeMirror-gutter { position: absolute; left: 0; top: 0; z-index: 10; background-color: #f7f7f7; border-right: 1px solid #eee; min-width: 2em; height: 100%; } .CodeMirror-gutter-text { color: #aaa; text-align: right; padding: .4em .2em .4em .4em; white-space: pre !important; cursor: default; } .CodeMirror-lines { padding: .4em; white-space: pre; cursor: text; } .CodeMirror-lines * { /* Necessary for throw-scrolling to decelerate properly on Safari. */ pointer-events: none; } .CodeMirror pre { -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0; border-width: 0; margin: 0; padding: 0; background: transparent; font-family: inherit; font-size: inherit; padding: 0; margin: 0; white-space: pre; word-wrap: normal; line-height: inherit; color: inherit; } .CodeMirror-wrap pre { word-wrap: break-word; white-space: pre-wrap; word-break: normal; } .CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; } .CodeMirror textarea { outline: none !important; } .CodeMirror pre.CodeMirror-cursor { z-index: 10; position: absolute; visibility: hidden; border-left: 1px solid black; border-right: none; width: 0; } .cm-keymap-fat-cursor pre.CodeMirror-cursor { width: auto; border: 0; background: transparent; background: rgba(0, 200, 0, .4); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); } /* Kludge to turn off filter in ie9+, which also accepts rgba */ .cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); } .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} .CodeMirror-focused pre.CodeMirror-cursor { visibility: visible; } div.CodeMirror-selected { background: #d9d9d9; } .CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } .CodeMirror-searching { background: #ffa; background: rgba(255, 255, 0, .4); } /* Default theme */ .cm-s-default span.cm-keyword {color: #708;} .cm-s-default span.cm-atom {color: #219;} .cm-s-default span.cm-number {color: #164;} .cm-s-default span.cm-def {color: #00f;} .cm-s-default span.cm-variable {color: black;} .cm-s-default span.cm-variable-2 {color: #05a;} .cm-s-default span.cm-variable-3 {color: #085;} .cm-s-default span.cm-property {color: black;} .cm-s-default span.cm-operator {color: black;} .cm-s-default span.cm-comment {color: #a50;} .cm-s-default span.cm-string {color: #a11;} .cm-s-default span.cm-string-2 {color: #f50;} .cm-s-default span.cm-meta {color: #555;} .cm-s-default span.cm-error {color: #f00;} .cm-s-default span.cm-qualifier {color: #555;} .cm-s-default span.cm-builtin {color: #30a;} .cm-s-default span.cm-bracket {color: #cc7;} .cm-s-default span.cm-tag {color: #170;} .cm-s-default span.cm-attribute {color: #00c;} .cm-s-default span.cm-header {color: blue;} .cm-s-default span.cm-quote {color: #090;} .cm-s-default span.cm-hr {color: #999;} .cm-s-default span.cm-link {color: #00c;} span.cm-header, span.cm-strong {font-weight: bold;} span.cm-em {font-style: italic;} span.cm-emstrong {font-style: italic; font-weight: bold;} span.cm-link {text-decoration: underline;} div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} ================================================ FILE: admin/codemirror/codemirror.js ================================================ var CodeMirror=function(){"use strict";function e(r,i){function un(e){if(s.onDragEvent&&s.onDragEvent(ln,I(e)))return;U(e)}function fn(e){return e>=0&&e=n.to||t.liner-400&&rt(Ht.pos,n))i="triple",q(e),setTimeout(Qn,20),wr(n.line);else if(Pt&&Pt.time>r-400&&rt(Pt.pos,n)){i="double",Ht={time:r,pos:n},q(e);var o=br(n);fr(o.from,o.to)}else Pt={time:r,pos:n};var a=n,f;if(s.dragDrop&&K&&!s.readOnly&&!rt(_t.from,_t.to)&&!it(n,_t.from)&&!it(_t.to,n)&&i=="single"){g&&(St.draggable=!0);var l=V(document,"mouseup",hi(u),!0),c=V(St,"drop",hi(u),!0);jt=!0,St.dragDrop&&St.dragDrop();return}q(e),i=="single"&&hr(n.line,n.ch,!0);var d=_t.from,v=_t.to,w=V(document,"mousemove",hi(function(e){clearTimeout(f),q(e),!p&&!W(e)?b(e):y(e)}),!0),l=V(document,"mouseup",hi(b),!0)}function yn(e){for(var t=z(e);t!=xt;t=t.parentNode)if(t.parentNode==yt)return q(e);q(e)}function bn(e){if(s.onDragEvent&&s.onDragEvent(ln,I(e)))return;q(e);var t=Zr(e,!0),n=e.dataTransfer.files;if(!t||s.readOnly)return;if(n&&n.length&&window.FileReader&&window.File){var r=n.length,i=Array(r),o=0,u=function(e,n){var s=new FileReader;s.onload=function(){i[n]=s.result,++o==r&&(t=dr(t),hi(function(){var e=qn(i.join(""),t,t);fr(t,e)})())},s.readAsText(e)};for(var a=0;a-1&&setTimeout(hi(function(){Sr(_t.to.line,"smart")}),75);if(Tn(e,r))return;Vn()}function Ln(e){if(s.onKeyEvent&&s.onKeyEvent(ln,I(e)))return;X(e,"keyCode")==16&&(Dt=null)}function An(){if(s.readOnly=="nocursor")return;Mt||(s.onFocus&&s.onFocus(ln),Mt=!0,St.className.search(/\bCodeMirror-focused\b/)==-1&&(St.className+=" CodeMirror-focused"),Xt||Kn(!0)),Xn(),ti()}function On(){Mt&&(s.onBlur&&s.onBlur(ln),Mt=!1,Yt&&hi(function(){Yt&&(Yt(),Yt=null)})(),St.className=St.className.replace(" CodeMirror-focused","")),clearInterval(kt),setTimeout(function(){Mt||(Dt=null)},150)}function Mn(e,t,n,r,i){if(It)return;if(on){var o=[];At.iter(e.line,t.line+1,function(e){o.push(e.text)}),on.addChange(e.line,n.length,o);while(on.done.length>s.undoDepth)on.done.shift()}Hn(e,t,n,r,i)}function _n(e,t){if(!e.length)return;var n=e.pop(),r=[];for(var i=n.length-1;i>=0;i-=1){var s=n[i],o=[],u=s.start+s.added;At.iter(s.start,u,function(e){o.push(e.text)}),r.push({start:s.start,added:s.old.length,old:o});var a={line:s.start+s.old.length-1,ch:lt(o[o.length-1],s.old[s.old.length-1])};Hn({line:s.start,ch:0},{line:u-1,ch:cn(u-1).text.length},s.old,a,a)}qt=!0,t.push(r)}function Dn(){_n(on.done,on.undone)}function Pn(){_n(on.undone,on.done)}function Hn(e,t,n,r,i){function x(e){return e<=Math.min(t.line,t.line+g)?e:e+g}if(It)return;var o=!1,u=Zt.text.length;s.lineWrapping||At.iter(e.line,t.line+1,function(e){if(!e.hidden&&e.text.length==u)return o=!0,!0});if(e.line!=t.line||n.length>1)Vt=!0;var a=t.line-e.line,f=cn(e.line),l=cn(t.line);if(e.ch==0&&t.ch==0&&n[n.length-1]==""){var c=[],h=null;e.line?(h=cn(e.line-1),h.fixMarkEnds(l)):l.fixMarkStarts();for(var p=0,d=n.length-1;p1&&At.remove(e.line+1,a-1,$t),At.insert(e.line+1,c)}if(s.lineWrapping){var v=Math.max(5,St.clientWidth/Qr()-3);At.iter(e.line,e.line+n.length,function(e){if(e.hidden)return;var t=Math.ceil(e.text.length/v)||1;t!=e.height&&hn(e,t)})}else At.iter(e.line,e.line+n.length,function(e){var t=e.text;!e.hidden&&t.length>u&&(Zt=e,u=t.length,tn=!0,o=!1)}),o&&(en=!0);var m=[],g=n.length-a-1;for(var p=0,y=Ot.length;pt.line&&m.push(b+g)}var w=e.line+Math.min(n.length,500);oi(e.line,w),m.push(w),Ot=m,ai(100),Ut.push({from:e.line,to:t.line+1,diff:g});var E={from:e,to:t,text:n};if(zt){for(var S=zt;S.next;S=S.next);S.next=E}else zt=E;lr(dr(r),dr(i),x(_t.from.line),x(_t.to.line))}function Bn(){var e=At.height*$r()+2*Gr();return e-1>St.offsetHeight?e:!1}function jn(e){var t=Bn();R.style.display=t?"block":"none",t?(F.style.height=Et.style.minHeight=t+"px",R.style.height=St.clientHeight+"px",e!=null&&(R.scrollTop=St.scrollTop=e)):Et.style.minHeight="",wt.style.top=Jt*$r()+"px"}function Fn(){var e=ot("div",null,"CodeMirror-scrollbar-inner","height: 200px"),t=ot("div",[e],"CodeMirror-scrollbar","position: absolute; left: -9999px; height: 100px;");document.body.appendChild(t);var n=t.offsetWidth<=1;return document.body.removeChild(t),n}function In(){Zt=cn(0),tn=!0;var e=Zt.text.length;At.iter(1,At.size,function(t){var n=t.text;!t.hidden&&n.length>e&&(e=n.length,Zt=t)}),en=!1}function qn(e,t,n){function r(r){if(it(r,t))return r;if(!it(n,r))return i;var s=r.line+e.length-(n.line-t.line)-1,o=r.ch;return r.line==n.line&&(o+=e[e.length-1].length-(n.ch-(n.line==t.line?t.ch:0))),{line:s,ch:o}}t=dr(t),n?n=dr(n):n=t,e=pt(e);var i;return Un(e,t,n,function(e){return i=e,{from:r(_t.from),to:r(_t.to)}}),i}function Rn(e,t){Un(pt(e),_t.from,_t.to,function(e){return t=="end"?{from:e,to:e}:t=="start"?{from:_t.from,to:_t.from}:{from:_t.from,to:e}})}function Un(e,t,n,r){var i=e.length==1?e[0].length+t.ch:e[e.length-1].length,s=r({line:t.line+e.length-1,ch:i});Mn(t,n,e,s.from,s.to)}function zn(e,t,n){var r=e.line,i=t.line;if(r==i)return cn(r).text.slice(e.ch,t.ch);var s=[cn(r).text.slice(e.ch)];return At.iter(r+1,i,function(e){s.push(e.text)}),s.push(cn(i).text.slice(0,t.ch)),s.join(n||"\n")}function Wn(e){return zn(_t.from,_t.to,e)}function Xn(){if(rn)return;Nt.set(s.pollInterval,function(){fi(),Jn(),Mt&&Xn(),li()})}function Vn(){function t(){fi();var n=Jn();!n&&!e?(e=!0,Nt.set(60,t)):(rn=!1,Xn()),li()}var e=!1;rn=!0,Nt.set(20,t)}function Jn(){if(Xt||!Mt||dt(L)||s.readOnly)return!1;var e=L.value;if(e==$n)return!1;Dt=null;var t=0,n=Math.min($n.length,e.length);while(t1e3?L.value=$n="":$n=e,!0}function Kn(e){rt(_t.from,_t.to)?e&&($n=L.value=""):($n="",L.value=Wn(),nt(L))}function Qn(){s.readOnly!="nocursor"&&L.focus()}function Gn(){var e=Z.getBoundingClientRect();if(p&&e.top==e.bottom)return;var t=window.innerHeight||Math.max(document.body.offsetHeight,document.documentElement.offsetHeight);(e.top<0||e.bottom>t)&&Yn()}function Yn(){var e=Zn();er(e.x,e.y,e.x,e.yBot)}function Zn(){var e=Rr(_t.inverted?_t.from:_t.to),t=s.lineWrapping?Math.min(e.x,gt.offsetWidth):e.x;return{x:t,y:e.y,yBot:e.yBot}}function er(e,t,n,r){var i=tr(e,t,n,r);i.scrollLeft!=null&&(St.scrollLeft=i.scrollLeft),i.scrollTop!=null&&(R.scrollTop=St.scrollTop=i.scrollTop)}function tr(e,t,n,r){var i=Yr(),o=Gr();t+=o,r+=o,e+=i,n+=i;var u=St.clientHeight,a=R.scrollTop,f={},l=Bn()||Infinity,c=tl-10;ta+u&&(f.scrollTop=(h?l:r)-u);var p=St.clientWidth,d=St.scrollLeft,v=s.fixedGutter?bt.clientWidth:0,m=ep+d-3&&(f.scrollLeft=n+10-p),f}function nr(e){var t=$r(),n=(e!=null?e:R.scrollTop)-Gr(),r=Math.max(0,Math.floor(n/t)),i=Math.ceil((n+St.clientHeight)/t);return{from:H(At,r),to:H(At,i)}}function rr(e,t,n){function d(){var e=Q.firstChild,t=!1;return At.iter(Kt,Qt,function(n){if(!e)return;if(!n.hidden){var r=Math.round(e.offsetHeight/c)||1;n.height!=r&&(hn(n,r),Vt=t=!0)}e=e.nextSibling}),t}if(!St.clientWidth){Kt=Qt=Jt=0;return}var r=nr(n);if(e!==!0&&e.length==0&&r.from>Kt&&r.too&&Qt-o<20&&(o=Math.min(At.size,Qt));var u=e===!0?[]:ir([{from:Kt,to:Qt,domStart:0}],e),a=0;for(var f=0;fo&&(l.to=o),l.from>=l.to?u.splice(f--,1):a+=l.to-l.from}if(a==o-i&&i==Kt&&o==Qt){jn(n);return}u.sort(function(e,t){return e.domStart-t.domStart});var c=$r(),h=bt.style.display;Q.style.display="none",sr(i,o,u),Q.style.display=bt.style.display="";var p=i!=Kt||o!=Qt||Gt!=St.clientHeight+c;p&&(Gt=St.clientHeight+c),Kt=i,Qt=o,Jt=B(At,i);if(Q.childNodes.length!=Qt-Kt)throw new Error("BAD PATCH! "+JSON.stringify(u)+" size="+(Qt-Kt)+" nodes="+Q.childNodes.length);if(s.lineWrapping){d();var v=Bn(),m=v?"block":"none";R.style.display!=m&&(R.style.display=m,v&&(F.style.height=v+"px"),d())}return bt.style.display=h,(p||Vt)&&or()&&s.lineWrapping&&d()&&or(),jn(n),ur(),!t&&s.onUpdate&&s.onUpdate(ln),!0}function ir(e,t){for(var n=0,r=t.length||0;n=f.to?s.push(f):(i.from>f.from&&s.push({from:f.from,to:i.from,domStart:f.domStart}),i.toi)s=r(s),i++;for(var f=0,l=a.to-a.from;ff){if(e.hidden)var t=ot("pre");else{var t=e.getElement(Cr);e.className&&(t.className=e.className);if(e.bgClassName){var r=ot("pre","\u00a0",e.bgClassName,"position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2");t=ot("div",[r,t],null,"position: relative")}}Q.insertBefore(t,s)}else s=s.nextSibling;++f})}function or(){if(!s.gutter&&!s.lineNumbers)return;var e=wt.offsetHeight,t=St.clientHeight;bt.style.height=(e-t<2?t:e)+"px";var n=document.createDocumentFragment(),r=Kt,i;At.iter(Kt,Math.max(Qt,Kt+1),function(e){if(e.hidden)n.appendChild(ot("pre"));else{var t=e.gutterMarker,o=s.lineNumbers?s.lineNumberFormatter(r+s.firstLineNumber):null;t&&t.text?o=t.text.replace("%N%",o!=null?o:""):o==null&&(o="\u00a0");var u=n.appendChild(ot("pre",null,t&&t.style));u.innerHTML=o;for(var a=1;a2;return gt.style.marginLeft=bt.offsetWidth+"px",Vt=!1,l}function ur(){var e=rt(_t.from,_t.to),t=Rr(_t.from,!0),n=e?t:Rr(_t.to,!0),r=_t.inverted?t:n,i=$r(),o=et(xt),u=et(Q);O.style.top=Math.max(0,Math.min(St.offsetHeight,r.y+u.top-o.top))+"px",O.style.left=Math.max(0,Math.min(St.offsetWidth,r.x+u.left-o.left))+"px";if(e)Z.style.top=r.y+"px",Z.style.left=(s.lineWrapping?Math.min(r.x,gt.offsetWidth):r.x)+"px",Z.style.display="",Y.style.display="none";else{var a=t.y==n.y,f=document.createDocumentFragment(),l=gt.clientWidth||gt.offsetWidth,c=gt.clientHeight||gt.offsetHeight,h=function(e,t,n,r){var i=m?"width: "+(n?l-n-e:l)+"px":"right: "+n+"px";f.appendChild(ot("div",null,"CodeMirror-selected","position: absolute; left: "+e+"px; top: "+t+"px; "+i+"; height: "+r+"px"))};if(_t.from.ch&&t.y>=0){var p=a?l-n.x:0;h(t.x,t.y,p,i)}var d=Math.max(0,t.y+(_t.from.ch?i:0)),v=Math.min(n.y,c)-d;v>.2*i&&h(0,d,0,v),(!a||!_t.from.ch)&&n.yn||u>o.text.length)u=o.text.length;return{line:r,ch:u}}r+=t}}var i=cn(e.line),s=e.ch==i.text.length&&e.ch!=n;return i.hidden?e.line>=t?r(1)||r(-1):r(-1)||r(1):e}function hr(e,t,n){var r=dr({line:e,ch:t||0});(n?fr:lr)(r,r)}function pr(e){return Math.max(0,Math.min(e,At.size-1))}function dr(e){if(e.line<0)return{line:0,ch:0};if(e.line>=At.size)return{line:At.size-1,ch:cn(At.size-1).text.length};var t=e.ch,n=cn(e.line).text.length;return t==null||t>n?{line:e.line,ch:n}:t<0?{line:e.line,ch:0}:e}function vr(e,t){function o(){for(var t=r+e,n=e<0?-1:At.size;t!=n;t+=e){var i=cn(t);if(!i.hidden)return r=t,s=i,!0}}function u(t){if(i==(e<0?0:s.text.length)){if(!!t||!o())return!1;i=e<0?s.text.length:0}else i+=e;return!0}var n=_t.inverted?_t.from:_t.to,r=n.line,i=n.ch,s=cn(r);if(t=="char")u();else if(t=="column")u(!0);else if(t=="word"){var a=!1;for(;;){if(e<0&&!u())break;if(ht(s.text.charAt(i)))a=!0;else if(a){e<0&&(e=1,u());break}if(e>0&&!u())break}}return{line:r,ch:i}}function mr(e,t){var n=e<0?_t.from:_t.to;if(Dt||rt(_t.from,_t.to))n=vr(e,t);hr(n.line,n.ch,!0)}function gr(e,t){rt(_t.from,_t.to)?e<0?qn("",vr(e,t),_t.to):qn("",_t.from,vr(e,t)):qn("",_t.from,_t.to),Rt=!0}function yr(e,t){var n=0,r=Rr(_t.inverted?_t.from:_t.to,!0);sn!=null&&(r.x=sn),t=="page"?n=Math.min(St.clientHeight,window.innerHeight||document.documentElement.clientHeight):t=="line"&&(n=$r());var i=Ur(r.x,r.y+n*e+2);t=="page"&&(R.scrollTop+=Rr(i,!0).y-r.y),hr(i.line,i.ch,!0),sn=r.x}function br(e){var t=cn(e.line).text,n=e.ch,r=e.ch;if(t){e.after===!1||r==t.length?--n:++r;var i=t.charAt(n),s=ht(i)?ht:/\s/.test(i)?function(e){return/\s/.test(e)}:function(e){return!/\s/.test(e)&&!ht(e)};while(n>0&&s(t.charAt(n-1)))--n;while(r=e.ch)&&t.push(s.marker||s)}return t}function Dr(e,t,n){return typeof e=="number"&&(e=cn(pr(e))),e.gutterMarker={text:t,style:n},Vt=!0,e}function Pr(e){typeof e=="number"&&(e=cn(pr(e))),e.gutterMarker=null,Vt=!0}function Hr(e,t){var n=e,r=e;return typeof e=="number"?r=cn(pr(e)):n=P(e),n==null?null:t(r,n)?(Ut.push({from:n,to:n+1}),r):null}function Br(e,t,n){return Hr(e,function(e){if(e.className!=t||e.bgClassName!=n)return e.className=t,e.bgClassName=n,!0})}function jr(e,t){return Hr(e,function(e,n){if(e.hidden!=t){e.hidden=t,s.lineWrapping||(t&&e.text.length==Zt.text.length?en=!0:!t&&e.text.length>Zt.text.length&&(Zt=e,en=!1)),hn(e,t?0:1);var r=_t.from.line,i=_t.to.line;if(t&&(r==n||i==n)){var o=r==n?cr({line:r,ch:0},r,0):_t.from,u=i==n?cr({line:i,ch:0},i,0):_t.to;if(!u)return;lr(o,u)}return Vt=!0}})}function Fr(e){if(typeof e=="number"){if(!fn(e))return null;var t=e;e=cn(e);if(!e)return null}else{var t=P(e);if(t==null)return null}var n=e.gutterMarker;return{line:t,handle:e,text:e.text,markerText:n&&n.text,markerClass:n&&n.style,lineClass:e.className,bgClass:e.bgClassName}}function Ir(e,t){function i(e){return qr(n,e).left}if(t<=0)return 0;var n=cn(e),r=n.text,s=0,o=0,u=r.length,a,f=Math.min(u,Math.ceil(t/Qr()));for(;;){var l=i(f);if(!(l<=t&&fa)return u;f=Math.floor(u*.8),l=i(f),lt-o?s:u;var c=Math.ceil((s+u)/2),h=i(c);h>t?(u=c,a=h):(s=c,o=h)}}function qr(e,t){if(t==0)return{top:0,left:0};var n=s.lineWrapping&&t=At.size)return{line:At.size-1,ch:cn(At.size-1).text.length};var u=cn(o),a=u.text,f=s.lineWrapping,l=f?i-B(At,o):0;if(e<=0&&l==0)return{line:o,ch:0};var c=!1,p=0,d=0,v=a.length,m,g=Math.min(v,Math.ceil((e+l*St.clientWidth*.9)/r));for(;;){var y=h(g);if(!(y<=e&&gm)return{line:o,ch:v};g=Math.floor(v*.8),y=h(g),ye?(v=w,m=E,c&&(m+=1e3)):(p=w,d=E)}}function zr(e){var t=Rr(e,!0),n=et(gt);return{x:n.left+t.x,y:n.top+t.y,yBot:n.top+t.yBot}}function $r(){if(Vr==null){Vr=ot("pre");for(var e=0;e<49;++e)Vr.appendChild(document.createTextNode("x")),Vr.appendChild(ot("br"));Vr.appendChild(document.createTextNode("x"))}var t=Q.clientHeight;return t==Xr?Wr:(Xr=t,at(mt,Vr.cloneNode(!0)),Wr=mt.firstChild.offsetHeight/50||1,ut(mt),Wr)}function Qr(){if(St.clientWidth==Kr)return Jr;Kr=St.clientWidth;var e=ot("span","x"),t=ot("pre",[e]);return at(mt,t),Jr=e.offsetWidth||10}function Gr(){return gt.offsetTop}function Yr(){return gt.offsetLeft}function Zr(e,t){var n=et(St,!0),r,i;try{r=e.clientX,i=e.clientY}catch(e){return null}if(!t&&(r-n.left>St.clientWidth||i-n.top>St.clientHeight))return null;var s=et(gt,!0);return Ur(r-s.left,i-s.top)}function ei(e){function o(){var e=pt(L.value).join("\n");e!=i&&!s.readOnly&&hi(Rn)(e,"end"),O.style.position="relative",L.style.cssText=r,v&&(R.scrollTop=n),Xt=!1,Kn(!0),Xn()}var t=Zr(e),n=R.scrollTop;if(!t||b)return;(rt(_t.from,_t.to)||it(t,_t.from)||!it(t,_t.to))&&hi(hr)(t.line,t.ch);var r=L.style.cssText;O.style.position="absolute",L.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(e.clientY-5)+"px; left: "+(e.clientX-5)+"px; z-index: 1000; background: white; "+"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",Xt=!0;var i=L.value=Wn();Qn(),nt(L);if(h){U(e);var u=V(window,"mouseup",function(){u(),setTimeout(o,20)},!0)}else setTimeout(o,50)}function ti(){clearInterval(kt);var e=!0;Z.style.visibility="",kt=setInterval(function(){Z.style.visibility=(e=!e)?"":"hidden"},650)}function ri(e){function v(e,t,n){if(!e.text)return;var r=e.styles,i=o?0:e.text.length-1,s;for(var a=o?0:r.length-2,f=o?r.length:-2;a!=f;a+=2*u){var l=r[a];if(r[a+1]!=h){i+=u*l.length;continue}for(var c=o?0:l.length-1,v=o?l.length:-1;c!=v;c+=u,i+=u)if(i>=t&&i"==o)p.push(s);else{if(p.pop()!=m.charAt(0))return{pos:i,match:!1};if(!p.length)return{pos:i,match:!0}}}}}var t=_t.inverted?_t.from:_t.to,n=cn(t.line),r=t.ch-1,i=r>=0&&ni[n.text.charAt(r)]||ni[n.text.charAt(++r)];if(!i)return;var s=i.charAt(0),o=i.charAt(1)==">",u=o?1:-1,a=n.styles;for(var f=r+1,l=0,c=a.length;li;--r){if(r==0)return 0;var o=cn(r-1);if(o.stateAfter)return r;var u=o.indentation(s.tabSize);if(n==null||t>u)n=r-1,t=u}return n}function si(e){var t=ii(e),n=t&&cn(t-1).stateAfter;return n?n=x(Lt,n):n=T(Lt),At.iter(t,e,function(e){e.highlight(Lt,n,s.tabSize),e.stateAfter=x(Lt,n)}),t=At.size)continue;var r=ii(n),i=r&&cn(r-1).stateAfter;i?i=x(Lt,i):i=T(Lt);var o=0,u=Lt.compareStates,a=!1,f=r,l=!1;At.iter(f,At.size,function(t){var r=t.stateAfter;if(+(new Date)>e)return Ot.push(f),ai(s.workDelay),a&&Ut.push({from:n,to:f+1}),l=!0;var c=t.highlight(Lt,i,s.tabSize);c&&(a=!0),t.stateAfter=x(Lt,i);var h=null;if(u){var p=r&&u(r,i);p!=J&&(h=!!p)}h==null&&(c!==!1||!r?o=0:++o>3&&(!Lt.indent||Lt.indent(r,"")==Lt.indent(i,""))&&(h=!0));if(h)return!0;++f});if(l)return;a&&Ut.push({from:n,to:f+1})}t&&s.onHighlightComplete&&s.onHighlightComplete(ln)}function ai(e){if(!Ot.length)return;Ct.set(e,hi(ui))}function fi(){qt=Rt=zt=null,Ut=[],Wt=!1,$t=[]}function li(){en&&In();if(tn&&!s.lineWrapping){var e=ft.offsetWidth,t=qr(Zt,Zt.text.length).left;ft.style.left=t+"px",gt.style.minWidth=t+e+"px",tn=!1}var n,r;if(Wt){var i=Zn();n=tr(i.x,i.y,i.x,i.yBot)}if(Ut.length||n&&n.scrollTop!=null)r=rr(Ut,!0,n&&n.scrollTop);r||(Wt&&ur(),Vt&&or()),n&&Yn(),Wt&&(Gn(),ti()),Mt&&!Xt&&(qt===!0||qt!==!1&&Wt)&&Kn(Rt),Wt&&s.matchBrackets&&setTimeout(hi(function(){Yt&&(Yt(),Yt=null),rt(_t.from,_t.to)&&ri(!1)}),20);var o=Wt,u=$t;zt&&s.onChange&&ln&&s.onChange(ln,zt),o&&s.onCursorActivity&&s.onCursorActivity(ln);for(var a=0;au&&e.y>t.offsetHeight&&(s=e.y-t.offsetHeight),o+t.offsetWidth>a&&(o=a-t.offsetWidth)}t.style.top=s+Gr()+"px",t.style.left=t.style.right="",i=="right"?(o=Et.clientWidth-t.offsetWidth,t.style.right="0px"):(i=="left"?o=0:i=="middle"&&(o=(Et.clientWidth-t.offsetWidth)/2),t.style.left=o+Yr()+"px"),n&&er(o,s,o+t.offsetWidth,s+t.offsetHeight)},lineCount:function(){return At.size},clipPos:dr,getCursor:function(e){return e==null&&(e=_t.inverted),st(e?_t.from:_t.to)},somethingSelected:function(){return!rt(_t.from,_t.to)},setCursor:hi(function(e,t,n){t==null&&typeof e.line=="number"?hr(e.line,e.ch,n):hr(e,t,n)}),setSelection:hi(function(e,t,n){(n?fr:lr)(dr(e),dr(t||e))}),getLine:function(e){if(fn(e))return cn(e).text},getLineHandle:function(e){if(fn(e))return cn(e)},setLine:hi(function(e,t){fn(e)&&qn(t,{line:e,ch:0},{line:e,ch:cn(e).text.length})}),removeLine:hi(function(e){fn(e)&&qn("",{line:e,ch:0},dr({line:e+1,ch:0}))}),replaceRange:hi(qn),getRange:function(e,t,n){return zn(dr(e),dr(t),n)},triggerOnKeyDown:hi(Cn),execCommand:function(e){return u[e](ln)},moveH:hi(mr),deleteH:hi(gr),moveV:hi(yr),toggleOverwrite:function(){Ft?(Ft=!1,Z.className=Z.className.replace(" CodeMirror-overwrite","")):(Ft=!0,Z.className+=" CodeMirror-overwrite")},posFromIndex:function(e){var t=0,n;return At.iter(0,At.size,function(r){var i=r.text.length+1;if(i>e)return n=e,!0;e-=i,++t}),dr({line:t,ch:n})},indexFromPos:function(e){if(e.line<0||e.ch<0)return 0;var t=e.ch;return At.iter(0,e.line,function(e){t+=e.text.length+1}),t},scrollTo:function(e,t){e!=null&&(St.scrollLeft=e),t!=null&&(R.scrollTop=St.scrollTop=t),rr([])},getScrollInfo:function(){return{x:St.scrollLeft,y:R.scrollTop,height:R.scrollHeight,width:St.scrollWidth}},setSize:function(e,t){function n(e){return e=String(e),/^\d+$/.test(e)?e+"px":e}e!=null&&(xt.style.width=n(e)),t!=null&&(St.style.height=n(t)),ln.refresh()},operation:function(e){return hi(e)()},compoundChange:function(e){return pi(e)},refresh:function(){rr(!0,null,Bt),R.scrollHeight>Bt&&(R.scrollTop=Bt)},getInputField:function(){return L},getWrapperElement:function(){return xt},getScrollerElement:function(){return St},getGutterElement:function(){return bt}},Sn,Nn=null,$n="";Ar.prototype.clear=hi(function(){var e=Infinity,t=-Infinity;for(var n=0,r=this.set.length;n",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"},ci=0;for(var di in o)o.propertyIsEnumerable(di)&&!ln.propertyIsEnumerable(di)&&(ln[di]=o[di]);return ln}function f(e){return typeof e=="string"?a[e]:e}function l(e,t,n,r,i){function s(t){t=f(t);var n=t[e];if(n===!1)return i&&i(),!0;if(n!=null&&r(n))return!0;if(t.nofallthrough)return i&&i(),!0;var o=t.fallthrough;if(o==null)return!1;if(Object.prototype.toString.call(o)!="[object Array]")return s(o);for(var u=0,a=o.length;ue&&r.push(u.slice(e-s,Math.min(u.length,t-s)),n[i+1]),a>=e&&(o=1)):o==1&&(a>t?r.push(u.slice(0,t-s),n[i+1]):r.push(u,n[i+1])),s=a}}function M(e){this.lines=e,this.parent=null;for(var t=0,n=e.length,r=0;t=0&&r>=0;--n,--r)if(e.charAt(n)!=t.charAt(r))break;return r+1}function ct(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,r=e.length;n2){n.dependencies=[];for(var r=2;r0&&t.ch=this.string.length},sol:function(){return this.pos==0},peek:function(){return this.string.charAt(this.pos)},next:function(){if(this.post},eatSpace:function(){var e=this.pos;while(/[\s\u00a0]/.test(this.string.charAt(this.pos)))++this.pos;return this.pos>e},skipToEnd:function(){this.pos=this.string.length},skipTo:function(e){var t=this.string.indexOf(e,this.pos);if(t>-1)return this.pos=t,!0},backUp:function(e){this.pos-=e},column:function(){return Y(this.string,this.start,this.tabSize)},indentation:function(){return Y(this.string,null,this.tabSize)},match:function(e,t,n){if(typeof e!="string"){var i=this.string.slice(this.pos).match(e);return i&&t!==!1&&(this.pos+=i[0].length),i}var r=function(e){return n?e.toLowerCase():e};if(r(this.string).indexOf(r(e),this.pos)==this.pos)return t!==!1&&(this.pos+=e.length),!0},current:function(){return this.string.slice(this.start,this.pos)}},e.StringStream=N,C.prototype={attach:function(e){this.marker.set.push(e)},detach:function(e){var t=ct(this.marker.set,e);t>-1&&this.marker.set.splice(t,1)},split:function(e,t){if(this.to<=e&&this.to!=null)return null;var n=this.fromthis.from&&(r=t&&(this.from=Math.max(r,this.from)+i),n&&(tthis.from||this.from==null)?this.to=null:this.to!=null&&this.to>t&&(this.to=r=this.to},sameSet:function(e){return this.marker==e.marker}},k.prototype={attach:function(e){this.line=e},detach:function(e){this.line==e&&(this.line=null)},split:function(e,t){if(ethis.to},clipTo:function(e,t,n,r,i){(e||tthis.to)?(this.from=0,this.to=-1):this.from>t&&(this.from=this.to=Math.max(r,this.from)+i)},sameSet:function(e){return!1},find:function(){return!this.line||!this.line.parent?null:{line:P(this.line),ch:this.from}},clear:function(){if(this.line){var e=ct(this.line.marked,this);e!=-1&&this.line.marked.splice(e,1),this.line=null}}};var L=" ";h||p&&!d?L="\u200b":b&&(L=""),A.inheritMarks=function(e,t){var n=new A(e),r=t&&t.marked;if(r)for(var i=0;i5e3){i[s++]=this.text.slice(r.pos),i[s++]=null;break}}return i.length!=s&&(i.length=s,o=!0),s&&i[s-2]!=a&&(o=!0),o||(i.length<5&&this.text.length<10?null:!1)},getTokenAt:function(e,t,n){var r=this.text,i=new N(r);while(i.pos=f&&tf&&(u(e,r.slice(0,t-f),i),n&&e.appendChild(ot("wbr"))),e.appendChild(l);var o=t-f;u(l,b?r.slice(o,o+1):r.slice(o),i),b&&u(e,r.slice(o+1),i),t--,f+=s}else f+=s,u(e,r,i),f==t&&f==v?(ft(l,L),e.appendChild(l)):f>t+10&&/\s/.test(r)&&(a=function(){})}}var c=this.styles,h=this.text,d=this.marked,v=h.length;if(!h&&t==null)a(o," ");else if(!d||!d.length)for(var g=0,y=0;yv&&(w=w.slice(0,v-y)),y+=S,a(o,w,m(E))}else{var x=0,g=0,T="",E,N=0,C=d[0].from||0,k=[],A=0,O=function(){var e;while(A_?T.slice(0,_-x):T,P);if(D>=_){T=T.slice(_-x),x=_;break}x=D}T=c[g++],E=m(c[g++])}}}return o},cleanUp:function(){this.parent=null;if(this.marked)for(var e=0,t=this.marked.length;e50){while(s.lines.length>50){var u=s.lines.splice(s.lines.length-25,25),a=new M(u);s.height-=a.height,this.children.splice(r+1,0,a),a.parent=this}this.maybeSpill()}break}e-=o}},maybeSpill:function(){if(this.children.length<=10)return;var e=this;do{var t=e.children.splice(e.children.length-5,5),n=new _(t);if(!e.parent){var r=new _(e.children);r.parent=e,e.children=[r,n],e=r}else{e.size-=n.size,e.height-=n.height;var i=ct(e.parent.children,e);e.parent.children.splice(i+1,0,n)}n.parent=e.parent}while(e.children.length>10);e.parent.maybeSpill()},iter:function(e,t,n){this.iterN(e,t-e,n)},iterN:function(e,t,n){for(var r=0,i=this.children.length;r400||!s||this.closed||s.start>e+n.length||s.start+s.added0;--f)s.old.unshift(n[f-1]);for(var f=a;f>0;--f)s.old.push(n[n.length-f]);u&&(s.start=e),s.added+=t-(n.length-u-a)}this.time=r},startCompound:function(){this.compound++||(this.closed=!0)},endCompound:function(){--this.compound||(this.closed=!0)}},e.e_stop=U,e.e_preventDefault=q,e.e_stopPropagation=R,e.connect=V,$.prototype={set:function(e,t){clearTimeout(this.id),this.id=setTimeout(t,e)}};var J=e.Pass={toString:function(){return"CodeMirror.Pass"}},K=function(){if(v)return!1;var e=ot("div");return"draggable"in e||"dragDrop"in e}(),Q=function(){var e=ot("textarea");return e.value="foo\nbar",e.value.indexOf("\r")>-1?"\r\n":"\n"}(),G=/^$/;h?G=/$'/:w?G=/\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/:y&&(G=/\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/),e.setTextContent=ft;var pt="\n\nb".split(/\n/).length!=3?function(e){var t=0,n=[],r=e.length;while(t<=r){var i=e.indexOf("\n",t);i==-1&&(i=e.length);var s=e.slice(t,e.charAt(i-1)=="\r"?i-1:i),o=s.indexOf("\r");o!=-1?(n.push(s.slice(0,o)),t+=o+1):(n.push(s),t=i+1)}return n}:function(e){return e.split(/\r\n?|\n/)};e.splitLines=pt;var dt=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch(t){return!1}}:function(e){try{var t=e.ownerDocument.selection.createRange()}catch(n){}return!t||t.parentElement()!=e?!1:t.compareEndPoints("StartToEnd",t)!=0};e.defineMode("null",function(){return{token:function(e){e.skipToEnd()}}}),e.defineMIME("text/plain","null");var vt={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",91:"Mod",92:"Mod",93:"Mod",109:"-",107:"=",127:"Delete",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63276:"PageUp",63277:"PageDown",63275:"End",63273:"Home",63234:"Left",63232:"Up",63235:"Right",63233:"Down",63302:"Insert",63272:"Delete"};return e.keyNames=vt,function(){for(var e=0;e<10;e++)vt[e+48]=String(e);for(var e=65;e<=90;e++)vt[e]=String.fromCharCode(e);for(var e=1;e<=12;e++)vt[e+111]=vt[e+63235]="F"+e}(),e}();CodeMirror.defineMode("properties",function(){return{token:function(e,t){var n=e.sol()||t.afterSection,r=e.eol();t.afterSection=!1,n&&(t.nextMultiline?(t.inMultiline=!0,t.nextMultiline=!1):t.position="def"),r&&!t.nextMultiline&&(t.inMultiline=!1,t.position="def");if(n)while(e.eatSpace());var i=e.next();return!n||i!=="#"&&i!=="!"&&i!==";"?n&&i==="["?(t.afterSection=!0,e.skipTo("]"),e.eat("]"),"header"):i==="="||i===":"?(t.position="quote",null):(i==="\\"&&t.position==="quote"&&e.next()!=="u"&&(t.nextMultiline=!0),t.position):(t.position="comment",e.skipToEnd(),"comment")},startState:function(){return{position:"def",nextMultiline:!1,inMultiline:!1,afterSection:!1}}}}),CodeMirror.defineMIME("text/x-properties","properties"),CodeMirror.defineMIME("text/x-ini","properties") ================================================ FILE: admin/codemirror/properties.js ================================================ CodeMirror.defineMode("properties", function() { return { token: function(stream, state) { var sol = stream.sol() || state.afterSection; var eol = stream.eol(); state.afterSection = false; if (sol) { if (state.nextMultiline) { state.inMultiline = true; state.nextMultiline = false; } else { state.position = "def"; } } if (eol && ! state.nextMultiline) { state.inMultiline = false; state.position = "def"; } if (sol) { while(stream.eatSpace()); } var ch = stream.next(); if (sol && (ch === "#")) { state.position = "comment"; stream.skipToEnd(); return "comment"; } else if (sol && ch === "[") { state.afterSection = true; stream.skipTo("]"); stream.eat("]"); return "header"; } else if (ch === ":") { state.position = "quote"; return null; } else if (ch === "\\" && state.position === "quote") { if (stream.next() !== "u") { // u = Unicode sequence \u1234 // Multiline value state.nextMultiline = true; } } return state.position; }, startState: function() { return { position : "def", // Current position, "def", "quote" or "comment" nextMultiline : false, // Is the next line multiline value inMultiline : false, // Is the current line a multiline value afterSection : false // Did we just open a section }; } }; }); CodeMirror.defineMIME("text/x-properties", "properties"); CodeMirror.defineMIME("text/x-ini", "properties"); ================================================ FILE: admin/edit-pattern.php ================================================ . */ // Usage // ----- // Access this file in your browser and follow the instructions to update your site config files. error_reporting(E_ALL ^ E_NOTICE); ini_set("display_errors", 1); @set_time_limit(120); if (get_magic_quotes_gpc()) { $process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST); while (list($key, $val) = each($process)) { foreach ($val as $k => $v) { unset($process[$key][$k]); if (is_array($v)) { $process[$key][stripslashes($k)] = $v; $process[] = &$process[$key][stripslashes($k)]; } else { $process[$key][stripslashes($k)] = stripslashes($v); } } } unset($process); } require_once '../libraries/content-extractor/SiteConfig.php'; //////////////////////////////// // Load config file //////////////////////////////// $admin_page = 'edit-pattern'; require_once('../config.php'); require_once('require_login.php'); require_once('template.php'); tpl_header('Edit site patterns'); $version = file_get_contents('../site_config/standard/version.txt'); function filter_only_text($filename) { return (strtolower(substr($filename, -4)) == '.txt'); } function is_valid_hostname($host) { return preg_match('!^[a-z0-9_.-]+$!i', $host); } ///////////////////////////////// // Process changes ///////////////////////////////// if ($_SERVER['REQUEST_METHOD'] == 'POST') { // DELETE if (@$_POST['delete'] != '' && @$_POST['delete_dir'] != '') { if (is_valid_hostname($_POST['delete'])) { $delete = $_POST['delete']; if ($_POST['delete_dir'] == 'standard') { $delete = '../site_config/standard/'.$delete; } else { $delete = '../site_config/custom/'.$delete; } if (@unlink($delete)) { echo 'Deleted '.$delete.''; } else { echo 'Failed to delete '.$delete.''; } } exit; } // SAVE if (@$_POST['save'] != '' && isset($_POST['contents'])) { if (is_valid_hostname(trim($_POST['save']))) { $save = strtolower(trim($_POST['save'])); if (@$_POST['save_dir'] == 'standard') { $savepath = '../site_config/standard/'.$save.'.txt'; } else { $savepath = '../site_config/custom/'.$save.'.txt'; } // TODO: check if file exists, if it does, prompt user whether to overwrite if (file_put_contents($savepath, $_POST['contents']) !== false) { echo '

    Saved to '.$savepath.'

    '; // check caching if ($options->caching) { echo '

    Note: caching is enabled — you may have to disable caching or delete cache files to see changes.

    '; } if ($options->apc && function_exists('apc_delete') && function_exists('apc_cache_info')) { $_apc_data = apc_cache_info('user'); foreach ($_apc_data['cache_list'] as $_apc_item) { if (substr($_apc_item['info'], 0, 3) == 'sc.') { apc_delete($_apc_item['info']); } } echo '

    Cleared site config cache in APC.

    '; } SiteConfig::set_config_path(dirname($savepath)); $sconfig = SiteConfig::build($save, $exact_host_match=true); if ($sconfig) { if (!empty($sconfig->test_url)) { echo '

    Test URLs

    '; echo '
      '; foreach ($sconfig->test_url as $test_url) { $ftr_test_url = $test_url; if (strtolower(substr($ftr_test_url, 0, 7)) == 'http://') { $ftr_test_url = substr($ftr_test_url, 7); } $ftr_test_url = '../makefulltextfeed.php?url='.urlencode($ftr_test_url); echo '
    • '; echo ''.htmlspecialchars($test_url).''; echo ' | Full-Text RSS result'; echo ' | Debug'; echo '
    • '; } echo '
    '; } else { echo '

    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.

    '; foreach ($sc_files as $file) { $file = basename($file, '.txt'); echo '
  • '.htmlspecialchars($file).'
  • '; } echo ''; // adapted from http://stackoverflow.com/a/11022738/407938 ... ?> No matching files found...

    '; } elseif ($exact_match) { $contents = $matched[$exact_match]; $file_location = $exact_match; echo '

    Loaded '.htmlspecialchars($exact_match).'

    '; } else { $contents = end($matched); $file_location = array_pop(array_keys($matched)); echo '

    Loaded '.htmlspecialchars($file_location).'

    '; } if (isset($file_location)) unset($related[$file_location]); $save_locations = array( 'custom' => 'custom (recommended)', 'standard' => 'standard' ); echo '
    '; echo ''; echo ''; echo '
    '; echo ' .txt'; echo '
    '; echo ' '; echo ''; echo '
    '; echo ' '; echo 'or Cancel and return to listing'; echo '
    '; // DELETE option if (!empty($matched)) { echo '

    Delete file?

    '; echo '

    Delete '.htmlspecialchars($file_location).'

    '; echo '
    '; echo ''; echo ''; echo ''; echo '
    '; } // TEST URLs if (!empty($matched)) { if ($sconfig = SiteConfig::build_from_array(explode("\n", $contents))) { if (!empty($sconfig->test_url)) { echo '

    Test URLs

    '; echo '
      '; foreach ($sconfig->test_url as $test_url) { $ftr_test_url = $test_url; if (strtolower(substr($ftr_test_url, 0, 7)) == 'http://') { $ftr_test_url = substr($ftr_test_url, 7); } $ftr_test_url = '../makefulltextfeed.php?url='.urlencode($ftr_test_url); echo '
    • '; echo ''.htmlspecialchars($test_url).''; echo ' | Full-Text RSS result'; echo ' | Debug'; echo '
    • '; } echo '
    '; } } } // RELATED files if (!empty($related)) { echo '

    Related files

    '; echo '
      '; foreach (array_keys($related) as $_m_file) { preg_match('!/(standard|custom)/(.+?)\.txt$!', $_m_file, $_m); echo '
    • '.htmlspecialchars($_m_file).'
    • '; } echo '
    '; } ?> ================================================ FILE: admin/index.php ================================================ . */ // Usage // ----- // Access this file in your browser error_reporting(E_ALL ^ E_NOTICE); ini_set("display_errors", 1); @set_time_limit(120); //////////////////////////////// // Load config file //////////////////////////////// require_once('../config.php'); require_once('require_login.php'); require_once('template.php'); tpl_header('Admin'); ?>

    The admin pages are intended to help you manage your copy of Full-Text RSS more easily.

    • Update patterns: an easy way to keep site config files up to date.
    • Edit patterns: need to fine-tune extraction for a certain site? Use this tool.
    • APC: If APC is enabled, you can use this tool to see what Full-Text RSS caches, and clear the cache if you need to.
    ================================================ FILE: admin/login.php ================================================ admin_credentials) || $options->admin_credentials['username'] == '' || $options->admin_credentials['password'] == '') { die('

    Admin privileges required

    This page requires admin privileges but Full-Text RSS has not been configured with admin credentials.

    If you are the administrator, please edit your custom_config.php file and enter the credentials in the appropriate section. When you\'ve done that, this page will prompt you for your admin credentials.

    '); } $name = @$_POST['username']; $pass = @$_POST['pass']; $invalid_login = false; if ($name || $pass) { if ($name == $options->admin_credentials['username'] && $pass == $options->admin_credentials['password']) { // Authentication successful - set session $_SESSION['auth'] = 1; if (isset($_POST['redirect']) && preg_match('/^[0-9a-z]+$/', $_POST['redirect'])) { header('Location: '.$_POST['redirect'].'.php'); } else { header('Location: index.php'); } exit; } $invalid_login = true; } ?> Login Invalid login, please try again. If you can\'t remember your admin credentials, open your custom_config.php and you\'ll find them in there.

    '; ?>
    '; ?>
    ================================================ FILE: admin/require_login.php ================================================ . */ // Usage // ----- // This file is included on pages which require admin privileges - e.g. updating the software. // The username is 'admin' by default and the password should be set in the custom_config.php file. session_start(); require_once(dirname(dirname(__FILE__)).'/config.php'); if (isset($_GET['logout'])) $_SESSION['auth'] = 0; if (!isset($_SESSION['auth']) || $_SESSION['auth'] != 1) { if (isset($admin_page)) { header('Location: login.php?redirect='.$admin_page); } else { header('Location: login.php'); } exit; } ================================================ FILE: admin/template.php ================================================ <?php echo $title; ?> . */ // Usage // ----- // * Access this file in your browser and follow the instructions to update your site config files. // * See section on automatic updates for a URL you can fetch periodically (e.g. with cron) to update site config files error_reporting(E_ALL ^ E_NOTICE); ini_set("display_errors", 1); @set_time_limit(120); //////////////////////////////// // Load config file //////////////////////////////// $admin_page = 'update'; require_once('../config.php'); require_once 'template.php'; tpl_header('Update site patterns'); ////////////////////////////////// // Username and password must be available ////////////////////////////////// if (!isset($options->admin_credentials) || $options->admin_credentials['username'] == '' || $options->admin_credentials['password'] == '') { header("X-Robots-Tag: noindex, nofollow", true); die('

    Username and password not set

    Full-Text RSS has not been configured with admin credentials.

    If you are the administrator, please edit your custom_config.php file and enter the credentials in the appropriate section. When you\'ve done that, this page will prompt you for your admin credentials.

    '); } $admin_hash = sha1($options->admin_credentials['username'].'+'.$options->admin_credentials['password']); $_self_host = $_SERVER['HTTP_HOST']; $_self_path = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\'); $self_update_url = 'http://'.htmlspecialchars($_self_host.$_self_path).'/update.php?key='.urlencode($admin_hash); $latest_remote = 'https://codeload.github.com/fivefilters/ftr-site-config/zip/master'; $version = @file_get_contents('../site_config/standard/version.txt'); ///////////////////////////////// // Check for update key ///////////////////////////////// if (!isset($_REQUEST['key']) || trim($_REQUEST['key']) == '') { require_once 'require_login.php'; if ($_SERVER['REQUEST_METHOD'] == 'POST') { header('Location: update.php'); exit; } $auto = true; $no_auto_reasons = array(); if (!class_exists('ZipArchive')) { $auto = false; $no_auto_reasons[] = 'zip support (PHP\'s ZipArchive class) is missing'; } if (!is_writable('../site_config')) { $auto = false; $no_auto_reasons[] = 'your site_config/ folder is not writable - change permissions to 777 and try again.

    '; } if (!file_exists('../site_config/standard/version.txt')) { die('Could not determine current version of your site pattern files (site_config/standard/version.txt). Make sure you\'re using at least version 3.2 of Full-Text RSS.'); } ?>

    You have Full-Text RSS (Site Patterns version: )

    To see if you have the latest versions, check for updates.

    This update tool will attempt to fetch the latest site patterns from our GitHub repository.

    '; echo '

    Important: if you\'ve modified or added your own config files in the site_config/standard/ folder, please move them to site_config/custom/ — the update process will attempt to replace everything in site_config/standard/ with our updated version.

    '; echo '
    '; echo ''; echo ''; echo '
    '; echo '

    Automatic updates

    '; echo '

    You can schedule automatic updates using something like cron. The URL to call is:

    '; echo '

    '.$self_update_url.'

    '; echo '

    We recommend you schedule this URL to be fetched once a day. If you do not have access to a scheduling service '; echo 'you may want to consider one of these online services: Easycron, SetCronJob, onlinecronjobs.com.

    '; echo '

    Note: the key contained in the URL is a hash value generated from your admin credentials. If you change these, the key will also change.

    '; } else { echo '
    '; echo '

    We cannot automatically update your site pattern files because:

    '; echo '
      '; foreach ($no_auto_reasons as $reason) { echo '
    • ',$reason,'
    • '; } echo '
    '; echo '

    You can still manually update by downloading the zip file and replacing everything in your site_config/standard/ folder with the contents of the zip file.

    '; echo '
    '; echo '

    Download site config files (zip)

    '; } echo '

    Help

    '; echo '

    If you have any trouble, please contact us via our support site.

    '; exit; } ////////////////////////////////// // Check update key valid ////////////////////////////////// if ($_REQUEST['key'] !== $admin_hash) { println("Sorry, invalid key supplied."); exit; } ////////////////////////////////// // Check for updates ////////////////////////////////// //$ff_version = @file_get_contents('http://fivefilters.org/content-only/site_config/standard/version.txt'); $_context = stream_context_create(array('http' => array('user_agent' => 'PHP/5.4'))); $latest_info_json = @file_get_contents('https://api.github.com/repos/fivefilters/ftr-site-config', false, $_context); if (!$latest_info_json) { println("Sorry, couldn't get info on latest site config files. Please try again later or contact us."); exit; } $latest_info_json = @json_decode($latest_info_json); if (!is_object($latest_info_json)) { println("Sorry, couldn't parse JSON from GitHub. Please try again later or contact us."); exit; } $ff_version = $latest_info_json->updated_at; if ($version == $ff_version) { die('Your site config files are up to date! If you have trouble extracting from a particular site, please email us: help@fivefilters.org'); } else { println("Updated site patterns are available (version $ff_version)..."); } ////////////////////////////////// // Prepare ////////////////////////////////// $tmp_latest_local = '../site_config/latest_site_config.zip'; $tmp_latest_local_dir = '../site_config/standard_latest'; $tmp_old_local_dir = '../site_config/standard_old'; if (file_exists($tmp_latest_local)) unlink($tmp_latest_local); if (file_exists($tmp_latest_local_dir)) { if (!rrmdir($tmp_latest_local_dir)) { println("Sorry, couldn't remove old folder from last update"); exit; } } if (file_exists($tmp_old_local_dir)) { rrmdir($tmp_old_local_dir); } $standard_local_dir = '../site_config/standard/'; //@copy($latest_remote, $tmp_latest_local); //copy() does not appear to fill $http_response_header in certain environments @file_put_contents($tmp_latest_local, @file_get_contents($latest_remote)); $headers = implode("\n", $http_response_header); //var_dump($headers); exit; if (strpos($headers, 'HTTP/1.0 200') === false) { println("Sorry, something went wrong. Please contact us if the problem persists."); exit; } if (class_exists('ZipArchive') && file_exists($tmp_latest_local)) { println("Downloaded latest copy of the site pattern files to $tmp_latest_local"); $zip = new ZipArchive; if ($zip->open($tmp_latest_local) === TRUE) { $zip->extractTo($tmp_latest_local_dir); $zip->close(); @unlink($tmp_latest_local); if (file_exists($tmp_latest_local_dir)) { println("Unzipped contents to $tmp_latest_local_dir"); if (!file_exists($tmp_latest_local_dir.'/ftr-site-config-master/README.md')) { println("There was a problem extracting the latest site patterns archive - your current site patterns remain untouched."); println("Please update manually."); exit; } @file_put_contents($tmp_latest_local_dir.'/ftr-site-config-master/version.txt', $ff_version); if (!file_exists($tmp_latest_local_dir.'/ftr-site-config-master/version.txt')) { println("There was a problem writing the new version number - your current site patterns remain untouched."); println("Please update manually."); exit; } rename($standard_local_dir, $tmp_old_local_dir); if (file_exists($tmp_old_local_dir)) println("Renamed $standard_local_dir to $tmp_old_local_dir"); rename($tmp_latest_local_dir."/ftr-site-config-master", $standard_local_dir); if (file_exists($standard_local_dir)) println("Renamed $tmp_latest_local_dir/ftr-site-config-master to $standard_local_dir"); rmdir($tmp_latest_local_dir); // clear cached site config files from APC if ($options->apc && function_exists('apc_delete') && function_exists('apc_cache_info')) { $_apc_data = apc_cache_info('user'); foreach ($_apc_data['cache_list'] as $_apc_item) { if (substr($_apc_item['info'], 0, 3) == 'sc.') { apc_delete($_apc_item['info']); } } println('Cleared site config cache in APC.'); } // all done! println("All done! Your old site config files are in $tmp_old_local_dir — these will be removed next time you go through the update process."); } else { if (file_exists($tmp_latest_local)) @unlink($tmp_latest_local); println("Failed to unzip to $tmp_latest_local_dir - your current site patterns remain untouched"); } } else { if (file_exists($tmp_latest_local)) @unlink($tmp_latest_local); println("Failed to extract from $tmp_latest_local - your current site patterns remain untouched"); } } else { println("Could not download the latest site config files. Please update manually - your current site patterns remain untouched."); } function println($txt) { echo $txt,"
    \n"; ob_end_flush(); ob_flush(); flush(); } function rrmdir($dir) { foreach(glob($dir . '/{*.txt,*.php,.*.txt,.*.php,.gitattributes,.gitignore,ftr-site-config-master,README.md}', GLOB_BRACE|GLOB_NOSORT) as $file) { if(is_dir($file)) { rrmdir($file); } else { unlink($file); } } return rmdir($dir); } ================================================ FILE: cache/index.php ================================================ ================================================ FILE: cache/rss/index.php ================================================ ================================================ FILE: cache/urls/index.php ================================================ ================================================ FILE: changelog.txt ================================================ FiveFilters.org: Full-Text RSS http://fivefilters.org/content-only/ CHANGELOG ------------------------------------ 3.2 (2013-05-14) - A short excerpt from the first few lines of the extracted content can now be included in the output (pass &summary=1 in querystring, see $options->summary in config file for more info) - Full content can now be excluded from the output (pass &content=0 in querystring, see $options->content in config file for more info) - Site config files can now be automatically updated from our GitHub repository (URL to call visible in admin area) - Site config files updated for better extraction - PHP Readability updated to be more lenient when pruning HTML - Language detection library updated - HTML meta refresh redirects now also followed - APC stats (if APC is available on your server) now visible in admin area - Bug fix: Duplicate find_string and replace_string values in site config files no longer removed (thanks Fabrizio!) - Bug fix: MIME type actions now applied when following single page URLs - Other minor fixes/improvements 3.1 (2013-03-06) - PHP Readability updated to preserve more images/videos - Site config files updated for better extraction - SimplePie updated - New config option favour_feed_titles and request parameter use_extracted_title to allow extracted titles to be used in generated feed - Remove image lazy loading (looks for markup used by http://wordpress.org/extend/plugins/lazy-load/) - elements appearing inside elements are now preserved in generated feed - elements now preserved - Allow multiple elements (previously only one was preserved) - Bug fix: No more self-closing iframe elements - Bug fix: Fixed manifest.yml to prevent error message when deploying to AppFog - Other minor fixes/improvements 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 - Experimental tool for simpler site pattern updates (access admin/ folder) - Plus other fixes/improvements 2.9.1 (2011-11-02) - Fix: Character encoding issue affecting some non-English articles (makefulltextfeed.php and SimplePie/Misc.php changed) 2.9 (2011-11-01) - New site patterns added and old ones updated - New config option: require_key - restrict access to those with password/key - New config option: rewrite_url - URL rewrite rules to be applied before HTTP request - New site config options to extract author(s) and publication date (matches included in feed item as and ) - New site config option: replace_string([string to find]): [replacement string] - New site identification method: site fingerprints (HTML fragments linked to site config) - Update check now also checks for new site patterns - Effective URL (URL after redirects/rewrites) now included in feed item as - Prevent indexing of generated feeds by search engines - Enclosure support (enclosures preserved as elements) - Better handling of non-HTML content types - Sending custom User-Agent HTTP header for matching sites now supported - CSS extraction deprecated in favour of site patterns (still works, but form field removed and feature may disappear in 3.0) - Fix: Improved character-encoding detection - Fix: URL parsing issues for certain URLs (SimplePie updated) - Fix: Author and other Dublin Core () elements now appear in JSON output - Fix: Minor fixes for PHP Readability - Plus other minor fixes/improvements 2.8 (2011-05-30) - Tidy no longer stripping HTML5 elements - JSON output (pass &format=json in querystring) - New site patterns added and old ones updated - New site config option to force full-page retrieval on multi-page articles: single_page_link - User Guide (PDF) now included (although still a work in progress) - URL placeholders now accepted in message_to_prepend/append config options - Plus minor fixes... 2.7 (2011-03-21) - Site patterns for better control over extraction (see site_config/README.txt) - hNews support (improves content extraction for sites using hNews microformatting) - Cookie Jar now used to store and sends cookies when following HTTP redirects - Better handling of certain cases where HTML Tidy fails to clean up properly - Bug fix: curl_multi_select() timing out in certain environments (fixed in HumbleHttpAgent.php) - Bug fix: broken HTTP header parsing in some environments (fixed in SimplePie_HumbleHttpAgent.php) - Bug fix: invalid API URL shown (fixed in index.php) - Plus other minor fixes... 2.6 (2011-03-02) - Rewriting of hash-bang (#!) URLs (see http://www.tbray.org/ongoing/When/201x/2011/02/09/Hash-Blecch for an explanation) - Improved parallel fetching support (HumbleHttpAgent uses curl_multi_* functions if PECL HTTP extension is not present) - Improved HTTP redirect support (now handled in HumbleHttpAgent, no longer relies on PHP) - Improved performance for single page (non-feed) requests: (SimplePie connected to HumbleHttpAgent) - Improved memory use for processing large feeds (HumbleHttpAgent's stored responses cleared as they're retrieved) - Bug fix: exclude on fail option no longer requires valid key - Bug fix: workaround for PHP bug http://bugs.php.net/51192 (fixed in makefulltextfeed.php) - Plus other minor changes... 2.5 (2011-01-08) - New option: custom extraction pattern (CSS selectors) - New option: allowed URLs (restrict service to pre-defined feeds/domains) - New option: exclude items on fail (remove items from feed if content extraction fails) - Remove 'http://' from URL before form submission (prevents errors on hosts which have overly vigilant security software) - Allow overriding of index.php with custom_index.php - config.php now required (override with custom_config.php) - index.php now uses config.php to determine what to display - Bug fix: occasional fatal error in IRI::__toString() (IRI updated) - Bug fix: workaround for PHP bug http://bugs.php.net/51192 (fixed in HumbleHttpAgent.php) 2.2 (2010-10-30) - Character-encoding detection improved (minor change) - Rewriting of relative URLs improved (tracks redirect URLs) - Minor changes to prevent errors in certain hosting environments - Compatibility test file updated with more tests 2.1 (2010-09-13) - Better content extraction (using PHP Readability 1.7.1) - Parallel HTTP requests (using Humble HTTP Agent) - Auto loading of necessary classes - Rewriting of relative URLs (using IRI) - Added compatibility test file (to check if server meets requirements) - Character-encoding support improved (using SimplePie) 1.5 (2010-05-30) - Support for PHP 5.3 (thanks Murilo!) - Character-encoding support improved (favours iconv over mb_convert_encoding) 1.0 (2010-03-05) - Better support for different character-encodings - Auto-cleanup of cache files - Very basic option for load distribution (if you're planning on installing the code on multiple servers) - Separate config file (see config-sample.php) ================================================ FILE: cleancache.php ================================================ . */ // 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. // 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. error_reporting(E_ALL ^ E_NOTICE); ini_set("display_errors", 1); @set_time_limit(200); // check file name if (basename(__FILE__) == 'cleancache.php') die('cleancache.php must be renamed'); // set include path set_include_path(realpath(dirname(__FILE__).'/libraries').PATH_SEPARATOR.get_include_path()); // Autoloading of classes allows us to include files only when they're // needed. If we've got a cached copy, for example, only Zend_Cache is loaded. function __autoload($class_name) { static $mapping = array( 'Zend_Cache' => 'Zend/Cache.php' ); if (isset($mapping[$class_name])) { //echo "Loading $class_name\n
    "; require_once $mapping[$class_name]; return true; } else { return false; } } require_once dirname(__FILE__).'/config.php'; if (!$options->caching) die('Caching is disabled'); // 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( 'lifetime' => 20*60, 'automatic_serialization' => false, 'write_control' => false, 'automatic_cleaning_factor' => 0, 'ignore_user_abort' => false ); $backendOptions = array( 'cache_dir' => $options->cache_dir.'/rss/', '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' ); $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions); $cache->clean(Zend_Cache::CLEANING_MODE_OLD); // clean rss (key) cache $frontendOptions = array( 'lifetime' => 20*60, 'automatic_serialization' => false, 'write_control' => false, 'automatic_cleaning_factor' => 0, 'ignore_user_abort' => false ); $backendOptions = array( 'cache_dir' => $options->cache_dir.'/rss-with-key/', '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' ); $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions); $cache->clean(Zend_Cache::CLEANING_MODE_OLD); // clean rss (urls) cache $frontendOptions = array( 'lifetime' => 60*60, 'automatic_serialization' => false, 'write_control' => false, 'automatic_cleaning_factor' => 0, 'ignore_user_abort' => false ); $backendOptions = array( 'cache_dir' => $options->cache_dir.'/urls/', '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' ); $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions); $cache->clean(Zend_Cache::CLEANING_MODE_OLD); ?> ================================================ FILE: config.php ================================================ enabled = true; // Debug mode // ---------------------- // Enable or disable debugging. When enabled debugging works by passing // &debug=1 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 // and no &max=x value is supplied in the querystring. $options->default_entries = 5; // Max entries (without access key) // ---------------------- // The maximum number of feed items to process when no access key is supplied. // This limits the user-supplied &max=x value. For example, if the user // asks for 20 items to be processed (&max=20), if max_entries is set to // 10, only 10 will be processed. $options->max_entries = 10; // Full content // ---------------------- // By default Full-Text RSS includes the extracted content in the output. // You can exclude this from the output by passing '&content=0' in the querystring. // // Possible values... // Always include: true // Never include: false // Include unless user overrides (&content=0): 'user' (default) // // Note: currently this does not disable full content extraction. It simply omits it // from the output. $options->content = 'user'; // Excerpts // ---------------------- // By default Full-Text RSS does not include excerpts in the output. // You can enable this by passing '&summary=1' in the querystring. // This will include a plain text excerpt from the extracted content. // // Possible values... // Always include: true (recommended for new users) // Never include: false // Don't include unless user overrides (&summary=1): 'user' (default) // // Important: if both content and excerpts are requested, the excerpt will be // placed in the description element and the full content inside content:encoded. // If excerpts are not requested, the full content will go inside the description element. // // Why are we not returning both excerpts and content by default? // Mainly for backward compatibility. // Excerpts should appear in the feed item's description element. Previous versions // of Full-Text RSS did not return excerpts, so the description element was always // used for the full content (as recommended by the RSS advisory). When returning both, // we need somewhere else to place the content (content:encoded). // Having both enabled should not create any problems for news readers, but it may create // problems for developers upgrading from one of our earlier versions who may now find // their applications are returning excerpts instead of the full content they were // expecting. To avoid such surprises for users who are upgrading Full-Text RSS, // excerpts must be explicitly requested in the querystring by default. // // Why not use a different element name for excerpts? // According to the RSS advisory: // "Publishers who employ summaries should store the summary in description and // the full content in content:encoded, ordering description first within the item. // On items with no summary, the full content should be stored in description." // See: http://www.rssboard.org/rss-profile#namespace-elements-content-encoded // // For more consistent element naming, we recommend new users set this option to true. // The full content can still be excluded via the querystring, but the element names // will not change: when $options->summary = true, the description element will always // be reserved for the excerpt and content:encoded always for full content. $options->summary = 'user'; // Rewrite relative URLs // ---------------------- // With this enabled relative URLs found in the extracted content // block are automatically rewritten as absolute URLs. $options->rewrite_relative_urls = true; // Exclude items if extraction fails // --------------------------------- // Excludes items from the resulting feed // if we cannot extract any content from the // item URL. // Possible values... // Enable: true // Disable: false (default) // 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. 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 // ---------------------- // Only used if caching is true $options->cache_dir = dirname(__FILE__).'/cache'; // Message to prepend (without access key) // ---------------------- // HTML to insert at the beginning of each feed item when no access key is supplied. // Substitution tags: // {url} - Feed item URL // {effective-url} - Feed item URL after we've followed all redirects $options->message_to_prepend = ''; // Message to append (without access key) // ---------------------- // HTML to insert at the end of each feed item when no access key is supplied. // Substitution tags: // {url} - Feed item URL // {effective-url} - Feed item URL after we've followed all redirects $options->message_to_append = ''; // Error message when content extraction fails (without access key) // ---------------------- $options->error_message = '[unable to retrieve full-text content]'; // Keep enclosure in feed items // If enabled, we will try to preserve enclosures if present. // ---------------------- $options->keep_enclosures = true; // Detect language // --------------- // Should we try and find/guess the language of the article being processed? // Values will be placed inside the element inside each element // Possible values: // * Ignore language: 0 // * Use article/feed metadata (e.g. HTML lang attribute): 1 (default) // * As above, but guess if not present: 2 // * Always guess: 3 // * User decides: 'user' (value of 0-3 can be passed in querystring: e.g. &l=2) $options->detect_language = 1; // Registration key // --------------- // The registration key is optional. It is not required to use Full-Text RSS, // and does not affect the normal operation of Full-Text RSS. It is currently // only used on admin pages which help you update site patterns with the // latest version offered by FiveFilters.org. For these admin-related // tasks to complete, we will require a valid registration key. // If you would like one, you can purchase the latest version of Full-Text RSS // at http://fivefilters.org/content-only/ // Your registration key will automatically be sent in the confirmation email. // Once you have it, simply copy and paste it here. $options->registration_key = ''; ///////////////////////////////////////////////// /// RESTRICT ACCESS ///////////////////////////// ///////////////////////////////////////////////// // Admin credentials // ---------------------- // Certain pages/actions, e.g. updating site patterns with our online tool, will require admin credentials. // 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'=>'admin0', 'password'=>'admin0'); // URLs to allow // ---------------------- // List of URLs (or parts of a URL) which the service will accept. // If the list is empty, all URLs (except those specified in the blocked list below) // will be permitted. // Empty: array(); // Non-empty example: array('example.com', 'anothersite.org'); $options->allowed_urls = array(); // URLs to block // ---------------------- // List of URLs (or parts of a URL) which the service will not accept. // Note: this list is ignored if allowed_urls is not empty $options->blocked_urls = array(); // Key holder(s) only? // ---------------------- // Set this to true if you want to restrict access only to // those with a key (see below to specify key(s)). // If set to true, no feed is produced unless a valid // key is provided. $options->key_required = false; // Favour item titles in feed // ---------------------- // By default, when processing feeds, we assume item titles in the feed // have not been truncated. So after processing web pages, the extracted titles // are not used in the generated feed. If you prefer to have extracted titles in // the feed you can either set this to false, in which case we will always favour // extracted titles. Alternatively, if set to 'user' (default) we'll use the // extracted title if you pass '&use_extracted_title' in the querystring. // Possible values: // * Favour feed titles: true // * Favour extracted titles: false // * Favour feed titles with user override: 'user' (default) // Note: this has no effect when the input URL is to a web page - in these cases // we always use the extracted title in the generated feed. $options->favour_feed_titles = 'user'; // Access keys (password protected access) // ------------------------------------ // NOTE: You do not need an API key from fivefilters.org to run your own // copy of the code. This is here if you'd like to restrict access to // _your_ copy. // Keys let you group users - those with a key and those without - and // restrict access to the service to those without a key. // If you want everyone to access the service in the same way, you can // leave the array below empty and ignore the access key options further down. // The options further down let you control how the service should behave // in each mode. // Note: Explicitly including the index number (1 and 2 in the examples below) // is highly recommended (when generating feeds, we encode the key and // refer to it by index number and hash). $options->api_keys = array(); // Example: // $options->api_keys[1] = 'secret-key-1'; // $options->api_keys[2] = 'secret-key-2'; // Default entries (with access key) // ---------------------- // The number of feed items to process when a valid access key is supplied. $options->default_entries_with_key = 5; // Max entries (with access key) // ---------------------- // The maximum number of feed items to process when a valid access key is supplied. $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) // value is host name to use for site config lookup if fingerprint matches $options->fingerprints = array( // Posterous ' array('hostname'=>'fingerprint.posterous.com', 'head'=>true), // Blogger ' array('hostname'=>'fingerprint.blogspot.com', 'head'=>true), ' array('hostname'=>'fingerprint.blogspot.com', 'head'=>true), // WordPress (hosted) // ' array('hostname'=>'fingerprint.wordpress.com', 'head'=>true), // WordPress (self-hosted and hosted) ''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); } ================================================ FILE: css/feed.css ================================================ /* RSS CSS Document */ * { margin:0; padding:0; } p { padding: .5em 0; } h1,h2,h3,h4,h5,h6 { font-size: 1em; padding: .5em 0; } html { display:block; padding-bottom:50px; } body { font:80% Verdana, sans-serif; color:#000; padding:25px 0 0 35px; } a { color:#5BAB03; text-decoration:none; } a:hover { color:#5BAB03; text-decoration: underline;} ul { margin-left:1.5em; } li { margin-bottom:0.4em; } div#content>ul { list-style-type: none; } div.article>li>a { font-weight:bold; font-size: 1.3em;} div { line-height:1.6em; } div#content { background:#fff; margin-right:15px; padding-left:1em;} div#content div { margin:0 1em 1em 0; } div#explanation { padding:1em 1em 0 1em; border:1px solid #ddd; background:#efefef; margin:0 2em 2em 0; } div#explanation h1 { font-weight:normal; font-size:1.8em; margin-bottom:0.3em; } div#explanation p { margin-bottom:1em; } .small { font-size: .7em; color: #666; } ================================================ FILE: css/feed.xsl ================================================ <xsl:value-of select="$title"/> (full-text feed)

    (full-text feed)

    You are viewing an auto-generated full-text RSS feed. RSS feeds allow you to stay up to date with the latest news and features you want from websites. To subscribe to it, you will need a News Reader or other similar device.

    Below is the latest content available from this feed.

    ================================================ FILE: custom_config.php.demo ================================================ debug = true; $options->default_entries = 15; $options->max_entries = 40; $options->summary = 'user'; $options->rewrite_relative_urls = true; $options->exclude_items_on_fail = 'user'; $options->multipage = true; $options->caching = true; $options->cache_dir = dirname(__FILE__).'/cache'; $options->error_message = '[unable to retrieve full-text content]'; $options->keep_enclosures = true; $options->detect_language = 1; $options->registration_key = ''; $options->admin_credentials = array('username'=>'admin0', 'password'=>'admin0'); // Non-empty example: array('example.com', 'anothersite.org'); $options->allowed_urls = array(); $options->blocked_urls = array(); $options->favour_feed_titles = 'user'; $options->xss_filter = 'user'; $options->allowed_parsers = array('libxml', 'html5lib'); $options->cors = false; $options->apc = true; $options->smart_cache = true; $options->cache_ttl = 60*60; $options->http_cache_ttl = 3*24*60*60; $options->fingerprints = array( // Posterous ' array('hostname'=>'fingerprint.posterous.com', 'head'=>true), // Blogger ' array('hostname'=>'fingerprint.blogspot.com', 'head'=>true), ' array('hostname'=>'fingerprint.blogspot.com', 'head'=>true), // WordPress (self-hosted and hosted) ' <?php echo $app_name; ?>: Server Compatibility Test

    : Compatibility Test

    Test Should Be What You Have
    PHP 5.2.0 or higher
    XML Enabled
    PCRE Enabled
    Zlib Enabled
    mbstring Enabled
    iconv Enabled
    Data filtering Enabled
    Tidy Enabled
    cURL Enabled
    Parallel URL fetching Enabled
    allow_url_fopen Enabled

    What does this mean?

    1. You have everything you need to run properly! Congratulations!
    2. PHP: You are running a supported version of PHP. No problems here.
    3. XML: You have XMLReader support or a version of XML support that isn't broken installed. No problems here.
    4. PCRE: You have PCRE support installed. No problems here.
    5. allow_url_fopen: You have allow_url_fopen enabled. No problems here.
    6. Data filtering: You have the PHP filter extension enabled. No problems here.
    7. Zlib: You have Zlib enabled. This allows SimplePie to support GZIP-encoded feeds. No problems here.
    8. Zlib: The Zlib extension is not available. SimplePie will ignore any GZIP-encoding, and instead handle feeds as uncompressed text.
    9. mbstring and iconv: You have both mbstring and iconv installed! This will allow to handle the greatest number of languages. No problems here.
    10. mbstring: mbstring is installed, but iconv is not.
    11. iconv: iconv is installed, but mbstring is not.
    12. 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.
    13. Tidy: You have Tidy support installed. No problems here.
    14. Tidy: The Tidy extension is not available. should still work with most feeds/articles, but you may experience problems with some.
    15. cURL: You have cURL support installed. No problems here.
    16. cURL: The cURL extension is not available. SimplePie will use fsockopen() instead.
    17. Parallel URL fetching: You have HttpRequestPool or curl_multi support installed. No problems here.
    18. 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.
    19. Data filtering: Your PHP configuration has the filter extension disabled. will not work here.
    20. allow_url_fopen: Your PHP configuration has allow_url_fopen disabled. will not work here.
    21. PCRE: Your PHP installation doesn't support Perl-Compatible Regular Expressions. will not work here.
    22. XML: Your PHP installation doesn't support XML parsing. will not work here.
    23. PHP: You are running an unsupported version of PHP. will not work here.

    Bottom Line: Yes, you can!

    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.

    Bottom Line: Yes, you can!

    For most feeds, it'll run with no problems. There are certain languages that you might have a hard time with though.

    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.

    Bottom Line: We're sorry…

    Your webhost does not support the minimum requirements for . It may be a good idea to contact your webhost and point them to the results of this test. They may be able to enable/install the required components.

    Alternatively, you can try one of our recommended hosts.

    Further info

    HTTP module

    Full-Text RSS can make use of HttpRequestPool or curl_multi to make parallel HTTP requests when processing feeds. If neither are available, it will make sequential requests using file_get_contents.

    will be used on this server.

    Alternative PHP Cache (APC)

    Full-Text RSS can make use of APC's memory cache to store site config files (when requested for the first time). This is not required, but if available it may improve performance slightly by reducing disk access.

    APC is available on this server.

    '; } else { echo '

    APC is not available on this server.

    '; } ?>

    Language detection

    Full-Text RSS can detect the language of each article processed. This occurs using Text_LanguageDetect or PHP-CLD (if available).

    = 0)) { echo '

    PHP-CLD will be used on this server.

    '; } else { echo '

    Text_LanguageDetect will be used on this server.

    '; } ?>

    Automatic site config updates

    Full-Text RSS can be configured to update its site config files (which determine how content should be extracted for certain sites) by downloading the latest set from our GitHub repository. This functionaility is not required, and can be done manually. To configure this to occur automatically, you will need zip support enabled in PHP - we make use of the ZipArchive class.

    ZipArchive is not available on this server. To update the site config files you will need to do it manually by downloading the latest set and uploading it to your server.

    '; } else { echo '

    ZipArchive is available on this server.

    '; } ?>

    This compatibility test has been borrowed (and slightly adapted) from the one supplied by SimplePie.org. We have kept most of their checks intact as we use SimplePie in our application.

    Date:

    ================================================ FILE: index.php ================================================ Full-Text RSS Feeds | from fivefilters.org

    Full-Text RSS — from FiveFilters.org

    Create full-text feed from feed or webpage URL
    Options api_keys) && !empty($options->api_keys)) { ?>
    key_required) echo 'required'; ?> title="Access Key" data-content="key_required) ? 'An access key is required to generate a feed' : 'If you have an access key, enter it here.'; ?>" />
    ' // for ($i = 1; $i <= $options->max_entries; $i++) { // printf("\n", $i, ($i==$options->default_entries) ? ' selected="selected"' : '', $i); // } // echo ''; if (!empty($options->api_keys)) { $msg = 'Limit: '.$options->max_entries.' (with key: '.$options->max_entries_with_key.')'; $msg_more = 'If you need more items, change max_entries (and max_entries_with_key) in config.'; } else { $msg = 'Limit: '.$options->max_entries; $msg_more = 'If you need more items, change max_entries in config.'; } ?>
    exclude_items_on_fail === 'user') { ?>
    summary === 'user') { ?>

    Quick start

    1. Check server compatibility to make sure this server meets the requirements
    2. Enter a feed or article URL in the form above and click 'Create Feed' ?
    3. If the generated full-text feed looks okay, copy the URL from your browser's address bar and use it in your news reader or application
    4. That's it! (Although see below if you'd like to customise further.)

    Configure

    In addition to the options above, Full-Text RSS comes with a configuration file which allows you to control how the application works. Find out more.

    Features include:

    • Site patterns for better control over extraction (more info)
    • Restrict access to those with an access key and/or to a pre-defined set of URLs
    • Restrict the maximum number of feed items to be processed
    • Prepend or append an HTML fragment to each feed item processed
    • Caching

    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.

    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:

    1. Save a copy of index.php as custom_index.php
    2. Edit custom_index.php

    Next time you load this page, it will automatically load custom_index.php instead.

    Support

    Check our help centre if you need help. You can also email us at help@fivefilters.org.

    Thank you!

    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.

    About

    This is a free software project to enable article extraction from web pages. It can extract content from a standard HTML page and return a 1-item feed or it can transform an existing feed into a full-text feed. It is being developed as part of the Five Filters project to promote independent, non-corporate media.

    Bookmarklet

    Rather than copying and pasting URLs into this form, you can add the bookmarklet on this page to your browser. Simply drag the link below to your browser's bookmarks toolbar. Then whenever you'd like a full-text feed, click the bookmarklet.

    Drag this:

    Note: This uses the default options and does not include your access key (if configured).

    Free Software

    Note: 'Free' as in 'free speech' (see the free software definition)

    If you're the owner of this site and you plan to offer this service to others through your hosted copy, please keep a download link so users can grab a copy of the code if they want it (you can either offer a free download yourself, or link to the purchase option on fivefilters.org to support us).

    For full details, please refer to the license.

    If you're not the owner of this site (ie. you're not hosting this yourself), you do not have to rely on an external service if you don't want to. You can download your own copy of Full-Text RSS under the AGPL license.

    URL Construction

    To extract content from a web page or to transform an existing partial feed to full text, pass the URL (encoded) in the querystring to the following URL:

    • /makefulltextfeed.php?url=[url]

    All the parameters in the form above can be passed in this way. Examine the URL in the address bar after you click 'Create Feed' to see the values.

    Software Components

    Full-Text RSS is written in PHP and relies on the following primary components:

    Depending on your configuration, these secondary components may also be used:

    System Requirements

    PHP 5.2 or above is required. A simple shared web hosting account will work fine. The code has been tested on Windows and Linux using the Apache web server. If you're a Windows user, you can try it on your own machine using WampServer. It has also been reported as working under IIS, but we have not tested this ourselves.

    Download

    Download from fivefilters.org — old versions are available in our code repository.

    Your version of Full-Text RSS:
    Your version of Site Patterns:

    To see if you have the latest versions, check for updates.

    If you've purchased this from FiveFilters.org, you'll receive notification when we release a new version or update the site patterns.

    AGPL logo

    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...

    ================================================ FILE: js/bootstrap-popover.js ================================================ /* =========================================================== * bootstrap-popover.js v2.0.3 * http://twitter.github.com/bootstrap/javascript.html#popovers * =========================================================== * Copyright 2012 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * =========================================================== */ !function ($) { "use strict"; // jshint ;_; /* POPOVER PUBLIC CLASS DEFINITION * =============================== */ var Popover = function ( element, options ) { this.init('popover', element, options) } /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js ========================================== */ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { constructor: Popover , setContent: function () { var $tip = this.tip() , title = this.getTitle() , content = this.getContent() $tip.find('.popover-title')[this.isHTML(title) ? 'html' : 'text'](title) $tip.find('.popover-content > *')[this.isHTML(content) ? 'html' : 'text'](content) $tip.removeClass('fade top bottom left right in') } , hasContent: function () { return this.getTitle() || this.getContent() } , getContent: function () { var content , $e = this.$element , o = this.options content = $e.attr('data-content') || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) return content } , tip: function () { if (!this.$tip) { this.$tip = $(this.options.template) } return this.$tip } }) /* POPOVER PLUGIN DEFINITION * ======================= */ $.fn.popover = function (option) { return this.each(function () { var $this = $(this) , data = $this.data('popover') , options = typeof option == 'object' && option if (!data) $this.data('popover', (data = new Popover(this, options))) if (typeof option == 'string') data[option]() }) } $.fn.popover.Constructor = Popover $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { placement: 'right' , content: '' , template: '

    ' }) }(window.jQuery); ================================================ FILE: js/bootstrap-tab.js ================================================ /* ======================================================== * bootstrap-tab.js v2.0.3 * http://twitter.github.com/bootstrap/javascript.html#tabs * ======================================================== * Copyright 2012 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ======================================================== */ !function ($) { "use strict"; // jshint ;_; /* TAB CLASS DEFINITION * ==================== */ var Tab = function ( element ) { this.element = $(element) } Tab.prototype = { constructor: Tab , show: function () { var $this = this.element , $ul = $this.closest('ul:not(.dropdown-menu)') , selector = $this.attr('data-target') , previous , $target , e if (!selector) { selector = $this.attr('href') selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 } if ( $this.parent('li').hasClass('active') ) return previous = $ul.find('.active a').last()[0] e = $.Event('show', { relatedTarget: previous }) $this.trigger(e) if (e.isDefaultPrevented()) return $target = $(selector) this.activate($this.parent('li'), $ul) this.activate($target, $target.parent(), function () { $this.trigger({ type: 'shown' , relatedTarget: previous }) }) } , activate: function ( element, container, callback) { var $active = container.find('> .active') , transition = callback && $.support.transition && $active.hasClass('fade') function next() { $active .removeClass('active') .find('> .dropdown-menu > .active') .removeClass('active') element.addClass('active') if (transition) { element[0].offsetWidth // reflow for transition element.addClass('in') } else { element.removeClass('fade') } if ( element.parent('.dropdown-menu') ) { element.closest('li.dropdown').addClass('active') } callback && callback() } transition ? $active.one($.support.transition.end, next) : next() $active.removeClass('in') } } /* TAB PLUGIN DEFINITION * ===================== */ $.fn.tab = function ( option ) { return this.each(function () { var $this = $(this) , data = $this.data('tab') if (!data) $this.data('tab', (data = new Tab(this))) if (typeof option == 'string') data[option]() }) } $.fn.tab.Constructor = Tab /* TAB DATA-API * ============ */ $(function () { $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { e.preventDefault() $(this).tab('show') }) }) }(window.jQuery); ================================================ FILE: js/bootstrap-tooltip.js ================================================ /* =========================================================== * bootstrap-tooltip.js v2.0.3 * http://twitter.github.com/bootstrap/javascript.html#tooltips * Inspired by the original jQuery.tipsy by Jason Frame * =========================================================== * Copyright 2012 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ========================================================== */ !function ($) { "use strict"; // jshint ;_; /* TOOLTIP PUBLIC CLASS DEFINITION * =============================== */ var Tooltip = function (element, options) { this.init('tooltip', element, options) } Tooltip.prototype = { constructor: Tooltip , init: function (type, element, options) { var eventIn , eventOut this.type = type this.$element = $(element) this.options = this.getOptions(options) this.enabled = true if (this.options.trigger != 'manual') { eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus' eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur' this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this)) this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this)) } this.options.selector ? (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : this.fixTitle() } , getOptions: function (options) { options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data()) if (options.delay && typeof options.delay == 'number') { options.delay = { show: options.delay , hide: options.delay } } return options } , enter: function (e) { var self = $(e.currentTarget)[this.type](this._options).data(this.type) if (!self.options.delay || !self.options.delay.show) return self.show() clearTimeout(this.timeout) self.hoverState = 'in' this.timeout = setTimeout(function() { if (self.hoverState == 'in') self.show() }, self.options.delay.show) } , leave: function (e) { var self = $(e.currentTarget)[this.type](this._options).data(this.type) if (!self.options.delay || !self.options.delay.hide) return self.hide() clearTimeout(this.timeout) self.hoverState = 'out' this.timeout = setTimeout(function() { if (self.hoverState == 'out') self.hide() }, self.options.delay.hide) } , show: function () { var $tip , inside , pos , actualWidth , actualHeight , placement , tp if (this.hasContent() && this.enabled) { $tip = this.tip() this.setContent() if (this.options.animation) { $tip.addClass('fade') } placement = typeof this.options.placement == 'function' ? this.options.placement.call(this, $tip[0], this.$element[0]) : this.options.placement inside = /in/.test(placement) $tip .remove() .css({ top: 0, left: 0, display: 'block' }) .appendTo(inside ? this.$element : document.body) pos = this.getPosition(inside) actualWidth = $tip[0].offsetWidth actualHeight = $tip[0].offsetHeight switch (inside ? placement.split(' ')[1] : placement) { case 'bottom': tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} break case 'top': tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2} break case 'left': tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth} break case 'right': tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width} break } $tip .css(tp) .addClass(placement) .addClass('in') } } , isHTML: function(text) { // html string detection logic adapted from jQuery return typeof text != 'string' || ( text.charAt(0) === "<" && text.charAt( text.length - 1 ) === ">" && text.length >= 3 ) || /^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(text) } , setContent: function () { var $tip = this.tip() , title = this.getTitle() $tip.find('.tooltip-inner')[this.isHTML(title) ? 'html' : 'text'](title) $tip.removeClass('fade in top bottom left right') } , hide: function () { var that = this , $tip = this.tip() $tip.removeClass('in') function removeWithAnimation() { var timeout = setTimeout(function () { $tip.off($.support.transition.end).remove() }, 500) $tip.one($.support.transition.end, function () { clearTimeout(timeout) $tip.remove() }) } $.support.transition && this.$tip.hasClass('fade') ? removeWithAnimation() : $tip.remove() } , fixTitle: function () { var $e = this.$element if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title') } } , hasContent: function () { return this.getTitle() } , getPosition: function (inside) { return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), { width: this.$element[0].offsetWidth , height: this.$element[0].offsetHeight }) } , getTitle: function () { var title , $e = this.$element , o = this.options title = $e.attr('data-original-title') || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) return title } , tip: function () { return this.$tip = this.$tip || $(this.options.template) } , validate: function () { if (!this.$element[0].parentNode) { this.hide() this.$element = null this.options = null } } , enable: function () { this.enabled = true } , disable: function () { this.enabled = false } , toggleEnabled: function () { this.enabled = !this.enabled } , toggle: function () { this[this.tip().hasClass('in') ? 'hide' : 'show']() } } /* TOOLTIP PLUGIN DEFINITION * ========================= */ $.fn.tooltip = function ( option ) { return this.each(function () { var $this = $(this) , data = $this.data('tooltip') , options = typeof option == 'object' && option if (!data) $this.data('tooltip', (data = new Tooltip(this, options))) if (typeof option == 'string') data[option]() }) } $.fn.tooltip.Constructor = Tooltip $.fn.tooltip.defaults = { animation: true , placement: 'top' , selector: false , template: '
    ' , trigger: 'hover' , title: '' , delay: 0 } }(window.jQuery); ================================================ FILE: libraries/Zend/Cache/Backend/ExtendedInterface.php ================================================ (string) cache_dir : * - Directory where to put the cache files * * =====> (boolean) file_locking : * - Enable / disable file_locking * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread * webservers and on NFS filesystems for example * * =====> (boolean) read_control : * - Enable / disable read control * - If enabled, a control key is embeded in cache file and this key is compared with the one * calculated after the reading. * * =====> (string) read_control_type : * - Type of read control (only if read control is enabled). Available values are : * 'md5' for a md5 hash control (best but slowest) * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice) * 'adler32' for an adler32 hash control (excellent choice too, faster than crc32) * 'strlen' for a length only test (fastest) * * =====> (int) hashed_directory_level : * - Hashed directory level * - Set the hashed directory structure level. 0 means "no hashed directory * structure", 1 means "one level of directory", 2 means "two levels"... * This option can speed up the cache only when you have many thousands of * cache file. Only specific benchs can help you to choose the perfect value * for you. Maybe, 1 or 2 is a good start. * * =====> (int) hashed_directory_umask : * - deprecated * - Permissions for hashed directory structure * * =====> (int) hashed_directory_perm : * - Permissions for hashed directory structure * * =====> (string) file_name_prefix : * - prefix for cache files * - be really carefull with this option because a too generic value in a system cache dir * (like /tmp) can cause disasters when cleaning the cache * * =====> (int) cache_file_umask : * - deprecated * - Permissions for cache files * * =====> (int) cache_file_perm : * - Permissions for cache files * * =====> (int) metatadatas_array_max_size : * - max size for the metadatas array (don't change this value unless you * know what you are doing) * * @var array available options */ protected $_options = array( 'cache_dir' => null, 'file_locking' => true, 'read_control' => true, 'read_control_type' => 'crc32', 'hashed_directory_level' => 0, 'hashed_directory_perm' => 0700, 'file_name_prefix' => 'zend_cache', 'cache_file_perm' => 0600, 'metadatas_array_max_size' => 100 ); /** * Array of metadatas (each item is an associative array) * * @var array */ protected $_metadatasArray = array(); /** * Constructor * * @param array $options associative array of options * @throws Zend_Cache_Exception * @return void */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_options['cache_dir'] !== null) { // particular case for this option $this->setCacheDir($this->_options['cache_dir']); } else { $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false); } if (isset($this->_options['file_name_prefix'])) { // particular case for this option if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) { Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]'); } } if ($this->_options['metadatas_array_max_size'] < 10) { Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10'); } if (isset($options['hashed_directory_umask'])) { // See #ZF-12047 trigger_error("'hashed_directory_umask' is deprecated -> please use 'hashed_directory_perm' instead", E_USER_NOTICE); if (!isset($options['hashed_directory_perm'])) { $options['hashed_directory_perm'] = $options['hashed_directory_umask']; } } if (isset($options['hashed_directory_perm']) && is_string($options['hashed_directory_perm'])) { // See #ZF-4422 $this->_options['hashed_directory_perm'] = octdec($this->_options['hashed_directory_perm']); } if (isset($options['cache_file_umask'])) { // See #ZF-12047 trigger_error("'cache_file_umask' is deprecated -> please use 'cache_file_perm' instead", E_USER_NOTICE); if (!isset($options['cache_file_perm'])) { $options['cache_file_perm'] = $options['cache_file_umask']; } } if (isset($options['cache_file_perm']) && is_string($options['cache_file_perm'])) { // See #ZF-4422 $this->_options['cache_file_perm'] = octdec($this->_options['cache_file_perm']); } } /** * Set the cache_dir (particular case of setOption() method) * * @param string $value * @param boolean $trailingSeparator If true, add a trailing separator is necessary * @throws Zend_Cache_Exception * @return void */ public function setCacheDir($value, $trailingSeparator = true) { if (!is_dir($value)) { Zend_Cache::throwException(sprintf('cache_dir "%s" must be a directory', $value)); } if (!is_writable($value)) { Zend_Cache::throwException(sprintf('cache_dir "%s" is not writable', $value)); } if ($trailingSeparator) { // add a trailing DIRECTORY_SEPARATOR if necessary $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR; } $this->_options['cache_dir'] = $value; } /** * Test if a cache is available for the given id and (if yes) return it (false else) * * @param string $id cache id * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested * @return string|false cached datas */ public function load($id, $doNotTestCacheValidity = false) { if (!($this->_test($id, $doNotTestCacheValidity))) { // The cache is not hit ! return false; } $metadatas = $this->_getMetadatas($id); $file = $this->_file($id); $data = $this->_fileGetContents($file); if ($this->_options['read_control']) { $hashData = $this->_hash($data, $this->_options['read_control_type']); $hashControl = $metadatas['hash']; if ($hashData != $hashControl) { // Problem detected by the read control ! $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match'); $this->remove($id); return false; } } return $data; } /** * Test if a cache is available or not (for the given id) * * @param string $id cache id * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record */ public function test($id) { clearstatcache(); return $this->_test($id, false); } /** * Save some string datas into a cache record * * Note : $data is always "string" (serialization is done by the * core not by the backend) * * @param string $data Datas to cache * @param string $id Cache id * @param array $tags Array of strings, the cache record will be tagged by each string entry * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) * @return boolean true if no problem */ public function save($data, $id, $tags = array(), $specificLifetime = false) { clearstatcache(); $file = $this->_file($id); $path = $this->_path($id); if ($this->_options['hashed_directory_level'] > 0) { if (!is_writable($path)) { // maybe, we just have to build the directory structure $this->_recursiveMkdirAndChmod($id); } if (!is_writable($path)) { return false; } } if ($this->_options['read_control']) { $hash = $this->_hash($data, $this->_options['read_control_type']); } else { $hash = ''; } $metadatas = array( 'hash' => $hash, 'mtime' => time(), 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)), 'tags' => $tags ); $res = $this->_setMetadatas($id, $metadatas); if (!$res) { $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata'); return false; } $res = $this->_filePutContents($file, $data); return $res; } /** * Remove a cache record * * @param string $id cache id * @return boolean true if no problem */ public function remove($id) { $file = $this->_file($id); $boolRemove = $this->_remove($file); $boolMetadata = $this->_delMetadatas($id); return $boolMetadata && $boolRemove; } /** * Clean some cache records * * Available modes are : * * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * * @param string $mode clean mode * @param tags array $tags array of tags * @return boolean true if no problem */ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) { // We use this protected method to hide the recursive stuff clearstatcache(); return $this->_clean($this->_options['cache_dir'], $mode, $tags); } /** * Return an array of stored cache ids * * @return array array of stored cache ids (string) */ public function getIds() { return $this->_get($this->_options['cache_dir'], 'ids', array()); } /** * Return an array of stored tags * * @return array array of stored tags (string) */ public function getTags() { return $this->_get($this->_options['cache_dir'], 'tags', array()); } /** * Return an array of stored cache ids which match given tags * * In case of multiple tags, a logical AND is made between tags * * @param array $tags array of tags * @return array array of matching cache ids (string) */ public function getIdsMatchingTags($tags = array()) { return $this->_get($this->_options['cache_dir'], 'matching', $tags); } /** * Return an array of stored cache ids which don't match given tags * * In case of multiple tags, a logical OR is made between tags * * @param array $tags array of tags * @return array array of not matching cache ids (string) */ public function getIdsNotMatchingTags($tags = array()) { return $this->_get($this->_options['cache_dir'], 'notMatching', $tags); } /** * Return an array of stored cache ids which match any given tags * * In case of multiple tags, a logical AND is made between tags * * @param array $tags array of tags * @return array array of any matching cache ids (string) */ public function getIdsMatchingAnyTags($tags = array()) { return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags); } /** * Return the filling percentage of the backend storage * * @throws Zend_Cache_Exception * @return int integer between 0 and 100 */ public function getFillingPercentage() { $free = disk_free_space($this->_options['cache_dir']); $total = disk_total_space($this->_options['cache_dir']); if ($total == 0) { Zend_Cache::throwException('can\'t get disk_total_space'); } else { if ($free >= $total) { return 100; } return ((int) (100. * ($total - $free) / $total)); } } /** * Return an array of metadatas for the given cache id * * The array must include these keys : * - expire : the expire timestamp * - tags : a string array of tags * - mtime : timestamp of last modification time * * @param string $id cache id * @return array array of metadatas (false if the cache id is not found) */ public function getMetadatas($id) { $metadatas = $this->_getMetadatas($id); if (!$metadatas) { return false; } if (time() > $metadatas['expire']) { return false; } return array( 'expire' => $metadatas['expire'], 'tags' => $metadatas['tags'], 'mtime' => $metadatas['mtime'] ); } /** * Give (if possible) an extra lifetime to the given cache id * * @param string $id cache id * @param int $extraLifetime * @return boolean true if ok */ public function touch($id, $extraLifetime) { $metadatas = $this->_getMetadatas($id); if (!$metadatas) { return false; } if (time() > $metadatas['expire']) { return false; } $newMetadatas = array( 'hash' => $metadatas['hash'], 'mtime' => time(), 'expire' => $metadatas['expire'] + $extraLifetime, 'tags' => $metadatas['tags'] ); $res = $this->_setMetadatas($id, $newMetadatas); if (!$res) { return false; } return true; } /** * Return an associative array of capabilities (booleans) of the backend * * The array must include these keys : * - automatic_cleaning (is automating cleaning necessary) * - tags (are tags supported) * - expired_read (is it possible to read expired cache records * (for doNotTestCacheValidity option for example)) * - priority does the backend deal with priority when saving * - infinite_lifetime (is infinite lifetime can work with this backend) * - get_list (is it possible to get the list of cache ids and the complete list of tags) * * @return array associative of with capabilities */ public function getCapabilities() { return array( 'automatic_cleaning' => true, 'tags' => true, 'expired_read' => true, 'priority' => false, 'infinite_lifetime' => true, 'get_list' => true ); } /** * PUBLIC METHOD FOR UNIT TESTING ONLY ! * * Force a cache record to expire * * @param string $id cache id */ public function ___expire($id) { $metadatas = $this->_getMetadatas($id); if ($metadatas) { $metadatas['expire'] = 1; $this->_setMetadatas($id, $metadatas); } } /** * Get a metadatas record * * @param string $id Cache id * @return array|false Associative array of metadatas */ protected function _getMetadatas($id) { if (isset($this->_metadatasArray[$id])) { return $this->_metadatasArray[$id]; } else { $metadatas = $this->_loadMetadatas($id); if (!$metadatas) { return false; } $this->_setMetadatas($id, $metadatas, false); return $metadatas; } } /** * Set a metadatas record * * @param string $id Cache id * @param array $metadatas Associative array of metadatas * @param boolean $save optional pass false to disable saving to file * @return boolean True if no problem */ protected function _setMetadatas($id, $metadatas, $save = true) { if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) { $n = (int) ($this->_options['metadatas_array_max_size'] / 10); $this->_metadatasArray = array_slice($this->_metadatasArray, $n); } if ($save) { $result = $this->_saveMetadatas($id, $metadatas); if (!$result) { return false; } } $this->_metadatasArray[$id] = $metadatas; return true; } /** * Drop a metadata record * * @param string $id Cache id * @return boolean True if no problem */ protected function _delMetadatas($id) { if (isset($this->_metadatasArray[$id])) { unset($this->_metadatasArray[$id]); } $file = $this->_metadatasFile($id); return $this->_remove($file); } /** * Clear the metadatas array * * @return void */ protected function _cleanMetadatas() { $this->_metadatasArray = array(); } /** * Load metadatas from disk * * @param string $id Cache id * @return array|false Metadatas associative array */ protected function _loadMetadatas($id) { $file = $this->_metadatasFile($id); $result = $this->_fileGetContents($file); if (!$result) { return false; } $tmp = @unserialize($result); return $tmp; } /** * Save metadatas to disk * * @param string $id Cache id * @param array $metadatas Associative array * @return boolean True if no problem */ protected function _saveMetadatas($id, $metadatas) { $file = $this->_metadatasFile($id); $result = $this->_filePutContents($file, serialize($metadatas)); if (!$result) { return false; } return true; } /** * Make and return a file name (with path) for metadatas * * @param string $id Cache id * @return string Metadatas file name (with path) */ protected function _metadatasFile($id) { $path = $this->_path($id); $fileName = $this->_idToFileName('internal-metadatas---' . $id); return $path . $fileName; } /** * Check if the given filename is a metadatas one * * @param string $fileName File name * @return boolean True if it's a metadatas one */ protected function _isMetadatasFile($fileName) { $id = $this->_fileNameToId($fileName); if (substr($id, 0, 21) == 'internal-metadatas---') { return true; } else { return false; } } /** * Remove a file * * If we can't remove the file (because of locks or any problem), we will touch * the file to invalidate it * * @param string $file Complete file path * @return boolean True if ok */ protected function _remove($file) { if (!is_file($file)) { return false; } if (!@unlink($file)) { # we can't remove the file (because of locks or any problem) $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file"); return false; } return true; } /** * Clean some cache records (protected method used for recursive stuff) * * Available modes are : * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * * @param string $dir Directory to clean * @param string $mode Clean mode * @param array $tags Array of tags * @throws Zend_Cache_Exception * @return boolean True if no problem */ protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) { if (!is_dir($dir)) { return false; } $result = true; $prefix = $this->_options['file_name_prefix']; $glob = @glob($dir . $prefix . '--*'); if ($glob === false) { // On some systems it is impossible to distinguish between empty match and an error. return true; } foreach ($glob as $file) { if (is_file($file)) { $fileName = basename($file); if ($this->_isMetadatasFile($fileName)) { // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files if ($mode != Zend_Cache::CLEANING_MODE_ALL) { continue; } } $id = $this->_fileNameToId($fileName); $metadatas = $this->_getMetadatas($id); if ($metadatas === FALSE) { $metadatas = array('expire' => 1, 'tags' => array()); } switch ($mode) { case Zend_Cache::CLEANING_MODE_ALL: $res = $this->remove($id); if (!$res) { // in this case only, we accept a problem with the metadatas file drop $res = $this->_remove($file); } $result = $result && $res; break; case Zend_Cache::CLEANING_MODE_OLD: if (time() > $metadatas['expire']) { $result = $this->remove($id) && $result; } break; case Zend_Cache::CLEANING_MODE_MATCHING_TAG: $matching = true; foreach ($tags as $tag) { if (!in_array($tag, $metadatas['tags'])) { $matching = false; break; } } if ($matching) { $result = $this->remove($id) && $result; } break; case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: $matching = false; foreach ($tags as $tag) { if (in_array($tag, $metadatas['tags'])) { $matching = true; break; } } if (!$matching) { $result = $this->remove($id) && $result; } break; case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: $matching = false; foreach ($tags as $tag) { if (in_array($tag, $metadatas['tags'])) { $matching = true; break; } } if ($matching) { $result = $this->remove($id) && $result; } break; default: Zend_Cache::throwException('Invalid mode for clean() method'); break; } } if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) { // Recursive call $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result; if ($mode == Zend_Cache::CLEANING_MODE_ALL) { // we try to drop the structure too @rmdir($file); } } } return $result; } protected function _get($dir, $mode, $tags = array()) { if (!is_dir($dir)) { return false; } $result = array(); $prefix = $this->_options['file_name_prefix']; $glob = @glob($dir . $prefix . '--*'); if ($glob === false) { // On some systems it is impossible to distinguish between empty match and an error. return array(); } foreach ($glob as $file) { if (is_file($file)) { $fileName = basename($file); $id = $this->_fileNameToId($fileName); $metadatas = $this->_getMetadatas($id); if ($metadatas === FALSE) { continue; } if (time() > $metadatas['expire']) { continue; } switch ($mode) { case 'ids': $result[] = $id; break; case 'tags': $result = array_unique(array_merge($result, $metadatas['tags'])); break; case 'matching': $matching = true; foreach ($tags as $tag) { if (!in_array($tag, $metadatas['tags'])) { $matching = false; break; } } if ($matching) { $result[] = $id; } break; case 'notMatching': $matching = false; foreach ($tags as $tag) { if (in_array($tag, $metadatas['tags'])) { $matching = true; break; } } if (!$matching) { $result[] = $id; } break; case 'matchingAny': $matching = false; foreach ($tags as $tag) { if (in_array($tag, $metadatas['tags'])) { $matching = true; break; } } if ($matching) { $result[] = $id; } break; default: Zend_Cache::throwException('Invalid mode for _get() method'); break; } } if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) { // Recursive call $recursiveRs = $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags); if ($recursiveRs === false) { $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"'); } else { $result = array_unique(array_merge($result, $recursiveRs)); } } } return array_unique($result); } /** * Compute & return the expire time * * @return int expire time (unix timestamp) */ protected function _expireTime($lifetime) { if ($lifetime === null) { return 9999999999; } return time() + $lifetime; } /** * Make a control key with the string containing datas * * @param string $data Data * @param string $controlType Type of control 'md5', 'crc32' or 'strlen' * @throws Zend_Cache_Exception * @return string Control key */ protected function _hash($data, $controlType) { switch ($controlType) { case 'md5': return md5($data); case 'crc32': return crc32($data); case 'strlen': return strlen($data); case 'adler32': return hash('adler32', $data); default: Zend_Cache::throwException("Incorrect hash function : $controlType"); } } /** * Transform a cache id into a file name and return it * * @param string $id Cache id * @return string File name */ protected function _idToFileName($id) { $prefix = $this->_options['file_name_prefix']; $result = $prefix . '---' . $id; return $result; } /** * Make and return a file name (with path) * * @param string $id Cache id * @return string File name (with path) */ protected function _file($id) { $path = $this->_path($id); $fileName = $this->_idToFileName($id); return $path . $fileName; } /** * Return the complete directory path of a filename (including hashedDirectoryStructure) * * @param string $id Cache id * @param boolean $parts if true, returns array of directory parts instead of single string * @return string Complete directory path */ protected function _path($id, $parts = false) { $partsArray = array(); $root = $this->_options['cache_dir']; $prefix = $this->_options['file_name_prefix']; if ($this->_options['hashed_directory_level']>0) { $hash = hash('adler32', $id); for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) { $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR; $partsArray[] = $root; } } if ($parts) { return $partsArray; } else { return $root; } } /** * Make the directory strucuture for the given id * * @param string $id cache id * @return boolean true */ protected function _recursiveMkdirAndChmod($id) { if ($this->_options['hashed_directory_level'] <=0) { return true; } $partsArray = $this->_path($id, true); foreach ($partsArray as $part) { if (!is_dir($part)) { @mkdir($part, $this->_options['hashed_directory_perm']); @chmod($part, $this->_options['hashed_directory_perm']); // see #ZF-320 (this line is required in some configurations) } } return true; } /** * Test if the given cache id is available (and still valid as a cache record) * * @param string $id Cache id * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record */ protected function _test($id, $doNotTestCacheValidity) { $metadatas = $this->_getMetadatas($id); if (!$metadatas) { return false; } if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) { return $metadatas['mtime']; } return false; } /** * Return the file content of the given file * * @param string $file File complete path * @return string File content (or false if problem) */ protected function _fileGetContents($file) { $result = false; if (!is_file($file)) { return false; } $f = @fopen($file, 'rb'); if ($f) { if ($this->_options['file_locking']) @flock($f, LOCK_SH); $result = stream_get_contents($f); if ($this->_options['file_locking']) @flock($f, LOCK_UN); @fclose($f); } return $result; } /** * Put the given string into the given file * * @param string $file File complete path * @param string $string String to put in file * @return boolean true if no problem */ protected function _filePutContents($file, $string) { $result = false; $f = @fopen($file, 'ab+'); if ($f) { if ($this->_options['file_locking']) @flock($f, LOCK_EX); fseek($f, 0); ftruncate($f, 0); $tmp = @fwrite($f, $string); if (!($tmp === FALSE)) { $result = true; } @fclose($f); } @chmod($file, $this->_options['cache_file_perm']); return $result; } /** * Transform a file name into cache id and return it * * @param string $fileName File name * @return string Cache id */ protected function _fileNameToId($fileName) { $prefix = $this->_options['file_name_prefix']; return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName); } } ================================================ FILE: libraries/Zend/Cache/Backend/Interface.php ================================================ infinite lifetime) * @return boolean true if no problem */ public function save($data, $id, $tags = array(), $specificLifetime = false); /** * Remove a cache record * * @param string $id Cache id * @return boolean True if no problem */ public function remove($id); /** * Clean some cache records * * Available modes are : * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * * @param string $mode Clean mode * @param array $tags Array of tags * @return boolean true if no problem */ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()); } ================================================ FILE: libraries/Zend/Cache/Backend.php ================================================ (int) lifetime : * - Cache lifetime (in seconds) * - If null, the cache is valid forever * * =====> (int) logging : * - if set to true, a logging is activated throw Zend_Log * * @var array directives */ protected $_directives = array( 'lifetime' => 3600, 'logging' => false, 'logger' => null ); /** * Available options * * @var array available options */ protected $_options = array(); /** * Constructor * * @param array $options Associative array of options * @throws Zend_Cache_Exception * @return void */ public function __construct(array $options = array()) { while (list($name, $value) = each($options)) { $this->setOption($name, $value); } } /** * Set the frontend directives * * @param array $directives Assoc of directives * @throws Zend_Cache_Exception * @return void */ public function setDirectives($directives) { if (!is_array($directives)) Zend_Cache::throwException('Directives parameter must be an array'); while (list($name, $value) = each($directives)) { if (!is_string($name)) { Zend_Cache::throwException("Incorrect option name : $name"); } $name = strtolower($name); if (array_key_exists($name, $this->_directives)) { $this->_directives[$name] = $value; } } $this->_loggerSanity(); } /** * Set an option * * @param string $name * @param mixed $value * @throws Zend_Cache_Exception * @return void */ public function setOption($name, $value) { if (!is_string($name)) { Zend_Cache::throwException("Incorrect option name : $name"); } $name = strtolower($name); if (array_key_exists($name, $this->_options)) { $this->_options[$name] = $value; } } /** * Returns an option * * @param string $name Optional, the options name to return * @throws Zend_Cache_Exceptions * @return mixed */ public function getOption($name) { $name = strtolower($name); if (array_key_exists($name, $this->_options)) { return $this->_options[$name]; } if (array_key_exists($name, $this->_directives)) { return $this->_directives[$name]; } Zend_Cache::throwException("Incorrect option name : {$name}"); } /** * Get the life time * * if $specificLifetime is not false, the given specific life time is used * else, the global lifetime is used * * @param int $specificLifetime * @return int Cache life time */ public function getLifetime($specificLifetime) { if ($specificLifetime === false) { return $this->_directives['lifetime']; } return $specificLifetime; } /** * Return true if the automatic cleaning is available for the backend * * DEPRECATED : use getCapabilities() instead * * @deprecated * @return boolean */ public function isAutomaticCleaningAvailable() { return true; } /** * Determine system TMP directory and detect if we have read access * * inspired from Zend_File_Transfer_Adapter_Abstract * * @return string * @throws Zend_Cache_Exception if unable to determine directory */ public function getTmpDir() { $tmpdir = array(); foreach (array($_ENV, $_SERVER) as $tab) { foreach (array('TMPDIR', 'TEMP', 'TMP', 'windir', 'SystemRoot') as $key) { if (isset($tab[$key]) && is_string($tab[$key])) { if (($key == 'windir') or ($key == 'SystemRoot')) { $dir = realpath($tab[$key] . '\\temp'); } else { $dir = realpath($tab[$key]); } if ($this->_isGoodTmpDir($dir)) { return $dir; } } } } $upload = ini_get('upload_tmp_dir'); if ($upload) { $dir = realpath($upload); if ($this->_isGoodTmpDir($dir)) { return $dir; } } if (function_exists('sys_get_temp_dir')) { $dir = sys_get_temp_dir(); if ($this->_isGoodTmpDir($dir)) { return $dir; } } // Attemp to detect by creating a temporary file $tempFile = tempnam(md5(uniqid(rand(), TRUE)), ''); if ($tempFile) { $dir = realpath(dirname($tempFile)); unlink($tempFile); if ($this->_isGoodTmpDir($dir)) { return $dir; } } if ($this->_isGoodTmpDir('/tmp')) { return '/tmp'; } if ($this->_isGoodTmpDir('\\temp')) { return '\\temp'; } Zend_Cache::throwException('Could not determine temp directory, please specify a cache_dir manually'); } /** * Verify if the given temporary directory is readable and writable * * @param string $dir temporary directory * @return boolean true if the directory is ok */ protected function _isGoodTmpDir($dir) { if (is_readable($dir)) { if (is_writable($dir)) { return true; } } return false; } /** * Make sure if we enable logging that the Zend_Log class * is available. * Create a default log object if none is set. * * @throws Zend_Cache_Exception * @return void */ protected function _loggerSanity() { if (!isset($this->_directives['logging']) || !$this->_directives['logging']) { return; } if (isset($this->_directives['logger'])) { if ($this->_directives['logger'] instanceof Zend_Log) { return; } Zend_Cache::throwException('Logger object is not an instance of Zend_Log class.'); } // Create a default logger to the standard output stream require_once 'Zend/Log.php'; require_once 'Zend/Log/Writer/Stream.php'; require_once 'Zend/Log/Filter/Priority.php'; $logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output')); $logger->addFilter(new Zend_Log_Filter_Priority(Zend_Log::WARN, '<=')); $this->_directives['logger'] = $logger; } /** * Log a message at the WARN (4) priority. * * @param string $message * @throws Zend_Cache_Exception * @return void */ protected function _log($message, $priority = 4) { if (!$this->_directives['logging']) { return; } if (!isset($this->_directives['logger'])) { Zend_Cache::throwException('Logging is enabled but logger is not set.'); } $logger = $this->_directives['logger']; if (!$logger instanceof Zend_Log) { Zend_Cache::throwException('Logger object is not an instance of Zend_Log class.'); } $logger->log($message, $priority); } } ================================================ FILE: libraries/Zend/Cache/Core.php ================================================ (boolean) write_control : * - Enable / disable write control (the cache is read just after writing to detect corrupt entries) * - Enable write control will lightly slow the cache writing but not the cache reading * Write control can detect some corrupt cache files but maybe it's not a perfect control * * ====> (boolean) caching : * - Enable / disable caching * (can be very useful for the debug of cached scripts) * * =====> (string) cache_id_prefix : * - prefix for cache ids (namespace) * * ====> (boolean) automatic_serialization : * - Enable / disable automatic serialization * - It can be used to save directly datas which aren't strings (but it's slower) * * ====> (int) automatic_cleaning_factor : * - Disable / Tune the automatic cleaning process * - The automatic cleaning process destroy too old (for the given life time) * cache files when a new cache file is written : * 0 => no automatic cache cleaning * 1 => systematic cache cleaning * x (integer) > 1 => automatic cleaning randomly 1 times on x cache write * * ====> (int) lifetime : * - Cache lifetime (in seconds) * - If null, the cache is valid forever. * * ====> (boolean) logging : * - If set to true, logging is activated (but the system is slower) * * ====> (boolean) ignore_user_abort * - If set to true, the core will set the ignore_user_abort PHP flag inside the * save() method to avoid cache corruptions in some cases (default false) * * @var array $_options available options */ protected $_options = array( 'write_control' => true, 'caching' => true, 'cache_id_prefix' => null, 'automatic_serialization' => false, 'automatic_cleaning_factor' => 10, 'lifetime' => 3600, 'logging' => false, 'logger' => null, 'ignore_user_abort' => false ); /** * Array of options which have to be transfered to backend * * @var array $_directivesList */ protected static $_directivesList = array('lifetime', 'logging', 'logger'); /** * Not used for the core, just a sort a hint to get a common setOption() method (for the core and for frontends) * * @var array $_specificOptions */ protected $_specificOptions = array(); /** * Last used cache id * * @var string $_lastId */ private $_lastId = null; /** * True if the backend implements Zend_Cache_Backend_ExtendedInterface * * @var boolean $_extendedBackend */ protected $_extendedBackend = false; /** * Array of capabilities of the backend (only if it implements Zend_Cache_Backend_ExtendedInterface) * * @var array */ protected $_backendCapabilities = array(); /** * Constructor * * @param array|Zend_Config $options Associative array of options or Zend_Config instance * @throws Zend_Cache_Exception * @return void */ public function __construct($options = array()) { if ($options instanceof Zend_Config) { $options = $options->toArray(); } if (!is_array($options)) { Zend_Cache::throwException("Options passed were not an array" . " or Zend_Config instance."); } while (list($name, $value) = each($options)) { $this->setOption($name, $value); } $this->_loggerSanity(); } /** * Set options using an instance of type Zend_Config * * @param Zend_Config $config * @return Zend_Cache_Core */ public function setConfig(Zend_Config $config) { $options = $config->toArray(); while (list($name, $value) = each($options)) { $this->setOption($name, $value); } return $this; } /** * Set the backend * * @param Zend_Cache_Backend $backendObject * @throws Zend_Cache_Exception * @return void */ public function setBackend(Zend_Cache_Backend $backendObject) { $this->_backend= $backendObject; // some options (listed in $_directivesList) have to be given // to the backend too (even if they are not "backend specific") $directives = array(); foreach (Zend_Cache_Core::$_directivesList as $directive) { $directives[$directive] = $this->_options[$directive]; } $this->_backend->setDirectives($directives); if (in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_backend))) { $this->_extendedBackend = true; $this->_backendCapabilities = $this->_backend->getCapabilities(); } } /** * Returns the backend * * @return Zend_Cache_Backend backend object */ public function getBackend() { return $this->_backend; } /** * Public frontend to set an option * * There is an additional validation (relatively to the protected _setOption method) * * @param string $name Name of the option * @param mixed $value Value of the option * @throws Zend_Cache_Exception * @return void */ public function setOption($name, $value) { if (!is_string($name)) { Zend_Cache::throwException("Incorrect option name!"); } $name = strtolower($name); if (array_key_exists($name, $this->_options)) { // This is a Core option $this->_setOption($name, $value); return; } if (array_key_exists($name, $this->_specificOptions)) { // This a specic option of this frontend $this->_specificOptions[$name] = $value; return; } } /** * Public frontend to get an option value * * @param string $name Name of the option * @throws Zend_Cache_Exception * @return mixed option value */ public function getOption($name) { $name = strtolower($name); if (array_key_exists($name, $this->_options)) { // This is a Core option return $this->_options[$name]; } if (array_key_exists($name, $this->_specificOptions)) { // This a specic option of this frontend return $this->_specificOptions[$name]; } Zend_Cache::throwException("Incorrect option name : $name"); } /** * Set an option * * @param string $name Name of the option * @param mixed $value Value of the option * @throws Zend_Cache_Exception * @return void */ private function _setOption($name, $value) { if (!is_string($name) || !array_key_exists($name, $this->_options)) { Zend_Cache::throwException("Incorrect option name : $name"); } if ($name == 'lifetime' && empty($value)) { $value = null; } $this->_options[$name] = $value; } /** * Force a new lifetime * * The new value is set for the core/frontend but for the backend too (directive) * * @param int $newLifetime New lifetime (in seconds) * @return void */ public function setLifetime($newLifetime) { $this->_options['lifetime'] = $newLifetime; $this->_backend->setDirectives(array( 'lifetime' => $newLifetime )); } /** * Test if a cache is available for the given id and (if yes) return it (false else) * * @param string $id Cache id * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested * @param boolean $doNotUnserialize Do not serialize (even if automatic_serialization is true) => for internal use * @return mixed|false Cached datas */ public function load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false) { if (!$this->_options['caching']) { return false; } $id = $this->_id($id); // cache id may need prefix $this->_lastId = $id; self::_validateIdOrTag($id); $this->_log("Zend_Cache_Core: load item '{$id}'", 7); $data = $this->_backend->load($id, $doNotTestCacheValidity); if ($data===false) { // no cache available return false; } if ((!$doNotUnserialize) && $this->_options['automatic_serialization']) { // we need to unserialize before sending the result return unserialize($data); } return $data; } /** * Test if a cache is available for the given id * * @param string $id Cache id * @return int|false Last modified time of cache entry if it is available, false otherwise */ public function test($id) { if (!$this->_options['caching']) { return false; } $id = $this->_id($id); // cache id may need prefix self::_validateIdOrTag($id); $this->_lastId = $id; $this->_log("Zend_Cache_Core: test item '{$id}'", 7); return $this->_backend->test($id); } /** * Save some data in a cache * * @param mixed $data Data to put in cache (can be another type than string if automatic_serialization is on) * @param string $id Cache id (if not set, the last cache id will be used) * @param array $tags Cache tags * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends * @throws Zend_Cache_Exception * @return boolean True if no problem */ public function save($data, $id = null, $tags = array(), $specificLifetime = false, $priority = 8) { if (!$this->_options['caching']) { return true; } if ($id === null) { $id = $this->_lastId; } else { $id = $this->_id($id); } self::_validateIdOrTag($id); self::_validateTagsArray($tags); if ($this->_options['automatic_serialization']) { // we need to serialize datas before storing them $data = serialize($data); } else { if (!is_string($data)) { Zend_Cache::throwException("Datas must be string or set automatic_serialization = true"); } } // automatic cleaning if ($this->_options['automatic_cleaning_factor'] > 0) { $rand = rand(1, $this->_options['automatic_cleaning_factor']); if ($rand==1) { // new way || deprecated way if ($this->_extendedBackend || method_exists($this->_backend, 'isAutomaticCleaningAvailable')) { $this->_log("Zend_Cache_Core::save(): automatic cleaning running", 7); $this->clean(Zend_Cache::CLEANING_MODE_OLD); } else { $this->_log("Zend_Cache_Core::save(): automatic cleaning is not available/necessary with current backend", 4); } } } $this->_log("Zend_Cache_Core: save item '{$id}'", 7); if ($this->_options['ignore_user_abort']) { $abort = ignore_user_abort(true); } if (($this->_extendedBackend) && ($this->_backendCapabilities['priority'])) { $result = $this->_backend->save($data, $id, $tags, $specificLifetime, $priority); } else { $result = $this->_backend->save($data, $id, $tags, $specificLifetime); } if ($this->_options['ignore_user_abort']) { ignore_user_abort($abort); } if (!$result) { // maybe the cache is corrupted, so we remove it ! $this->_log("Zend_Cache_Core::save(): failed to save item '{$id}' -> removing it", 4); $this->_backend->remove($id); return false; } if ($this->_options['write_control']) { $data2 = $this->_backend->load($id, true); if ($data!=$data2) { $this->_log("Zend_Cache_Core::save(): write control of item '{$id}' failed -> removing it", 4); $this->_backend->remove($id); return false; } } return true; } /** * Remove a cache * * @param string $id Cache id to remove * @return boolean True if ok */ public function remove($id) { if (!$this->_options['caching']) { return true; } $id = $this->_id($id); // cache id may need prefix self::_validateIdOrTag($id); $this->_log("Zend_Cache_Core: remove item '{$id}'", 7); return $this->_backend->remove($id); } /** * Clean cache entries * * Available modes are : * 'all' (default) => remove all cache entries ($tags is not used) * 'old' => remove too old cache entries ($tags is not used) * 'matchingTag' => remove cache entries matching all given tags * ($tags can be an array of strings or a single string) * 'notMatchingTag' => remove cache entries not matching one of the given tags * ($tags can be an array of strings or a single string) * 'matchingAnyTag' => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * * @param string $mode * @param array|string $tags * @throws Zend_Cache_Exception * @return boolean True if ok */ public function clean($mode = 'all', $tags = array()) { if (!$this->_options['caching']) { return true; } if (!in_array($mode, array(Zend_Cache::CLEANING_MODE_ALL, Zend_Cache::CLEANING_MODE_OLD, Zend_Cache::CLEANING_MODE_MATCHING_TAG, Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG, Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG))) { Zend_Cache::throwException('Invalid cleaning mode'); } self::_validateTagsArray($tags); return $this->_backend->clean($mode, $tags); } /** * Return an array of stored cache ids which match given tags * * In case of multiple tags, a logical AND is made between tags * * @param array $tags array of tags * @return array array of matching cache ids (string) */ public function getIdsMatchingTags($tags = array()) { if (!$this->_extendedBackend) { Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); } if (!($this->_backendCapabilities['tags'])) { Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG); } $ids = $this->_backend->getIdsMatchingTags($tags); // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { $prefix = & $this->_options['cache_id_prefix']; $prefixLen = strlen($prefix); foreach ($ids as &$id) { if (strpos($id, $prefix) === 0) { $id = substr($id, $prefixLen); } } } return $ids; } /** * Return an array of stored cache ids which don't match given tags * * In case of multiple tags, a logical OR is made between tags * * @param array $tags array of tags * @return array array of not matching cache ids (string) */ public function getIdsNotMatchingTags($tags = array()) { if (!$this->_extendedBackend) { Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); } if (!($this->_backendCapabilities['tags'])) { Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG); } $ids = $this->_backend->getIdsNotMatchingTags($tags); // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { $prefix = & $this->_options['cache_id_prefix']; $prefixLen = strlen($prefix); foreach ($ids as &$id) { if (strpos($id, $prefix) === 0) { $id = substr($id, $prefixLen); } } } return $ids; } /** * Return an array of stored cache ids which match any given tags * * In case of multiple tags, a logical OR is made between tags * * @param array $tags array of tags * @return array array of matching any cache ids (string) */ public function getIdsMatchingAnyTags($tags = array()) { if (!$this->_extendedBackend) { Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); } if (!($this->_backendCapabilities['tags'])) { Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG); } $ids = $this->_backend->getIdsMatchingAnyTags($tags); // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { $prefix = & $this->_options['cache_id_prefix']; $prefixLen = strlen($prefix); foreach ($ids as &$id) { if (strpos($id, $prefix) === 0) { $id = substr($id, $prefixLen); } } } return $ids; } /** * Return an array of stored cache ids * * @return array array of stored cache ids (string) */ public function getIds() { if (!$this->_extendedBackend) { Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); } $ids = $this->_backend->getIds(); // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { $prefix = & $this->_options['cache_id_prefix']; $prefixLen = strlen($prefix); foreach ($ids as &$id) { if (strpos($id, $prefix) === 0) { $id = substr($id, $prefixLen); } } } return $ids; } /** * Return an array of stored tags * * @return array array of stored tags (string) */ public function getTags() { if (!$this->_extendedBackend) { Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); } if (!($this->_backendCapabilities['tags'])) { Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG); } return $this->_backend->getTags(); } /** * Return the filling percentage of the backend storage * * @return int integer between 0 and 100 */ public function getFillingPercentage() { if (!$this->_extendedBackend) { Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); } return $this->_backend->getFillingPercentage(); } /** * Return an array of metadatas for the given cache id * * The array will include these keys : * - expire : the expire timestamp * - tags : a string array of tags * - mtime : timestamp of last modification time * * @param string $id cache id * @return array array of metadatas (false if the cache id is not found) */ public function getMetadatas($id) { if (!$this->_extendedBackend) { Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); } $id = $this->_id($id); // cache id may need prefix return $this->_backend->getMetadatas($id); } /** * Give (if possible) an extra lifetime to the given cache id * * @param string $id cache id * @param int $extraLifetime * @return boolean true if ok */ public function touch($id, $extraLifetime) { if (!$this->_extendedBackend) { Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); } $id = $this->_id($id); // cache id may need prefix $this->_log("Zend_Cache_Core: touch item '{$id}'", 7); return $this->_backend->touch($id, $extraLifetime); } /** * Validate a cache id or a tag (security, reliable filenames, reserved prefixes...) * * Throw an exception if a problem is found * * @param string $string Cache id or tag * @throws Zend_Cache_Exception * @return void */ protected static function _validateIdOrTag($string) { if (!is_string($string)) { Zend_Cache::throwException('Invalid id or tag : must be a string'); } if (substr($string, 0, 9) == 'internal-') { Zend_Cache::throwException('"internal-*" ids or tags are reserved'); } if (!preg_match('~^[a-zA-Z0-9_]+$~D', $string)) { Zend_Cache::throwException("Invalid id or tag '$string' : must use only [a-zA-Z0-9_]"); } } /** * Validate a tags array (security, reliable filenames, reserved prefixes...) * * Throw an exception if a problem is found * * @param array $tags Array of tags * @throws Zend_Cache_Exception * @return void */ protected static function _validateTagsArray($tags) { if (!is_array($tags)) { Zend_Cache::throwException('Invalid tags array : must be an array'); } foreach($tags as $tag) { self::_validateIdOrTag($tag); } reset($tags); } /** * Make sure if we enable logging that the Zend_Log class * is available. * Create a default log object if none is set. * * @throws Zend_Cache_Exception * @return void */ protected function _loggerSanity() { if (!isset($this->_options['logging']) || !$this->_options['logging']) { return; } if (isset($this->_options['logger']) && $this->_options['logger'] instanceof Zend_Log) { return; } // Create a default logger to the standard output stream require_once 'Zend/Log.php'; require_once 'Zend/Log/Writer/Stream.php'; require_once 'Zend/Log/Filter/Priority.php'; $logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output')); $logger->addFilter(new Zend_Log_Filter_Priority(Zend_Log::WARN, '<=')); $this->_options['logger'] = $logger; } /** * Log a message at the WARN (4) priority. * * @param string $message * @throws Zend_Cache_Exception * @return void */ protected function _log($message, $priority = 4) { if (!$this->_options['logging']) { return; } if (!(isset($this->_options['logger']) || $this->_options['logger'] instanceof Zend_Log)) { Zend_Cache::throwException('Logging is enabled but logger is not set'); } $logger = $this->_options['logger']; $logger->log($message, $priority); } /** * Make and return a cache id * * Checks 'cache_id_prefix' and returns new id with prefix or simply the id if null * * @param string $id Cache id * @return string Cache id (with or without prefix) */ protected function _id($id) { if (($id !== null) && isset($this->_options['cache_id_prefix'])) { return $this->_options['cache_id_prefix'] . $id; // return with prefix } return $id; // no prefix, just return the $id passed } } ================================================ FILE: libraries/Zend/Cache/Exception.php ================================================ setBackend($backendObject); return $frontendObject; } /** * Backend Constructor * * @param string $backend * @param array $backendOptions * @param boolean $customBackendNaming * @param boolean $autoload * @return Zend_Cache_Backend */ public static function _makeBackend($backend, $backendOptions, $customBackendNaming = false, $autoload = false) { if (!$customBackendNaming) { $backend = self::_normalizeName($backend); } if (in_array($backend, Zend_Cache::$standardBackends)) { // we use a standard backend $backendClass = 'Zend_Cache_Backend_' . $backend; // security controls are explicit require_once realpath(dirname(__FILE__).'/..').DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $backendClass) . '.php'; } else { // we use a custom backend if (!preg_match('~^[\w\\\\]+$~D', $backend)) { Zend_Cache::throwException("Invalid backend name [$backend]"); } if (!$customBackendNaming) { // we use this boolean to avoid an API break $backendClass = 'Zend_Cache_Backend_' . $backend; } else { $backendClass = $backend; } if (!$autoload) { $file = str_replace('_', DIRECTORY_SEPARATOR, $backendClass) . '.php'; if (!(self::_isReadable($file))) { self::throwException("file $file not found in include_path"); } require_once $file; } } return new $backendClass($backendOptions); } /** * Frontend Constructor * * @param string $frontend * @param array $frontendOptions * @param boolean $customFrontendNaming * @param boolean $autoload * @return Zend_Cache_Core|Zend_Cache_Frontend */ public static function _makeFrontend($frontend, $frontendOptions = array(), $customFrontendNaming = false, $autoload = false) { if (!$customFrontendNaming) { $frontend = self::_normalizeName($frontend); } if (in_array($frontend, self::$standardFrontends)) { // we use a standard frontend // For perfs reasons, with frontend == 'Core', we can interact with the Core itself $frontendClass = 'Zend_Cache_' . ($frontend != 'Core' ? 'Frontend_' : '') . $frontend; // security controls are explicit require_once realpath(dirname(__FILE__).'/..').DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $frontendClass) . '.php'; } else { // we use a custom frontend if (!preg_match('~^[\w\\\\]+$~D', $frontend)) { Zend_Cache::throwException("Invalid frontend name [$frontend]"); } if (!$customFrontendNaming) { // we use this boolean to avoid an API break $frontendClass = 'Zend_Cache_Frontend_' . $frontend; } else { $frontendClass = $frontend; } if (!$autoload) { $file = str_replace('_', DIRECTORY_SEPARATOR, $frontendClass) . '.php'; if (!(self::_isReadable($file))) { self::throwException("file $file not found in include_path"); } require_once $file; } } return new $frontendClass($frontendOptions); } /** * Throw an exception * * Note : for perf reasons, the "load" of Zend/Cache/Exception is dynamic * @param string $msg Message for the exception * @throws Zend_Cache_Exception */ public static function throwException($msg, Exception $e = null) { // For perfs reasons, we use this dynamic inclusion require_once 'Zend/Cache/Exception.php'; throw new Zend_Cache_Exception($msg, 0, $e); } /** * Normalize frontend and backend names to allow multiple words TitleCased * * @param string $name Name to normalize * @return string */ protected static function _normalizeName($name) { $name = ucfirst(strtolower($name)); $name = str_replace(array('-', '_', '.'), ' ', $name); $name = ucwords($name); $name = str_replace(' ', '', $name); if (stripos($name, 'ZendServer') === 0) { $name = 'ZendServer_' . substr($name, strlen('ZendServer')); } return $name; } /** * Returns TRUE if the $filename is readable, or FALSE otherwise. * This function uses the PHP include_path, where PHP's is_readable() * does not. * * Note : this method comes from Zend_Loader (see #ZF-2891 for details) * * @param string $filename * @return boolean */ private static function _isReadable($filename) { if (!$fh = @fopen($filename, 'r', true)) { return false; } @fclose($fh); return true; } } ================================================ FILE: libraries/Zend/Exception.php ================================================ _previous = $previous; } else { parent::__construct($msg, (int) $code, $previous); } } /** * Overloading * * For PHP < 5.3.0, provides access to the getPrevious() method. * * @param string $method * @param array $args * @return mixed */ public function __call($method, array $args) { if ('getprevious' == strtolower($method)) { return $this->_getPrevious(); } return null; } /** * String representation of the exception * * @return string */ public function __toString() { if (version_compare(PHP_VERSION, '5.3.0', '<')) { if (null !== ($e = $this->getPrevious())) { return $e->__toString() . "\n\nNext " . parent::__toString(); } } return parent::__toString(); } /** * Returns previous Exception * * @return Exception|null */ protected function _getPrevious() { return $this->_previous; } } ================================================ FILE: libraries/content-extractor/ContentExtractor.php ================================================ debug) { $mem = round(memory_get_usage()/1024, 2); $memPeak = round(memory_get_peak_usage()/1024, 2); echo '* ',$msg,"
    "; if ($this->debugVerbose) echo ' - mem used: ',$mem," (peak: $memPeak)"; echo "\n"; ob_flush(); flush(); } } public function reset() { $this->html = null; $this->readability = null; $this->config = null; $this->title = null; $this->body = null; $this->author = array(); $this->language = null; $this->date = null; $this->nextPageUrl = null; $this->success = false; } public function findHostUsingFingerprints($html) { $this->debug('Checking fingerprints...'); $head = substr($html, 0, 8000); foreach ($this->fingerprints as $_fp => $_fphost) { $lookin = 'html'; if (is_array($_fphost)) { if (isset($_fphost['head']) && $_fphost['head']) { $lookin = 'head'; } $_fphost = $_fphost['hostname']; } if (strpos($$lookin, $_fp) !== false) { $this->debug("Found match: $_fphost"); return $_fphost; } } $this->debug('No fingerprint matches'); return false; } // returns SiteConfig instance (joined in order: exact match, wildcard, fingerprint, global, default) public function buildSiteConfig($url, $html='', $add_to_cache=true) { // extract host name $host = @parse_url($url, PHP_URL_HOST); $host = strtolower($host); if (substr($host, 0, 4) == 'www.') $host = substr($host, 4); // is merged version already cached? if (SiteConfig::is_cached("$host.merged")) { $this->debug("Returning cached and merged site config for $host"); return SiteConfig::build("$host.merged"); } // let's build from site_config/custom/ and standard/ $config = SiteConfig::build($host); if ($add_to_cache && $config && !SiteConfig::is_cached("$host")) { SiteConfig::add_to_cache($host, $config); } // if no match, use defaults if (!$config) $config = new SiteConfig(); // load fingerprint config? if ($config->autodetect_on_failure()) { // check HTML for fingerprints if (!empty($this->fingerprints) && ($_fphost = $this->findHostUsingFingerprints($html))) { if ($config_fingerprint = SiteConfig::build($_fphost)) { $this->debug("Appending site config settings from $_fphost (fingerprint match)"); $config->append($config_fingerprint); if ($add_to_cache && !SiteConfig::is_cached($_fphost)) { //$config_fingerprint->cache_in_apc = true; SiteConfig::add_to_cache($_fphost, $config_fingerprint); } } } } // load global config? if ($config->autodetect_on_failure()) { if ($config_global = SiteConfig::build('global', true)) { $this->debug('Appending site config settings from global.txt'); $config->append($config_global); if ($add_to_cache && !SiteConfig::is_cached('global')) { //$config_global->cache_in_apc = true; SiteConfig::add_to_cache('global', $config_global); } } } // store copy of merged config if ($add_to_cache) { // do not store in APC if wildcard match $use_apc = ($host == $config->cache_key); $config->cache_key = null; SiteConfig::add_to_cache("$host.merged", $config, $use_apc); } return $config; } // returns true on success, false on failure // $smart_tidy indicates that if tidy is used and no results are produced, we will // try again without it. Tidy helps us deal with PHP's patchy HTML parsing most of the time // but it has problems of its own which we try to avoid with this option. public function process($html, $url, $smart_tidy=true) { $this->reset(); $this->config = $this->buildSiteConfig($url, $html); // do string replacements if (!empty($this->config->find_string)) { if (count($this->config->find_string) == count($this->config->replace_string)) { $html = str_replace($this->config->find_string, $this->config->replace_string, $html, $_count); $this->debug("Strings replaced: $_count (find_string and/or replace_string)"); } else { $this->debug('Skipped string replacement - incorrect number of find-replace strings in site config'); } unset($_count); } // load and parse html $_parser = $this->config->parser(); if (!in_array($_parser, $this->allowedParsers)) { $this->debug("HTML parser $_parser not listed, using libxml instead"); $_parser = 'libxml'; } $this->debug("Attempting to parse HTML with $_parser"); $this->readability = new Readability($html, $url, $_parser, $this->config->tidy() && $smart_tidy); $tidied = $this->readability->tidied; // we use xpath to find elements in the given HTML document; see http://en.wikipedia.org/wiki/XPath_1.0 $xpath = new DOMXPath($this->readability->dom); // skip entries (using xpath expressions) foreach ($this->config->skip_entry as $pattern) { $elems = @$xpath->evaluate($pattern, $this->readability->dom); // check for matches if (is_string($elems) || ($elems instanceof DOMNodeList && $elems->length > 0)) { $this->debug('Skipping entry on pattern.'); $this->debug("...XPath match: $pattern"); return false; } } // try to get next page link foreach ($this->config->next_page_link as $pattern) { $elems = @$xpath->evaluate($pattern, $this->readability->dom); if (is_string($elems)) { $this->nextPageUrl = trim($elems); break; } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { foreach ($elems as $item) { if ($item instanceof DOMElement && $item->hasAttribute('href')) { $this->nextPageUrl = $item->getAttribute('href'); break 2; } elseif ($item instanceof DOMAttr && $item->value) { $this->nextPageUrl = $item->value; break 2; } } } } // try to get title foreach ($this->config->title as $pattern) { // $this->debug("Trying to get title $pattern"); $elems = @$xpath->evaluate($pattern, $this->readability->dom); if (is_string($elems)) { $this->title = trim($elems); $this->debug('Title expression evaluated as string: '.$this->title); $this->debug("...XPath match: $pattern"); break; } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { $this->title = $elems->item(0)->textContent; $this->debug('Title matched: '.$this->title); $this->debug("...XPath match: $pattern"); // remove title from document try { @$elems->item(0)->parentNode->removeChild($elems->item(0)); } catch (DOMException $e) { // do nothing } break; } } // try to get author (if it hasn't already been set) if (empty($this->author)) { foreach ($this->config->author as $pattern) { $elems = @$xpath->evaluate($pattern, $this->readability->dom); if (is_string($elems)) { if (trim($elems) != '') { $this->author[] = trim($elems); $this->debug('Author expression evaluated as string: '.trim($elems)); $this->debug("...XPath match: $pattern"); break; } } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { foreach ($elems as $elem) { if (!isset($elem->parentNode)) continue; $this->author[] = trim($elem->textContent); $this->debug('Author matched: '.trim($elem->textContent)); } if (!empty($this->author)) { $this->debug("...XPath match: $pattern"); break; } } } } // try to get language $_lang_xpath = array('//html[@lang]/@lang', '//body[@lang]/@lang', '//meta[@name="DC.language"]/@content'); foreach ($_lang_xpath as $pattern) { $elems = @$xpath->evaluate($pattern, $this->readability->dom); if (is_string($elems)) { if (trim($elems) != '') { $this->language = trim($elems); $this->debug('Language matched: '.$this->language); break; } } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { foreach ($elems as $elem) { if (!isset($elem->parentNode)) continue; $this->language = trim($elem->textContent); $this->debug('Language matched: '.$this->language); } if ($this->language) break; } } // try to get date foreach ($this->config->date as $pattern) { $elems = @$xpath->evaluate($pattern, $this->readability->dom); if (is_string($elems)) { $this->date = strtotime(trim($elems, "; \t\n\r\0\x0B")); } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { $this->date = $elems->item(0)->textContent; $this->date = strtotime(trim($this->date, "; \t\n\r\0\x0B")); // remove date from document // $elems->item(0)->parentNode->removeChild($elems->item(0)); } if (!$this->date) { $this->date = null; } else { $this->debug('Date matched: '.date('Y-m-d H:i:s', $this->date)); $this->debug("...XPath match: $pattern"); break; } } // strip elements (using xpath expressions) foreach ($this->config->strip as $pattern) { $elems = @$xpath->query($pattern, $this->readability->dom); // check for matches if ($elems && $elems->length > 0) { $this->debug('Stripping '.$elems->length.' elements (strip)'); for ($i=$elems->length-1; $i >= 0; $i--) { $elems->item($i)->parentNode->removeChild($elems->item($i)); } } } // strip elements (using id and class attribute values) foreach ($this->config->strip_id_or_class as $string) { $string = strtr($string, array("'"=>'', '"'=>'')); $elems = @$xpath->query("//*[contains(@class, '$string') or contains(@id, '$string')]", $this->readability->dom); // check for matches if ($elems && $elems->length > 0) { $this->debug('Stripping '.$elems->length.' elements (strip_id_or_class)'); for ($i=$elems->length-1; $i >= 0; $i--) { $elems->item($i)->parentNode->removeChild($elems->item($i)); } } } // strip images (using src attribute values) foreach ($this->config->strip_image_src as $string) { $string = strtr($string, array("'"=>'', '"'=>'')); $elems = @$xpath->query("//img[contains(@src, '$string')]", $this->readability->dom); // check for matches if ($elems && $elems->length > 0) { $this->debug('Stripping '.$elems->length.' image elements'); for ($i=$elems->length-1; $i >= 0; $i--) { $elems->item($i)->parentNode->removeChild($elems->item($i)); } } } // strip elements using Readability.com and Instapaper.com ignore class names // .entry-unrelated and .instapaper_ignore // See https://www.readability.com/publishers/guidelines/#view-plainGuidelines // and http://blog.instapaper.com/post/730281947 $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' entry-unrelated ') or contains(concat(' ',normalize-space(@class),' '),' instapaper_ignore ')]", $this->readability->dom); // check for matches if ($elems && $elems->length > 0) { $this->debug('Stripping '.$elems->length.' .entry-unrelated,.instapaper_ignore elements'); for ($i=$elems->length-1; $i >= 0; $i--) { $elems->item($i)->parentNode->removeChild($elems->item($i)); } } // strip elements that contain style 'display: none' or 'visibility:hidden' $elems = @$xpath->query("//*[contains(@style,'display:none') or contains(@style,'visibility:hidden')]", $this->readability->dom); // check for matches if ($elems && $elems->length > 0) { $this->debug('Stripping '.$elems->length.' elements with inline display:none or visibility:hidden style'); for ($i=$elems->length-1; $i >= 0; $i--) { $elems->item($i)->parentNode->removeChild($elems->item($i)); } } // try to get body foreach ($this->config->body as $pattern) { $elems = @$xpath->query($pattern, $this->readability->dom); $this->debug("Matched $elems->length content element(s)"); // check for matches if ($elems && $elems->length > 0) { $this->debug('Body matched'); $this->debug("...XPath match: $pattern"); if ($elems->length == 1) { $this->body = $elems->item(0); // prune (clean up elements that may not be content) if ($this->config->prune()) { $this->debug('...pruning content'); $this->readability->prepArticle($this->body); } break; } else { $this->body = $this->readability->dom->createElement('div'); $this->debug($elems->length.' body elems found'); $len = 0; foreach ($elems as $elem) { if (!isset($elem->parentNode)) continue; $isDescendant = false; foreach ($this->body->childNodes as $parent) { if ($this->isDescendant($parent, $elem)) { $isDescendant = true; break; } } if ($isDescendant) { $this->debug('...element is child of another body element, skipping.'); } else { // prune (clean up elements that may not be content) if ($this->config->prune()) { $this->debug('...pruning content'); $this->readability->prepArticle($elem); } if ($elem) { $len++; $this->body->appendChild($elem); } } } $this->debug('...'.$len.' elements added to body'); unset($len); if ($this->body->hasChildNodes()) break; } } } // auto detect? $detect_title = $detect_body = $detect_author = $detect_date = false; // detect title? if (!isset($this->title)) { if (empty($this->config->title) || $this->config->autodetect_on_failure()) { $detect_title = true; } } // detect body? if (!isset($this->body)) { if (empty($this->config->body) || $this->config->autodetect_on_failure()) { $detect_body = true; } } // detect author? if (empty($this->author)) { if (empty($this->config->author) || $this->config->autodetect_on_failure()) { $detect_author = true; } } // detect date? if (!isset($this->date)) { if (empty($this->config->date) || $this->config->autodetect_on_failure()) { $detect_date = true; } } // check for hNews if ($detect_title || $detect_body) { // check for hentry $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' hentry ')]", $this->readability->dom); if ($elems && $elems->length > 0) { $this->debug('hNews: found hentry'); $hentry = $elems->item(0); if ($detect_title) { // check for entry-title $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' entry-title ')]", $hentry); if ($elems && $elems->length > 0) { $this->title = $elems->item(0)->textContent; $this->debug('hNews: found entry-title: '.$this->title); // remove title from document $elems->item(0)->parentNode->removeChild($elems->item(0)); $detect_title = false; } } if ($detect_date) { // check for time element with pubdate attribute $elems = @$xpath->query(".//time[@pubdate] | .//abbr[contains(concat(' ',normalize-space(@class),' '),' published ')]", $hentry); if ($elems && $elems->length > 0) { $this->date = strtotime(trim($elems->item(0)->textContent)); // remove date from document //$elems->item(0)->parentNode->removeChild($elems->item(0)); if ($this->date) { $this->debug('hNews: found publication date: '.date('Y-m-d H:i:s', $this->date)); $detect_date = false; } else { $this->date = null; } } } if ($detect_author) { // check for time element with pubdate attribute $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' vcard ') and (contains(concat(' ',normalize-space(@class),' '),' author ') or contains(concat(' ',normalize-space(@class),' '),' byline '))]", $hentry); if ($elems && $elems->length > 0) { $author = $elems->item(0); $fn = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' fn ')]", $author); if ($fn && $fn->length > 0) { foreach ($fn as $_fn) { if (trim($_fn->textContent) != '') { $this->author[] = trim($_fn->textContent); $this->debug('hNews: found author: '.trim($_fn->textContent)); } } } else { if (trim($author->textContent) != '') { $this->author[] = trim($author->textContent); $this->debug('hNews: found author: '.trim($author->textContent)); } } $detect_author = empty($this->author); } } // check for entry-content. // according to hAtom spec, if there are multiple elements marked entry-content, // we include all of these in the order they appear - see http://microformats.org/wiki/hatom#Entry_Content if ($detect_body) { $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' entry-content ')]", $hentry); if ($elems && $elems->length > 0) { $this->debug('hNews: found entry-content'); if ($elems->length == 1) { // what if it's empty? (some sites misuse hNews - place their content outside an empty entry-content element) $e = $elems->item(0); if (strcasecmp($e->tagName, 'img') == 0 || (trim($e->textContent) != '')) { $this->body = $elems->item(0); // prune (clean up elements that may not be content) if ($this->config->prune()) { $this->debug('Pruning content'); $this->readability->prepArticle($this->body); } $detect_body = false; } else { $this->debug('hNews: skipping entry-content - appears not to contain content'); } unset($e); } else { $this->body = $this->readability->dom->createElement('div'); $this->debug($elems->length.' entry-content elems found'); foreach ($elems as $elem) { if (!isset($elem->parentNode)) continue; $isDescendant = false; foreach ($this->body->childNodes as $parent) { if ($this->isDescendant($parent, $elem)) { $isDescendant = true; break; } } if ($isDescendant) { $this->debug('Element is child of another body element, skipping.'); } else { // prune (clean up elements that may not be content) if ($this->config->prune()) { $this->debug('Pruning content'); $this->readability->prepArticle($elem); } $this->debug('Element added to body'); $this->body->appendChild($elem); } } $detect_body = false; } } } } } // check for elements marked with instapaper_title if ($detect_title) { // check for instapaper_title $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' instapaper_title ')]", $this->readability->dom); if ($elems && $elems->length > 0) { $this->title = $elems->item(0)->textContent; $this->debug('Title found (.instapaper_title): '.$this->title); // remove title from document $elems->item(0)->parentNode->removeChild($elems->item(0)); $detect_title = false; } } // check for elements marked with instapaper_body if ($detect_body) { $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' instapaper_body ')]", $this->readability->dom); if ($elems && $elems->length > 0) { $this->debug('body found (.instapaper_body)'); $this->body = $elems->item(0); // prune (clean up elements that may not be content) if ($this->config->prune()) { $this->debug('Pruning content'); $this->readability->prepArticle($this->body); } $detect_body = false; } } // Find author in rel="author" marked element // We only use this if there's exactly one. // If there's more than one, it could indicate more than // one author, but it could also indicate that we're processing // a page listing different articles with different authors. if ($detect_author) { $elems = @$xpath->query("//a[contains(concat(' ',normalize-space(@rel),' '),' author ')]", $this->readability->dom); if ($elems && $elems->length == 1) { $author = trim($elems->item(0)->textContent); if ($author != '') { $this->debug("Author found (rel=\"author\"): $author"); $this->author[] = $author; $detect_author = false; } } } // Find date in pubdate marked time element // For the same reason given above, we only use this // if there's exactly one element. if ($detect_date) { $elems = @$xpath->query("//time[@pubdate]", $this->readability->dom); if ($elems && $elems->length == 1) { $this->date = strtotime(trim($elems->item(0)->textContent)); // remove date from document //$elems->item(0)->parentNode->removeChild($elems->item(0)); if ($this->date) { $this->debug('Date found (pubdate marked time element): '.date('Y-m-d H:i:s', $this->date)); $detect_date = false; } else { $this->date = null; } } } // still missing title or body, so we detect using Readability if ($detect_title || $detect_body) { $this->debug('Using Readability'); // clone body if we're only using Readability for title (otherwise it may interfere with body element) if (isset($this->body)) $this->body = $this->body->cloneNode(true); $success = $this->readability->init(); } if ($detect_title) { $this->title = $this->readability->getTitle()->textContent; $this->debug("Detected title \"$this->title\""); } if ($detect_body && $success) { $this->debug('Detecting body'); $this->body = $this->readability->getContent(); if ($this->body->childNodes->length == 1 && $this->body->firstChild->nodeType === XML_ELEMENT_NODE) { $this->body = $this->body->firstChild; } // prune (clean up elements that may not be content) if ($this->config->prune()) { $this->debug('Pruning content'); $this->readability->prepArticle($this->body); } } if (isset($this->body)) { // remove scripts $this->readability->removeScripts($this->body); // remove any h1-h6 elements that appear as first thing in the body // and which match our title if (isset($this->title) && ($this->title != '')) { $firstChild = $this->body->firstChild; while ($firstChild->nodeType && ($firstChild->nodeType !== XML_ELEMENT_NODE)) { $firstChild = $firstChild->nextSibling; } if (($firstChild->nodeType === XML_ELEMENT_NODE) && in_array(strtolower($firstChild->tagName), array('h1', 'h2', 'h3', 'h4', 'h5', 'h6')) && (strtolower(trim($firstChild->textContent)) == strtolower(trim($this->title)))) { $this->body->removeChild($firstChild); } } // prevent self-closing iframes $elems = $this->body->getElementsByTagName('iframe'); for ($i = $elems->length-1; $i >= 0; $i--) { $e = $elems->item($i); if (!$e->hasChildNodes()) { $e->appendChild($this->body->ownerDocument->createTextNode('[embedded content]')); } } // remove image lazy loading - WordPress plugin http://wordpress.org/extend/plugins/lazy-load/ // the plugin replaces the src attribute to point to a 1x1 gif and puts the original src // inside the data-lazy-src attribute. It also places the original image inside a noscript element // next to the amended one. $elems = @$xpath->query("//img[@data-lazy-src]|//img[@data-src]", $this->body); for ($i = $elems->length-1; $i >= 0; $i--) { $e = $elems->item($i); // let's see if we can grab image from noscript if ($e->nextSibling !== null && $e->nextSibling->nodeName === 'noscript') { $_new_elem = $e->ownerDocument->createDocumentFragment(); @$_new_elem->appendXML($e->nextSibling->innerHTML); $e->nextSibling->parentNode->replaceChild($_new_elem, $e->nextSibling); $e->parentNode->removeChild($e); } else { // Use data[-lazy]-src as src value $src = $e->getAttribute('data-lazy-src'); $e->removeAttribute('data-lazy-src'); if (!$src) { $src = $e->getAttribute('data-src'); $e->removeAttribute('data-src'); } $e->setAttribute('src', $src); } } $this->success = true; } // if we've had no success and we've used tidy, there's a chance // that tidy has messed up. So let's try again without tidy... if (!$this->success && $tidied && $smart_tidy) { $this->debug('Trying again without tidy'); unset($this->readability, $this->body, $xpath); return $this->process($original_html, $url, false); } if ($this->success && $this->config->images_to_datauri()) { $this->debug('Converting images to data-URI'); $this->readability->imageCache->cacheFromDocument($this->body); } return $this->success; } private function isDescendant(DOMElement $parent, DOMElement $child) { $node = $child->parentNode; while ($node != null) { if ($node->isSameNode($parent)) return true; $node = $node->parentNode; } return false; } public function getContent() { return $this->body; } public function getTitle() { return $this->title; } public function getAuthors() { return $this->author; } public function getLanguage() { return $this->language; } public function getDate() { return $this->date; } public function getSiteConfig() { return $this->config; } public function getNextPageUrl() { return $this->nextPageUrl; } } ================================================ FILE: libraries/content-extractor/SiteConfig.php ================================================ "; //echo ' - mem used: ',$mem," (peak: $memPeak)\n"; echo "\n"; ob_flush(); flush(); } } // enable APC caching of certain site config files? // If enabled the following site config files will be // cached in APC cache (when requested for first time): // * anything in site_config/custom/ and its corresponding file in site_config/standard/ // * the site config files associated with HTML fingerprints // * the global site config file // returns true if enabled, false otherwise public static function use_apc($apc=true) { if (!function_exists('apc_add')) { if ($apc) self::debug('APC will not be used (function apc_add does not exist)'); return false; } self::$apc = $apc; return $apc; } // return bool or null public function tidy($use_default=true) { if ($use_default) return (isset($this->tidy)) ? $this->tidy : $this->default_tidy; return $this->tidy; } // return bool or null public function prune($use_default=true) { if ($use_default) return (isset($this->prune)) ? $this->prune : $this->default_prune; return $this->prune; } // return bool or null public function images_to_datauri($use_default=true) { if ($use_default) return (isset($this->images_to_datauri)) ? $this->images_to_datauri : $this->default_images_to_datauri; return $this->images_to_datauri; } // return string or null public function parser($use_default=true) { if ($use_default) return (isset($this->parser)) ? $this->parser : $this->default_parser; return $this->parser; } // return bool or null public function autodetect_on_failure($use_default=true) { if ($use_default) return (isset($this->autodetect_on_failure)) ? $this->autodetect_on_failure : $this->default_autodetect_on_failure; return $this->autodetect_on_failure; } public static function set_config_path($path, $fallback=null) { self::$config_path = $path; self::$config_path_fallback = $fallback; } public static function add_to_cache($key, SiteConfig $config, $use_apc=true) { $key = strtolower($key); if (substr($key, 0, 4) == 'www.') $key = substr($key, 4); if ($config->cache_key) $key = $config->cache_key; self::$config_cache[$key] = $config; if (self::$apc && $use_apc) { self::debug("Adding site config to APC cache with key sc.$key"); apc_add("sc.$key", $config); } self::debug("Cached site config with key $key"); } public static function is_cached($key) { $key = strtolower($key); if (substr($key, 0, 4) == 'www.') $key = substr($key, 4); if (array_key_exists($key, self::$config_cache)) { return true; } elseif (self::$apc && (bool)apc_fetch("sc.$key")) { return true; } return false; } public function append(SiteConfig $newconfig) { // check for commands where we accept multiple statements (no test_url) foreach (array('skip_entry', 'title', 'body', 'author', 'date', 'strip', 'strip_id_or_class', 'strip_image_src', 'single_page_link', 'single_page_link_in_feed', 'next_page_link', 'http_header') as $var) { $this->$var = array_unique(array_merge($this->$var, $newconfig->$var)); } // check for single statement commands; we do not overwrite existing non null values foreach (array('tidy', 'prune', 'parser', 'autodetect_on_failure') as $var) { if ($this->$var === null) $this->$var = $newconfig->$var; } // treat find_string and replace_string separately (don't apply array_unique) (thanks fabrizio!) foreach (array('find_string', 'replace_string') as $var) { $this->$var = array_merge($this->$var, $newconfig->$var); } } // returns SiteConfig instance if an appropriate one is found, false otherwise // if $exact_host_match is true, we will not look for wildcard config matches // by default if host is 'test.example.org' we will look for and load '.example.org.txt' if it exists public static function build($host, $exact_host_match=false) { $host = strtolower($host); if (substr($host, 0, 4) == 'www.') $host = substr($host, 4); if (!$host || (strlen($host) > 200) || !preg_match(self::HOSTNAME_REGEX, ltrim($host, '.'))) return false; // check for site configuration $try = array($host); // should we look for wildcard matches if (!$exact_host_match) { $split = explode('.', $host); if (count($split) > 1) { array_shift($split); $try[] = '.'.implode('.', $split); } } // look for site config file in primary folder self::debug(". looking for site config for $host in primary folder"); foreach ($try as $h) { if (array_key_exists($h, self::$config_cache)) { self::debug("... site config for $h already loaded in this request"); return self::$config_cache[$h]; } elseif (self::$apc && ($sconfig = apc_fetch("sc.$h"))) { self::debug("... site config for $h in APC cache"); return $sconfig; } elseif (file_exists(self::$config_path."/$h.txt")) { self::debug("... found site config ($h.txt)"); $file_primary = self::$config_path."/$h.txt"; $matched_name = $h; break; } } // if we found site config, process it if (isset($file_primary)) { $config_lines = file($file_primary, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if (!$config_lines || !is_array($config_lines)) return false; $config = self::build_from_array($config_lines); // if APC caching is available and enabled, mark this for cache //$config->cache_in_apc = true; $config->cache_key = $matched_name; // if autodetec on failure is off (on by default) we do not need to look // in secondary folder if (!$config->autodetect_on_failure()) { self::debug('... autodetect on failure is disabled (no other site config files will be loaded)'); return $config; } } // look for site config file in secondary folder if (isset(self::$config_path_fallback)) { self::debug(". looking for site config for $host in secondary folder"); foreach ($try as $h) { if (file_exists(self::$config_path_fallback."/$h.txt")) { self::debug("... found site config in secondary folder ($h.txt)"); $file_secondary = self::$config_path_fallback."/$h.txt"; $matched_name = $h; break; } } if (!isset($file_secondary)) { self::debug("... no site config match in secondary folder"); } } // return false if no config file found if (!isset($file_primary) && !isset($file_secondary)) { self::debug("... no site config match for $host"); return false; } // return primary config if secondary not found if (!isset($file_secondary) && isset($config)) { return $config; } // process secondary config file $config_lines = file($file_secondary, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if (!$config_lines || !is_array($config_lines)) { // failed to process secondary if (isset($config)) { // return primary config return $config; } else { return false; } } // merge with primary and return if (isset($config)) { self::debug('. merging config files'); $config->append(self::build_from_array($config_lines)); return $config; } else { // return just secondary $config = self::build_from_array($config_lines); // if APC caching is available and enabled, mark this for cache //$config->cache_in_apc = true; $config->cache_key = $matched_name; return $config; } } public static function build_from_array(array $lines) { $config = new SiteConfig(); foreach ($lines as $line) { $line = trim($line); // skip comments, empty lines if ($line == '' || $line[0] == '#') continue; // get command $command = explode(':', $line, 2); // if there's no colon ':', skip this line if (count($command) != 2) continue; $val = trim($command[1]); $command = trim($command[0]); if ($command == '' || $val == '') continue; // check for commands where we accept multiple statements if (in_array($command, array('skip_entry', 'title', 'body', 'author', 'date', 'strip', 'strip_id_or_class', 'strip_image_src', 'single_page_link', 'single_page_link_in_feed', 'next_page_link', 'http_header', 'test_url', 'find_string', 'replace_string'))) { array_push($config->$command, $val); // check for single statement commands that evaluate to true or false } elseif (in_array($command, array('images_to_datauri', 'tidy', 'prune', 'autodetect_on_failure'))) { $config->$command = ($val == 'yes' || $val == 'true'); // check for single statement commands stored as strings } elseif (in_array($command, array('parser'))) { $config->$command = $val; // check for replace_string(find): replace } elseif ((substr($command, -1) == ')') && preg_match('!^([a-z0-9_]+)\((.*?)\)$!i', $command, $match)) { if (in_array($match[1], array('replace_string'))) { $command = $match[1]; array_push($config->find_string, $match[2]); array_push($config->$command, $val); } } } return $config; } } ================================================ FILE: libraries/feedwriter/FeedItem.php ================================================ * @link http://www.ajaxray.com/projects/rss */ class FeedItem { private $elements = array(); //Collection of feed elements private $version; /** * Constructor * * @param contant (RSS1/RSS2/ATOM) RSS2 is default. */ function __construct($version = RSS2) { $this->version = $version; } /** * Set element (overwrites existing elements with $elementName) * * @access public * @param srting The tag name of an element * @param srting The content of tag * @param array Attributes(if any) in 'attrName' => 'attrValue' format * @return void */ public function setElement($elementName, $content, $attributes = null) { if (isset($this->elements[$elementName])) { unset($this->elements[$elementName]); } $this->addElement($elementName, $content, $attributes); } /** * Add an element to elements array * * @access public * @param srting The tag name of an element * @param srting The content of tag * @param array Attributes(if any) in 'attrName' => 'attrValue' format * @return void */ public function addElement($elementName, $content, $attributes = null) { $i = 0; if (isset($this->elements[$elementName])) { $i = count($this->elements[$elementName]); } else { $this->elements[$elementName] = array(); } $this->elements[$elementName][$i]['name'] = $elementName; $this->elements[$elementName][$i]['content'] = $content; $this->elements[$elementName][$i]['attributes'] = $attributes; } /** * Set multiple feed elements from an array. * Elements which have attributes cannot be added by this method * * @access public * @param array array of elements in 'tagName' => 'tagContent' format. * @return void */ public function addElementArray($elementArray) { if(! is_array($elementArray)) return; foreach ($elementArray as $elementName => $content) { $this->addElement($elementName, $content); } } /** * Return the collection of elements in this feed item * * @access public * @return array */ public function getElements() { return $this->elements; } // Wrapper functions ------------------------------------------------------ /** * Set the 'dscription' element of feed item * * @access public * @param string The content of 'description' element * @return void */ public function setDescription($description) { $tag = ($this->version == ATOM)? 'summary' : 'description'; $this->setElement($tag, $description); } /** * @desc Set the 'title' element of feed item * @access public * @param string The content of 'title' element * @return void */ public function setTitle($title) { $this->setElement('title', $title); } /** * Set the 'date' element of feed item * * @access public * @param string The content of 'date' element * @return void */ public function setDate($date) { if(! is_numeric($date)) { $date = strtotime($date); } if($this->version == ATOM) { $tag = 'updated'; $value = date(DATE_ATOM, $date); } elseif($this->version == RSS2) { $tag = 'pubDate'; $value = date(DATE_RSS, $date); } else { $tag = 'dc:date'; $value = date("Y-m-d", $date); } $this->setElement($tag, $value); } /** * Set the 'link' element of feed item * * @access public * @param string The content of 'link' element * @return void */ public function setLink($link) { if($this->version == RSS2 || $this->version == RSS1) { $this->setElement('link', $link); } else { $this->setElement('link','',array('href'=>$link)); $this->setElement('id', FeedWriter::uuid($link,'urn:uuid:')); } } /** * Set the 'encloser' element of feed item * For RSS 2.0 only * * @access public * @param string The url attribute of encloser tag * @param string The length attribute of encloser tag * @param string The type attribute of encloser tag * @return void */ public function setEncloser($url, $length, $type) { $attributes = array('url'=>$url, 'length'=>$length, 'type'=>$type); $this->setElement('enclosure','',$attributes); } } // end of class FeedItem ?> ================================================ FILE: libraries/feedwriter/FeedWriter.php ================================================ * @link http://www.ajaxray.com/projects/rss */ class FeedWriter { private $self = null; // self URL - http://feed2.w3.org/docs/warning/MissingAtomSelfLink.html private $hubs = array(); // PubSubHubbub hubs private $channels = array(); // Collection of channel elements private $items = array(); // Collection of items as object of FeedItem class. private $data = array(); // Store some other version wise data private $CDATAEncoding = array(); // The tag names which have to encoded as CDATA private $xsl = null; // stylesheet to render RSS (used by Chrome) private $json = null; // JSON object private $version = null; /** * Constructor * * @param constant the version constant (RSS2 or JSON). */ function __construct($version = RSS2) { $this->version = $version; // Setting default value for assential channel elements $this->channels['title'] = $version . ' Feed'; $this->channels['link'] = 'http://www.ajaxray.com/blog'; //Tag names to encode in CDATA $this->CDATAEncoding = array('description', 'content:encoded', 'content', 'subtitle', 'summary'); } public function setFormat($format) { $this->version = $format; } // Start # public functions --------------------------------------------- /** * Set a channel element * @access public * @param srting name of the channel tag * @param string content of the channel tag * @return void */ public function setChannelElement($elementName, $content) { $this->channels[$elementName] = $content ; } /** * Set multiple channel elements from an array. Array elements * should be 'channelName' => 'channelContent' format. * * @access public * @param array array of channels * @return void */ public function setChannelElementsFromArray($elementArray) { if(! is_array($elementArray)) return; foreach ($elementArray as $elementName => $content) { $this->setChannelElement($elementName, $content); } } /** * Genarate the actual RSS/JSON file * * @access public * @return void */ public function genarateFeed() { if ($this->version == RSS2) { header('Content-type: text/xml; charset=UTF-8'); // this line prevents Chrome 20 from prompting download // used by Google: https://news.google.com/news/feeds?ned=us&topic=b&output=rss header('X-content-type-options: nosniff'); } elseif ($this->version == JSON) { header('Content-type: application/json; charset=UTF-8'); $this->json = new stdClass(); } elseif ($this->version == JSONP) { header('Content-type: application/javascript; charset=UTF-8'); $this->json = new stdClass(); } $this->printHead(); $this->printChannels(); $this->printItems(); $this->printTale(); if ($this->version == JSON || $this->version == JSONP) { echo json_encode($this->json); } } public function &getItems() { return $this->items; } /** * Create a new FeedItem. * * @access public * @return object instance of FeedItem class */ public function createNewItem() { $Item = new FeedItem($this->version); return $Item; } /** * Add a FeedItem to the main class * * @access public * @param object instance of FeedItem class * @return void */ public function addItem($feedItem) { $this->items[] = $feedItem; } // Wrapper functions ------------------------------------------------------------------- /** * Set the 'title' channel element * * @access public * @param srting value of 'title' channel tag * @return void */ public function setTitle($title) { $this->setChannelElement('title', $title); } /** * Add a hub to the channel element * * @access public * @param string URL * @return void */ public function addHub($hub) { $this->hubs[] = $hub; } /** * Set XSL URL * * @access public * @param string URL * @return void */ public function setXsl($xsl) { $this->xsl = $xsl; } /** * Set self URL * * @access public * @param string URL * @return void */ public function setSelf($self) { $this->self = $self; } /** * Set the 'description' channel element * * @access public * @param srting value of 'description' channel tag * @return void */ public function setDescription($desciption) { $tag = ($this->version == ATOM)? 'subtitle' : 'description'; $this->setChannelElement($tag, $desciption); } /** * Set the 'link' channel element * * @access public * @param srting value of 'link' channel tag * @return void */ public function setLink($link) { $this->setChannelElement('link', $link); } /** * Set the 'image' channel element * * @access public * @param srting title of image * @param srting link url of the imahe * @param srting path url of the image * @return void */ public function setImage($title, $link, $url) { $this->setChannelElement('image', array('title'=>$title, 'link'=>$link, 'url'=>$url)); } // End # public functions ---------------------------------------------- // Start # private functions ---------------------------------------------- /** * Prints the xml and rss namespace * * @access private * @return void */ private function printHead() { if ($this->version == RSS2) { $out = ''."\n"; if ($this->xsl) $out .= 'xsl).'"?>' . PHP_EOL; $out .= '' . PHP_EOL; echo $out; } elseif ($this->version == JSON || $this->version == JSONP) { $this->json->rss = array('@attributes' => array('version' => '2.0')); } } /** * Closes the open tags at the end of file * * @access private * @return void */ private function printTale() { if ($this->version == RSS2) { echo '',PHP_EOL,''; } // do nothing for JSON } /** * Creates a single node as xml format * * @access private * @param string name of the tag * @param mixed tag value as string or array of nested tags in 'tagName' => 'tagValue' format * @param array Attributes(if any) in 'attrName' => 'attrValue' format * @return string formatted xml tag */ private function makeNode($tagName, $tagContent, $attributes = null) { if ($this->version == RSS2) { $nodeText = ''; $attrText = ''; if (is_array($attributes)) { foreach ($attributes as $key => $value) { $attrText .= " $key=\"$value\" "; } } $nodeText .= "<{$tagName}{$attrText}>"; if (is_array($tagContent)) { foreach ($tagContent as $key => $value) { $nodeText .= $this->makeNode($key, $value); } } else { //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? $tagContent : htmlentities($tagContent); $nodeText .= htmlspecialchars($tagContent); } //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? "]]>" : ""; $nodeText .= ""; return $nodeText . PHP_EOL; } elseif ($this->version == JSON || $this->version == JSONP) { $tagName = (string)$tagName; $tagName = strtr($tagName, ':', '_'); $node = null; if (!$tagContent && is_array($attributes) && count($attributes)) { $node = array('@attributes' => $this->json_keys($attributes)); } else { if (is_array($tagContent)) { $node = $this->json_keys($tagContent); } else { $node = $tagContent; } } return $node; } return ''; // should not get here } private function json_keys(array $array) { $new = array(); foreach ($array as $key => $val) { if (is_string($key)) $key = strtr($key, ':', '_'); if (is_array($val)) { $new[$key] = $this->json_keys($val); } else { $new[$key] = $val; } } return $new; } /** * @desc Print channels * @access private * @return void */ private function printChannels() { //Start channel tag if ($this->version == RSS2) { echo '' . PHP_EOL; // add hubs foreach ($this->hubs as $hub) { //echo $this->makeNode('link', '', array('rel'=>'hub', 'href'=>$hub, 'xmlns'=>'http://www.w3.org/2005/Atom')); echo '' . PHP_EOL; } // add self if (isset($this->self)) { //echo $this->makeNode('link', '', array('rel'=>'self', 'href'=>$this->self, 'xmlns'=>'http://www.w3.org/2005/Atom')); echo '' . PHP_EOL; } //Print Items of channel foreach ($this->channels as $key => $value) { echo $this->makeNode($key, $value); } } elseif ($this->version == JSON || $this->version == JSONP) { $this->json->rss['channel'] = (object)$this->json_keys($this->channels); } } /** * Prints formatted feed items * * @access private * @return void */ private function printItems() { foreach ($this->items as $item) { $itemElements = $item->getElements(); echo $this->startItem(); if ($this->version == JSON || $this->version == JSONP) { $json_item = array(); } foreach ($itemElements as $thisElement) { foreach ($thisElement as $instance) { if ($this->version == RSS2) { echo $this->makeNode($instance['name'], $instance['content'], $instance['attributes']); } elseif ($this->version == JSON || $this->version == JSONP) { $_json_node = $this->makeNode($instance['name'], $instance['content'], $instance['attributes']); if (count($thisElement) > 1) { $json_item[strtr($instance['name'], ':', '_')][] = $_json_node; } else { $json_item[strtr($instance['name'], ':', '_')] = $_json_node; } } } } echo $this->endItem(); if ($this->version == JSON || $this->version == JSONP) { if (count($this->items) > 1) { $this->json->rss['channel']->item[] = $json_item; } else { $this->json->rss['channel']->item = $json_item; } } } } /** * Make the starting tag of channels * * @access private * @return void */ private function startItem() { if ($this->version == RSS2) { echo '' . PHP_EOL; } // nothing for JSON } /** * Closes feed item tag * * @access private * @return void */ private function endItem() { if ($this->version == RSS2) { echo '' . PHP_EOL; } // nothing for JSON } // End # private functions ---------------------------------------------- } ================================================ FILE: libraries/htmLawed/htmLawed2.php ================================================ 1, 'abbr'=>1, 'acronym'=>1, 'address'=>1, 'applet'=>1, 'area'=>1, 'article'=>1, 'aside'=>1, 'audio'=>1, 'b'=>1, 'bdi'=>1, 'bdo'=>1, 'big'=>1, 'blockquote'=>1, 'br'=>1, 'button'=>1, 'canvas'=>1, 'caption'=>1, 'center'=>1, 'cite'=>1, 'code'=>1, 'col'=>1, 'colgroup'=>1, 'command'=>1, 'data'=>1, 'datalist'=>1, 'dd'=>1, 'del'=>1, 'details'=>1, 'dfn'=>1, 'dir'=>1, 'div'=>1, 'dl'=>1, 'dt'=>1, 'em'=>1, 'embed'=>1, 'fieldset'=>1, 'figcaption'=>1, 'figure'=>1, 'font'=>1, 'footer'=>1, 'form'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'header'=>1, 'hgroup'=>1, 'hr'=>1, 'i'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'ins'=>1, 'isindex'=>1, 'kbd'=>1, 'keygen'=>1, 'label'=>1, 'legend'=>1, 'li'=>1, 'link'=>1, 'main'=>1, 'map'=>1, 'mark'=>1, 'menu'=>1, 'meta'=>1, 'meter'=>1, 'nav'=>1, 'noscript'=>1, 'object'=>1, 'ol'=>1, 'optgroup'=>1, 'option'=>1, 'output'=>1, 'p'=>1, 'param'=>1, 'pre'=>1, 'progress'=>1, 'q'=>1, 'rb'=>1, 'rbc'=>1, 'rp'=>1, 'rt'=>1, 'rtc'=>1, 'ruby'=>1, 's'=>1, 'samp'=>1, 'script'=>1, 'section'=>1, 'select'=>1, 'small'=>1, 'source'=>1, 'span'=>1, 'strike'=>1, 'strong'=>1, 'style'=>1, 'sub'=>1, 'summary'=>1, 'sup'=>1, 'table'=>1, 'tbody'=>1, 'td'=>1, 'textarea'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'time'=>1, 'tr'=>1, 'track'=>1, 'tt'=>1, 'u'=>1, 'ul'=>1, 'var'=>1, 'video'=>1, 'wbr'=>1); // 118 incl. deprecated & some Ruby if(!empty($C['safe'])){ unset($e['applet'], $e['audio'], $e['canvas'], $e['embed'], $e['iframe'], $e['object'], $e['script'], $e['video']); } $x = !empty($C['elements']) ? str_replace(array("\n", "\r", "\t", ' '), '', $C['elements']) : '*'; if($x == '-*'){$e = array();} elseif(strpos($x, '*') === false){$e = array_flip(explode(',', $x));} else{ if(isset($x[1])){ preg_match_all('`(?:^|-|\+)[^\-+]+?(?=-|\+|$)`', $x, $m, PREG_SET_ORDER); for($i=count($m); --$i>=0;){$m[$i] = $m[$i][0];} foreach($m as $v){ if($v[0] == '+'){$e[substr($v, 1)] = 1;} if($v[0] == '-' && isset($e[($v = substr($v, 1))]) && !in_array('+'. $v, $m)){unset($e[$v]);} } } } $C['elements'] =& $e; // config attrs $x = !empty($C['deny_attribute']) ? str_replace(array("\n", "\r", "\t", ' '), '', $C['deny_attribute']) : ''; $x = array_flip((isset($x[0]) && $x[0] == '*') ? explode('-', $x) : explode(',', $x. (!empty($C['safe']) ? ',on*' : ''))); if(isset($x['on*'])){ unset($x['on*']); $x += array('onabort'=>1, 'onblur'=>1, 'oncanplay'=>1, 'oncanplaythrough'=>1, 'onchange'=>1, 'onclick'=>1, 'oncontextmenu'=>1, 'oncuechange'=>1, 'ondblclick'=>1, 'ondrag'=>1, 'ondragend'=>1, 'ondragenter'=>1, 'ondragleave'=>1, 'ondragover'=>1, 'ondragstart'=>1, 'ondrop'=>1, 'ondurationchange'=>1, 'onemptied'=>1, 'onended'=>1, 'onerror'=>1, 'onfocus'=>1, 'oninput'=>1, 'oninvalid'=>1, 'onkeydown'=>1, 'onkeypress'=>1, 'onkeyup'=>1, 'onload'=>1, 'onloadeddata'=>1, 'onloadedmetadata'=>1, 'onloadstart'=>1, 'onmousedown'=>1, 'onmousemove'=>1, 'onmouseout'=>1, 'onmouseover'=>1, 'onmouseup'=>1, 'onmousewheel'=>1, 'onpause'=>1, 'onplay'=>1, 'onplaying'=>1, 'onprogress'=>1, 'onratechange'=>1, 'onreadystatechange'=>1, 'onreset'=>1, 'onscroll'=>1, 'onseeked'=>1, 'onseeking'=>1, 'onselect'=>1, 'onshow'=>1, 'onstalled'=>1, 'onsubmit'=>1, 'onsuspend'=>1, 'ontimeupdate'=>1, 'onvolumechange'=>1, 'onwaiting'=>1); } $C['deny_attribute'] = $x; // config URL $x = (isset($C['schemes'][2]) && strpos($C['schemes'], ':')) ? strtolower($C['schemes']) : 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet, git; *:file, http, https'; $C['schemes'] = array(); foreach(explode(';', str_replace(array(' ', "\t", "\r", "\n"), '', $x)) as $v){ $x = $x2 = null; list($x, $x2) = explode(':', $v, 2); if($x2){$C['schemes'][$x] = array_flip(explode(',', $x2));} } if(!isset($C['schemes']['*'])){$C['schemes']['*'] = array('file'=>1, 'http'=>1, 'https'=>1,);} if(!empty($C['safe']) && empty($C['schemes']['style'])){$C['schemes']['style'] = array('!'=>1);} $C['abs_url'] = isset($C['abs_url']) ? $C['abs_url'] : 0; if(!isset($C['base_url']) or !preg_match('`^[a-zA-Z\d.+\-]+://[^/]+/(.+?/)?$`', $C['base_url'])){ $C['base_url'] = $C['abs_url'] = 0; } // config rest $C['and_mark'] = empty($C['and_mark']) ? 0 : 1; $C['anti_link_spam'] = (isset($C['anti_link_spam']) && is_array($C['anti_link_spam']) && count($C['anti_link_spam']) == 2 && (empty($C['anti_link_spam'][0]) or htmLawed::hl_regex($C['anti_link_spam'][0])) && (empty($C['anti_link_spam'][1]) or htmLawed::hl_regex($C['anti_link_spam'][1]))) ? $C['anti_link_spam'] : 0; $C['anti_mail_spam'] = isset($C['anti_mail_spam']) ? $C['anti_mail_spam'] : 0; $C['balance'] = isset($C['balance']) ? (bool)$C['balance'] : 1; $C['cdata'] = isset($C['cdata']) ? $C['cdata'] : (empty($C['safe']) ? 3 : 0); $C['clean_ms_char'] = empty($C['clean_ms_char']) ? 0 : $C['clean_ms_char']; $C['comment'] = isset($C['comment']) ? $C['comment'] : (empty($C['safe']) ? 3 : 0); $C['css_expression'] = empty($C['css_expression']) ? 0 : 1; $C['direct_list_nest'] = empty($C['direct_list_nest']) ? 0 : 1; $C['hexdec_entity'] = isset($C['hexdec_entity']) ? $C['hexdec_entity'] : 1; $C['hook'] = (!empty($C['hook']) && function_exists($C['hook'])) ? $C['hook'] : 0; $C['hook_tag'] = (!empty($C['hook_tag']) && function_exists($C['hook_tag'])) ? $C['hook_tag'] : 0; $C['keep_bad'] = isset($C['keep_bad']) ? $C['keep_bad'] : 6; $C['lc_std_val'] = isset($C['lc_std_val']) ? (bool)$C['lc_std_val'] : 1; $C['make_tag_strict'] = isset($C['make_tag_strict']) ? $C['make_tag_strict'] : 1; $C['named_entity'] = isset($C['named_entity']) ? (bool)$C['named_entity'] : 1; $C['no_deprecated_attr'] = isset($C['no_deprecated_attr']) ? $C['no_deprecated_attr'] : 1; $C['parent'] = isset($C['parent'][0]) ? strtolower($C['parent']) : 'body'; $C['show_setting'] = !empty($C['show_setting']) ? $C['show_setting'] : 0; $C['style_pass'] = empty($C['style_pass']) ? 0 : 1; $C['tidy'] = empty($C['tidy']) ? 0 : $C['tidy']; $C['unique_ids'] = isset($C['unique_ids']) && (!preg_match('`\W`', $C['unique_ids'])) ? $C['unique_ids'] : 1; $C['xml:lang'] = isset($C['xml:lang']) ? $C['xml:lang'] : 0; if(isset($GLOBALS['C'])){$reC = $GLOBALS['C'];} $GLOBALS['C'] = $C; $S = is_array($S) ? $S : htmLawed::hl_spec($S); if(isset($GLOBALS['S'])){$reS = $GLOBALS['S'];} $GLOBALS['S'] = $S; $t = preg_replace('`[\x00-\x08\x0b-\x0c\x0e-\x1f]`', '', $t); if($C['clean_ms_char']){ $x = array("\x7f"=>'', "\x80"=>'€', "\x81"=>'', "\x83"=>'ƒ', "\x85"=>'…', "\x86"=>'†', "\x87"=>'‡', "\x88"=>'ˆ', "\x89"=>'‰', "\x8a"=>'Š', "\x8b"=>'‹', "\x8c"=>'Œ', "\x8d"=>'', "\x8e"=>'Ž', "\x8f"=>'', "\x90"=>'', "\x95"=>'•', "\x96"=>'–', "\x97"=>'—', "\x98"=>'˜', "\x99"=>'™', "\x9a"=>'š', "\x9b"=>'›', "\x9c"=>'œ', "\x9d"=>'', "\x9e"=>'ž', "\x9f"=>'Ÿ'); $x = $x + ($C['clean_ms_char'] == 1 ? array("\x82"=>'‚', "\x84"=>'„', "\x91"=>'‘', "\x92"=>'’', "\x93"=>'“', "\x94"=>'”') : array("\x82"=>'\'', "\x84"=>'"', "\x91"=>'\'', "\x92"=>'\'', "\x93"=>'"', "\x94"=>'"')); $t = strtr($t, $x); } if($C['cdata'] or $C['comment']){$t = preg_replace_callback('``sm', 'htmLawed::hl_cmtcd', $t);} $t = preg_replace_callback('`&([a-zA-Z][a-zA-Z0-9]{1,30}|#(?:[0-9]{1,8}|[Xx][0-9A-Fa-f]{1,7}));`', 'htmLawed::hl_ent', str_replace('&', '&', $t)); if($C['unique_ids'] && !isset($GLOBALS['hl_Ids'])){$GLOBALS['hl_Ids'] = array();} if($C['hook']){$t = $C['hook']($t, $C, $S);} if($C['show_setting'] && preg_match('`^[a-z][a-z0-9_]*$`i', $C['show_setting'])){ $GLOBALS[$C['show_setting']] = array('config'=>$C, 'spec'=>$S, 'time'=>microtime()); } // main $t = preg_replace_callback('`<(?:(?:\s|$)|(?:[^>]*(?:>|$)))|>`m', 'htmLawed::hl_tag', $t); $t = $C['balance'] ? htmLawed::hl_bal($t, $C['keep_bad'], $C['parent']) : $t; $t = (($C['cdata'] or $C['comment']) && strpos($t, "\x01") !== false) ? str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05"), array('', '', '&', '<', '>'), $t) : $t; $t = $C['tidy'] ? htmLawed::hl_tidy($t, $C['tidy'], $C['parent']) : $t; unset($C, $e); if(isset($reC)){$GLOBALS['C'] = $reC;} if(isset($reS)){$GLOBALS['S'] = $reS;} return $t; // eof } public static function hl_attrval($t, $p){ // check attr val against $S $o = 1; $l = strlen($t); foreach($p as $k=>$v){ switch($k){ case 'maxlen':if($l > $v){$o = 0;} break; case 'minlen': if($l < $v){$o = 0;} break; case 'maxval': if((float)($t) > $v){$o = 0;} break; case 'minval': if((float)($t) < $v){$o = 0;} break; case 'match': if(!preg_match($v, $t)){$o = 0;} break; case 'nomatch': if(preg_match($v, $t)){$o = 0;} break; case 'oneof': $m = 0; foreach(explode('|', $v) as $n){if($t == $n){$m = 1; break;}} $o = $m; break; case 'noneof': $m = 1; foreach(explode('|', $v) as $n){if($t == $n){$m = 0; break;}} $o = $m; break; default: break; } if(!$o){break;} } return ($o ? $t : (isset($p['default']) ? $p['default'] : 0)); // eof } public static function hl_bal($t, $do=1, $in='div'){ // balance tags // by content $cB = array('blockquote'=>1, 'form'=>1, 'map'=>1, 'noscript'=>1); // Block $cE = array('area'=>1, 'br'=>1, 'col'=>1, 'command'=>1, 'embed'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'isindex'=>1, 'keygen'=>1, 'link'=>1, 'meta'=>1, 'param'=>1, 'source'=>1, 'track'=>1, 'wbr'=>1); // Empty $cF = array('a'=>1, 'article'=>1, 'aside'=>1, 'audio'=>1, 'button'=>1, 'canvas'=>1, 'del'=>1, 'details'=>1, 'div'=>1, 'dd'=>1, 'fieldset'=>1, 'figure'=>1, 'footer'=>1, 'header'=>1, 'iframe'=>1, 'ins'=>1, 'li'=>1, 'main'=>1, 'menu'=>1, 'nav'=>1, 'noscript'=>1, 'object'=>1, 'section'=>1, 'style'=>1, 'td'=>1, 'th'=>1, 'video'=>1); // Flow; later context-wise dynamic move of ins & del to $cI $cI = array('abbr'=>1, 'acronym'=>1, 'address'=>1, 'b'=>1, 'bdi'=>1, 'bdo'=>1, 'big'=>1, 'caption'=>1, 'cite'=>1, 'code'=>1, 'data'=>1, 'datalist'=>1, 'dfn'=>1, 'dt'=>1, 'em'=>1, 'figcaption'=>1, 'font'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'hgroup'=>1, 'i'=>1, 'kbd'=>1, 'label'=>1, 'legend'=>1, 'mark'=>1, 'meter'=>1, 'output'=>1, 'p'=>1, 'pre'=>1, 'progress'=>1, 'q'=>1, 'rb'=>1, 'rt'=>1, 's'=>1, 'samp'=>1, 'small'=>1, 'span'=>1, 'strike'=>1, 'strong'=>1, 'sub'=>1, 'summary'=>1, 'sup'=>1, 'time'=>1, 'tt'=>1, 'u'=>1, 'var'=>1); // Inline $cN = array('a'=>array('a'=>1, 'address'=>1, 'button'=>1, 'details'=>1, 'embed'=>1, 'keygen'=>1, 'label'=>1, 'select'=>1, 'textarea'=>1), 'address'=>array('address'=>1, 'article'=>1, 'aside'=>1, 'header'=>1, 'keygen'=>1, 'footer'=>1, 'nav'=>1, 'section'=>1), 'button'=>array('a'=>1, 'address'=>1, 'button'=>1, 'details'=>1, 'embed'=>1, 'fieldset'=>1, 'form'=>1, 'iframe'=>1, 'input'=>1, 'keygen'=>1, 'label'=>1, 'select'=>1, 'textarea'=>1), 'fieldset'=>array('fieldset'=>1), 'footer'=>array('header'=>1, 'footer'=>1), 'form'=>array('form'=>1), 'header'=>array('header'=>1, 'footer'=>1), 'label'=>array('label'=>1), 'main'=>array('main'=>1), 'meter'=>array('meter'=>1), 'noscript'=>array('script'=>1), 'pre'=>array('big'=>1, 'font'=>1, 'img'=>1, 'object'=>1, 'script'=>1, 'small'=>1, 'sub'=>1, 'sup'=>1), 'progress'=>array('progress'=>1), 'rb'=>array('ruby'=>1), 'rt'=>array('ruby'=>1), 'time'=>array('time'=>1), ); // Illegal $cN2 = array_keys($cN); $cR = array('blockquote'=>1, 'dir'=>1, 'dl'=>1, 'form'=>1, 'map'=>1, 'menu'=>1, 'noscript'=>1, 'ol'=>1, 'optgroup'=>1, 'rbc'=>1, 'rtc'=>1, 'ruby'=>1, 'select'=>1, 'table'=>1, 'tbody'=>1, 'tfoot'=>1, 'thead'=>1, 'tr'=>1, 'ul'=>1); $cS = array('colgroup'=>array('col'=>1), 'datalist'=>array('option'=>1), 'dir'=>array('li'=>1), 'dl'=>array('dd'=>1, 'dt'=>1), 'hgroup'=>array('h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1), 'menu'=>array('li'=>1), 'ol'=>array('li'=>1), 'optgroup'=>array('option'=>1), 'option'=>array('#pcdata'=>1), 'rbc'=>array('rb'=>1), 'rp'=>array('#pcdata'=>1), 'rtc'=>array('rt'=>1), 'ruby'=>array('rb'=>1, 'rbc'=>1, 'rp'=>1, 'rt'=>1, 'rtc'=>1), 'select'=>array('optgroup'=>1, 'option'=>1), 'script'=>array('#pcdata'=>1), 'table'=>array('caption'=>1, 'col'=>1, 'colgroup'=>1, 'tfoot'=>1, 'tbody'=>1, 'tr'=>1, 'thead'=>1), 'tbody'=>array('tr'=>1), 'tfoot'=>array('tr'=>1), 'textarea'=>array('#pcdata'=>1), 'thead'=>array('tr'=>1), 'tr'=>array('td'=>1, 'th'=>1), 'ul'=>array('li'=>1)); // Specific - immediate parent-child if($GLOBALS['C']['direct_list_nest']){$cS['ol'] = $cS['ul'] = $cS['menu'] += array('menu'=>1, 'ol'=>1, 'ul'=>1);} $cO = array('address'=>array('p'=>1), 'applet'=>array('param'=>1), 'audio'=>array('source'=>1, 'track'=>1), 'blockquote'=>array('script'=>1), 'details'=>array('summary'=>1), 'fieldset'=>array('legend'=>1, '#pcdata'=>1), 'figure'=>array('figcaption'=>1),'form'=>array('script'=>1), 'map'=>array('area'=>1), 'object'=>array('param'=>1, 'embed'=>1), 'video'=>array('source'=>1, 'track'=>1)); // Other $cT = array('colgroup'=>1, 'dd'=>1, 'dt'=>1, 'li'=>1, 'option'=>1, 'p'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1); // Omitable closing // block/inline type; ins & del both type; #pcdata: text $eB = array('a'=>1, 'address'=>1, 'article'=>1, 'aside'=>1, 'blockquote'=>1, 'center'=>1, 'del'=>1, 'details'=>1, 'dir'=>1, 'dl'=>1, 'div'=>1, 'fieldset'=>1, 'figure'=>1, 'footer'=>1, 'form'=>1, 'ins'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'header'=>1, 'hr'=>1, 'isindex'=>1, 'main'=>1, 'menu'=>1, 'nav'=>1, 'noscript'=>1, 'ol'=>1, 'p'=>1, 'pre'=>1, 'section'=>1, 'style'=>1, 'table'=>1, 'ul'=>1); $eI = array('#pcdata'=>1, 'abbr'=>1, 'acronym'=>1, 'applet'=>1, 'audio'=>1, 'b'=>1, 'bdi'=>1, 'bdo'=>1, 'big'=>1, 'br'=>1, 'button'=>1, 'canvas'=>1, 'cite'=>1, 'code'=>1, 'command'=>1, 'data'=>1, 'datalist'=>1, 'del'=>1, 'dfn'=>1, 'em'=>1, 'embed'=>1, 'figcaption'=>1, 'font'=>1, 'i'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'ins'=>1, 'kbd'=>1, 'label'=>1, 'link'=>1, 'map'=>1, 'mark'=>1, 'meta'=>1, 'meter'=>1, 'object'=>1, 'output'=>1, 'progress'=>1, 'q'=>1, 'ruby'=>1, 's'=>1, 'samp'=>1, 'select'=>1, 'script'=>1, 'small'=>1, 'span'=>1, 'strike'=>1, 'strong'=>1, 'sub'=>1, 'summary'=>1, 'sup'=>1, 'textarea'=>1, 'time'=>1, 'tt'=>1, 'u'=>1, 'var'=>1, 'video'=>1, 'wbr'=>1); $eN = array('a'=>1, 'address'=>1, 'article'=>1, 'aside'=>1, 'big'=>1, 'button'=>1, 'details'=>1, 'embed'=>1, 'fieldset'=>1, 'font'=>1, 'footer'=>1, 'form'=>1, 'header'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'keygen'=>1, 'label'=>1, 'meter'=>1, 'nav'=>1, 'object'=>1, 'progress'=>1, 'ruby'=>1, 'script'=>1, 'select'=>1, 'small'=>1, 'sub'=>1, 'sup'=>1, 'textarea'=>1, 'time'=>1); // Exclude from specific ele; $cN values $eO = array('area'=>1, 'caption'=>1, 'col'=>1, 'colgroup'=>1, 'command'=>1, 'dd'=>1, 'dt'=>1, 'hgroup'=>1, 'keygen'=>1, 'legend'=>1, 'li'=>1, 'optgroup'=>1, 'option'=>1, 'param'=>1, 'rb'=>1, 'rbc'=>1, 'rp'=>1, 'rt'=>1, 'rtc'=>1, 'script'=>1, 'source'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'thead'=>1, 'th'=>1, 'tr'=>1, 'track'=>1); // Missing in $eB & $eI $eF = $eB + $eI; // $in sets allowed child $in = ((isset($eF[$in]) && $in != '#pcdata') or isset($eO[$in])) ? $in : 'div'; if(isset($cE[$in])){ return (!$do ? '' : str_replace(array('<', '>'), array('<', '>'), $t)); } if(isset($cS[$in])){$inOk = $cS[$in];} elseif(isset($cI[$in])){$inOk = $eI; $cI['del'] = 1; $cI['ins'] = 1;} elseif(isset($cF[$in])){$inOk = $eF; unset($cI['del'], $cI['ins']);} elseif(isset($cB[$in])){$inOk = $eB; unset($cI['del'], $cI['ins']);} if(isset($cO[$in])){$inOk = $inOk + $cO[$in];} if(isset($cN[$in])){$inOk = array_diff_assoc($inOk, $cN[$in]);} $t = explode('<', $t); $ok = $q = array(); // $q seq list of open non-empty ele ob_start(); for($i=-1, $ci=count($t); ++$i<$ci;){ // allowed $ok in parent $p if($ql = count($q)){ $p = array_pop($q); $q[] = $p; if(isset($cS[$p])){$ok = $cS[$p];} elseif(isset($cI[$p])){$ok = $eI; $cI['del'] = 1; $cI['ins'] = 1;} elseif(isset($cF[$p])){$ok = $eF; unset($cI['del'], $cI['ins']);} elseif(isset($cB[$p])){$ok = $eB; unset($cI['del'], $cI['ins']);} if(isset($cO[$p])){$ok = $ok + $cO[$p];} if(isset($cN[$p])){$ok = array_diff_assoc($ok, $cN[$p]);} }else{$ok = $inOk; unset($cI['del'], $cI['ins']);} // bad tags, & ele content if(isset($e) && ($do == 1 or (isset($ok['#pcdata']) && ($do == 3 or $do == 5)))){ echo '<', $s, $e, $a, '>'; } if(isset($x[0])){ if(strlen(trim($x)) && (($ql && isset($cB[$p])) or (isset($cB[$in]) && !$ql))){ echo '
    ', $x, '
    '; } 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);} } // get markup if(!preg_match('`^(/?)([a-zA-Z1-6]+)([^>]*)>(.*)`sm', $t[$i], $r)){$x = $t[$i]; continue;} $s = null; $e = null; $a = null; $x = null; list($all, $s, $e, $a, $x) = $r; // close tag if($s){ if(isset($cE[$e]) or !in_array($e, $q)){continue;} // Empty/unopen if($p == $e){array_pop($q); echo ''; unset($e); continue;} // Last open $add = ''; // Nesting - close open tags that need to be for($j=-1, $cj=count($q); ++$j<$cj;){ if(($d = array_pop($q)) == $e){break;} else{$add .= "";} } echo $add, ''; unset($e); continue; } // open tag // $cB ele needs $eB ele as child if(isset($cB[$e]) && strlen(trim($x))){ $t[$i] = "{$e}{$a}>"; array_splice($t, $i+1, 0, 'div>'. $x); unset($e, $x); ++$ci; --$i; continue; } if((($ql && isset($cB[$p])) or (isset($cB[$in]) && !$ql)) && !isset($eB[$e]) && !isset($ok[$e])){ array_splice($t, $i, 0, 'div>'); unset($e, $x); ++$ci; --$i; continue; } // if no open ele, $in = parent; mostly immediate parent-child relation should hold if(!$ql or !isset($eN[$e]) or !array_intersect($q, $cN2)){ if(!isset($ok[$e])){ if($ql && isset($cT[$p])){echo ''; unset($e, $x); --$i;} continue; } if(!isset($cE[$e])){$q[] = $e;} echo '<', $e, $a, '>'; unset($e); continue; } // specific parent-child if(isset($cS[$p][$e])){ if(!isset($cE[$e])){$q[] = $e;} echo '<', $e, $a, '>'; unset($e); continue; } // nesting $add = ''; $q2 = array(); for($k=-1, $kc=count($q); ++$k<$kc;){ $d = $q[$k]; $ok2 = array(); if(isset($cS[$d])){$q2[] = $d; continue;} $ok2 = isset($cI[$d]) ? $eI : $eF; if(isset($cO[$d])){$ok2 = $ok2 + $cO[$d];} if(isset($cN[$d])){$ok2 = array_diff_assoc($ok2, $cN[$d]);} if(!isset($ok2[$e])){ if(!$k && !isset($inOk[$e])){continue 2;} $add = ""; for(;++$k<$kc;){$add = "{$add}";} break; } else{$q2[] = $d;} } $q = $q2; if(!isset($cE[$e])){$q[] = $e;} echo $add, '<', $e, $a, '>'; unset($e); continue; } // end if($ql = count($q)){ $p = array_pop($q); $q[] = $p; if(isset($cS[$p])){$ok = $cS[$p];} elseif(isset($cI[$p])){$ok = $eI; $cI['del'] = 1; $cI['ins'] = 1;} elseif(isset($cF[$p])){$ok = $eF; unset($cI['del'], $cI['ins']);} elseif(isset($cB[$p])){$ok = $eB; unset($cI['del'], $cI['ins']);} if(isset($cO[$p])){$ok = $ok + $cO[$p];} if(isset($cN[$p])){$ok = array_diff_assoc($ok, $cN[$p]);} }else{$ok = $inOk; unset($cI['del'], $cI['ins']);} if(isset($e) && ($do == 1 or (isset($ok['#pcdata']) && ($do == 3 or $do == 5)))){ echo '<', $s, $e, $a, '>'; } if(isset($x[0])){ if(strlen(trim($x)) && (($ql && isset($cB[$p])) or (isset($cB[$in]) && !$ql))){ echo '
    ', $x, '
    '; } 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 '';} $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('acronym'=>1, 'applet'=>1, 'big'=>1, 'center'=>1, 'dir'=>1, 'font'=>1, 'isindex'=>1, 's'=>1, 'strike'=>1, 'tt'=>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, 'command'=>1, 'embed'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'isindex'=>1, 'keygen'=>1, 'link'=>1, 'meta'=>1, 'param'=>1, 'source'=>1, 'track'=>1, 'wbr'=>1); // Empty ele if(!empty($m[1])){ return (!isset($eE[$e]) ? (empty($C['hook_tag']) ? "" : $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), '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), 'async'=>array('script'=>1), 'autocomplete'=>array('input'=>1), 'autofocus'=>array('button'=>1, 'input'=>1, 'keygen'=>1, 'select'=>1, 'textarea'=>1), 'autoplay'=>array('audio'=>1, 'video'=>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), 'challenge'=>array('keygen'=>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('command'=>1, '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), 'content'=>array('meta'=>1), 'controls'=>array('audio'=>1, 'video'=>1), 'coords'=>array('area'=>1, 'a'=>1), 'crossorigin'=>array('img'=>1), 'data'=>array('object'=>1), 'datetime'=>array('del'=>1, 'ins'=>1, 'time'=>1), 'declare'=>array('object'=>1), 'default'=>array('track'=>1), 'defer'=>array('script'=>1), 'dirname'=>array('input'=>1, 'textarea'=>1), 'disabled'=>array('button'=>1, 'command'=>1, 'fieldset'=>1, 'input'=>1, 'keygen'=>1, 'optgroup'=>1, 'option'=>1, 'select'=>1, 'textarea'=>1), 'download'=>array('a'=>1), 'enctype'=>array('form'=>1), 'face'=>array('font'=>1), 'flashvars'=>array('embed'=>1), 'for'=>array('label'=>1, 'output'=>1), 'form'=>array('button'=>1, 'fieldset'=>1, 'input'=>1, 'keygen'=>1, 'label'=>1, 'object'=>1, 'output'=>1, 'select'=>1, 'textarea'=>1), 'formaction'=>array('button'=>1, 'input'=>1), 'formenctype'=>array('button'=>1, 'input'=>1), 'formmethod'=>array('button'=>1, 'input'=>1), 'formnovalidate'=>array('button'=>1, 'input'=>1), 'formtarget'=>array('button'=>1, 'input'=>1), 'frame'=>array('table'=>1), 'frameborder'=>array('iframe'=>1), 'headers'=>array('td'=>1, 'th'=>1), 'height'=>array('canvas'=>1, 'embed'=>1, 'iframe'=>1, 'input'=>1, 'td'=>1, 'th'=>1, 'img'=>1, 'object'=>1, 'applet'=>1, 'video'=>1), 'high'=>array('meter'=>1), 'href'=>array('a'=>1, 'area'=>1, 'link'=>1), 'hreflang'=>array('a'=>1, 'area'=>1, 'link'=>1), 'hspace'=>array('applet'=>1, 'embed'=>1, 'img'=>1, 'object'=>1), 'icon'=>array('command'=>1), 'ismap'=>array('img'=>1, 'input'=>1), 'keyparams'=>array('keygen'=>1), 'keytype'=>array('keygen'=>1), 'kind'=>array('track'=>1), 'label'=>array('command'=>1, 'menu'=>1, 'option'=>1, 'optgroup'=>1, 'track'=>1), 'language'=>array('script'=>1), 'list'=>array('input'=>1), 'longdesc'=>array('img'=>1, 'iframe'=>1), 'loop'=>array('audio'=>1, 'video'=>1), 'low'=>array('meter'=>1), 'marginheight'=>array('iframe'=>1), 'marginwidth'=>array('iframe'=>1), 'max'=>array('input'=>1, 'meter'=>1, 'progress'=>1), 'maxlength'=>array('input'=>1, 'textarea'=>1), 'media'=>array('a'=>1, 'area'=>1, 'link'=>1, 'source'=>1, 'style'=>1), 'mediagroup'=>array('audio'=>1, 'video'=>1), 'method'=>array('form'=>1), 'min'=>array('input'=>1, 'meter'=>1), 'model'=>array('embed'=>1), 'multiple'=>array('input'=>1, 'select'=>1), 'muted'=>array('audio'=>1, 'video'=>1), 'name'=>array('button'=>1, 'embed'=>1, 'fieldset'=>1, 'keygen'=>1, 'output'=>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), 'novalidate'=>array('form'=>1), 'nowrap'=>array('td'=>1, 'th'=>1), 'object'=>array('applet'=>1), 'open'=>array('details'=>1), 'optimum'=>array('meter'=>1), 'pattern'=>array('input'=>1), 'ping'=>array('a'=>1), 'placeholder'=>array('input'=>1, 'textarea'=>1), 'pluginspage'=>array('embed'=>1), 'pluginurl'=>array('embed'=>1), 'poster'=>array('video'=>1), 'pqg'=>array('keygen'=>1), 'preload'=>array('audio'=>1, 'video'=>1), 'prompt'=>array('isindex'=>1), 'radiogroup'=>array('command'=>1), 'readonly'=>array('textarea'=>1, 'input'=>1), 'rel'=>array('a'=>1, 'area'=>1, 'link'=>1), 'required'=>array('input'=>1, 'select'=>1, 'textarea'=>1), 'rev'=>array('a'=>1), 'reversed'=>array('ol'=>1), 'rows'=>array('textarea'=>1), 'rowspan'=>array('td'=>1, 'th'=>1), 'rules'=>array('table'=>1), 'sandbox'=>array('iframe'=>1), 'scope'=>array('td'=>1, 'th'=>1), 'scoped'=>array('style'=>1), 'scrolling'=>array('iframe'=>1), 'seamless'=>array('iframe'=>1), 'selected'=>array('option'=>1), 'shape'=>array('area'=>1, 'a'=>1), 'size'=>array('hr'=>1, 'font'=>1, 'input'=>1, 'select'=>1), 'sizes'=>array('link'=>1), 'span'=>array('col'=>1, 'colgroup'=>1), 'src'=>array('audio'=>1, 'embed'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'source'=>1, 'script'=>1, 'track'=>1, 'video'=>1), 'srcdoc'=>array('iframe'=>1), 'srclang'=>array('track'=>1), 'standby'=>array('object'=>1), 'start'=>array('ol'=>1), 'step'=>array('input'=>1), 'summary'=>array('table'=>1), 'target'=>array('a'=>1, 'area'=>1, 'form'=>1), 'type'=>array('a'=>1, 'area'=>1, 'command'=>1, 'embed'=>1, 'link'=>1, 'menu'=>1, 'object'=>1, 'param'=>1, 'script'=>1, 'source'=>1, 'style'=>1, 'input'=>1, 'li'=>1, 'ol'=>1, 'ul'=>1, 'button'=>1), 'typemustmatch'=>array('object'=>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('data'=>1, 'input'=>1, 'meter'=>1, 'option'=>1, 'param'=>1, 'progress'=>1, 'button'=>1, 'li'=>1), 'valuetype'=>array('param'=>1), 'vspace'=>array('applet'=>1, 'embed'=>1, 'img'=>1, 'object'=>1), 'width'=>array('canvas'=>1, 'embed'=>1, 'hr'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'object'=>1, 'table'=>1, 'td'=>1, 'th'=>1, 'applet'=>1, 'col'=>1, 'colgroup'=>1, 'pre'=>1, 'video'=>1), 'wmode'=>array('embed'=>1), 'wrap'=>array('textarea'=>1)); // Ele-specific static $aNE = array('checkbox'=>1, 'checked'=>1, 'command'=>1, 'compact'=>1, 'declare'=>1, 'defer'=>1, 'default'=>1, 'disabled'=>1, 'ismap'=>1, 'itemscope'=>1, 'multiple'=>1, 'nohref'=>1, 'noresize'=>1, 'noshade'=>1, 'nowrap'=>1, 'open'=>1, 'radio'=>1, 'readonly'=>1, 'required'=>1, 'reversed'=>1, 'selected'=>1); // Empty static $aNP = array('action'=>1, 'cite'=>1, 'classid'=>1, 'codebase'=>1, 'data'=>1, 'href'=>1, 'itemtype'=>1, 'longdesc'=>1, 'model'=>1, 'pluginspage'=>1, 'pluginurl'=>1, 'usemap'=>1); // Need scheme check; excludes style, on* & src static $aNU = array('accesskey'=>1, 'aria-activedescendant'=>1, 'aria-atomic'=>1, 'aria-autocomplete'=>1, 'aria-busy'=>1, 'aria-checked'=>1, 'aria-controls'=>1, 'aria-describedby'=>1, 'aria-disabled'=>1, 'aria-dropeffect'=>1, 'aria-expanded'=>1, 'aria-flowto'=>1, 'aria-grabbed'=>1, 'aria-haspopup'=>1, 'aria-hidden'=>1, 'aria-invalid'=>1, 'aria-label'=>1, 'aria-labelledby'=>1, 'aria-level'=>1, 'aria-live'=>1, 'aria-multiline'=>1, 'aria-multiselectable'=>1, 'aria-orientation'=>1, 'aria-owns'=>1, 'aria-posinset'=>1, 'aria-pressed'=>1, 'aria-readonly'=>1, 'aria-relevant'=>1, 'aria-required'=>1, 'aria-selected'=>1, 'aria-setsize'=>1, 'aria-sort'=>1, 'aria-valuemax'=>1, 'aria-valuemin'=>1, 'aria-valuenow'=>1, 'aria-valuetext'=>1, 'class'=>1, 'contenteditable'=>1, 'contextmenu'=>1, 'dir'=>1, 'draggable'=>1, 'dropzone'=>1, 'hidden'=>1, 'id'=>1, 'inert'=>1, 'itemid'=>1, 'itemprop'=>1, 'itemref'=>1, 'itemscope'=>1, 'itemtype'=>1, 'lang'=>1, 'spellcheck'=>1, 'style'=>1, 'tabindex'=>1, 'title'=>1, 'translate'=>1, 'onabort'=>1, 'onblur'=>1, 'oncanplay'=>1, 'oncanplaythrough'=>1, 'onchange'=>1, 'onclick'=>1, 'oncontextmenu'=>1, 'oncuechange'=>1, 'ondblclick'=>1, 'ondrag'=>1, 'ondragend'=>1, 'ondragenter'=>1, 'ondragleave'=>1, 'ondragover'=>1, 'ondragstart'=>1, 'ondrop'=>1, 'ondurationchange'=>1, 'onemptied'=>1, 'onended'=>1, 'onerror'=>1, 'onfocus'=>1, 'oninput'=>1, 'oninvalid'=>1, 'onkeydown'=>1, 'onkeypress'=>1, 'onkeyup'=>1, 'onload'=>1, 'onloadeddata'=>1, 'onloadedmetadata'=>1, 'onloadstart'=>1, 'onmousedown'=>1, 'onmousemove'=>1, 'onmouseout'=>1, 'onmouseover'=>1, 'onmouseup'=>1, 'onmousewheel'=>1, 'onpause'=>1, 'onplay'=>1, 'onplaying'=>1, 'onprogress'=>1, 'onratechange'=>1, 'onreadystatechange'=>1, 'onreset'=>1, 'onscroll'=>1, 'onseeked'=>1, 'onseeking'=>1, 'onselect'=>1, 'onshow'=>1, 'onstalled'=>1, 'onsubmit'=>1, 'onsuspend'=>1, 'ontimeupdate'=>1, 'onvolumechange'=>1, 'onwaiting'=>1, 'role'=>1, 'translate'=>1, 'xmlns'=>1, 'xml:base'=>1, 'xml:lang'=>1, 'xml:space'=>1); // Univ if($C['lc_std_val']){ // predef attr vals for $eAL & $aNE ele static $aNL = array('all'=>1, 'auto'=>1, 'baseline'=>1, 'bottom'=>1, 'button'=>1, 'captions'=>1, 'center'=>1, 'chapters'=>1, 'char'=>1, 'checkbox'=>1, 'circle'=>1, 'col'=>1, 'colgroup'=>1, 'color'=>1, 'cols'=>1, 'data'=>1, 'date'=>1, 'datetime'=>1, 'datetime-local'=>1, 'default'=>1, 'descriptions'=>1, 'email'=>1, 'file'=>1, 'get'=>1, 'groups'=>1, 'hidden'=>1, 'image'=>1, 'justify'=>1, 'left'=>1, 'ltr'=>1, 'metadata'=>1, 'middle'=>1, 'month'=>1, 'none'=>1, 'number'=>1, 'object'=>1, 'password'=>1, 'poly'=>1, 'post'=>1, 'preserve'=>1, 'radio'=>1, 'range'=>1, 'rect'=>1, 'ref'=>1, 'reset'=>1, 'right'=>1, 'row'=>1, 'rowgroup'=>1, 'rows'=>1, 'rtl'=>1, 'search'=>1, 'submit'=>1, 'subtitles'=>1, 'tel'=>1, 'text'=>1, 'top'=>1, 'url'=>1, 'week'=>1); static $eAL = array('a'=>1, 'area'=>1, 'bdo'=>1, 'button'=>1, 'col'=>1, 'fieldset'=>1, 'form'=>1, 'img'=>1, 'input'=>1, 'object'=>1, 'ol'=>1, 'optgroup'=>1, 'option'=>1, 'param'=>1, 'script'=>1, 'select'=>1, 'table'=>1, 'td'=>1, 'textarea'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1, 'track'=>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('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), '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, '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][^\s=]+`', $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]) or preg_match('`data-((?!xml)[^:]+$)`', $k)) && !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'], ENT_COMPAT, 'UTF-8'), $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'), 'command'=>array('label'=>''), 'form'=>array('action'=>''), 'img'=>array('src'=>'', 'alt'=>'image'), 'map'=>array('name'=>''), 'optgroup'=>array('label'=>''), 'param'=>array('name'=>''), 'style'=>array('scoped'=>''), '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('`\W`', $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 == '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('`\s`', ($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 == 'big'){$e = 'span'; return 'font-size: larger;';} if($e == 's' or $e == 'strike'){$e = 'span'; return 'text-decoration: line-through;';} if($e == 'tt'){$e = 'code'; return '';} if($e == 'center'){$e = 'div'; return 'text-align: center;';} 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($e == 'acronym'){$e = 'abbr'; return '';} if($e == 'dir'){$e = 'ul'; return '';} 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(' ]*(?)\s+`', '`\s+`', '`(<\w[^>]*(?) `'), array(' $1', ' ', '$1'), preg_replace_callback(array('`(<(!\[CDATA\[))(.+?)(\]\]>)`sm', '`(<(!--))(.+?)(-->)`sm', '`(<(pre|script|textarea)[^>]*?>)(.+?)()`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, 'command'=>1, 'input'=>1, 'option'=>1, 'param'=>1, 'track'=>1); $c = array('audio'=>1, 'canvas'=>1, 'caption'=>1, 'dd'=>1, 'dt'=>1, 'figcaption'=>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, 'style'=>1, 'summary'=>1, 'td'=>1, 'textarea'=>1, 'th'=>1, 'video'=>1); $d = array('address'=>1, 'article'=>1, 'aside'=>1, 'blockquote'=>1, 'center'=>1, 'colgroup'=>1, 'datalist'=>1, 'details'=>1, 'dir'=>1, 'div'=>1, 'dl'=>1, 'fieldset'=>1, 'figure'=>1, 'footer'=>1, 'form'=>1, 'header'=>1, 'hgroup'=>1, 'hr'=>1, 'iframe'=>1, 'main'=>1, 'map'=>1, 'menu'=>1, 'nav'=>1, 'noscript'=>1, 'ol'=>1, 'optgroup'=>1, 'rbc'=>1, 'rtc'=>1, 'ruby'=>1, 'script'=>1, 'section'=>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.2.beta.2'; // 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 } ================================================ FILE: libraries/html5/Data.php ================================================ 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; } } ================================================ FILE: libraries/html5/InputStream.php ================================================ 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--; } } } ================================================ FILE: libraries/html5/Parser.php ================================================ 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(); } } ================================================ FILE: libraries/html5/Tokenizer.php ================================================ 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' => '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' => '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; } } } ================================================ FILE: libraries/html5/TreeBuilder.php ================================================ 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_XHTML = 'http://www.w3.org/1999/xhtml'; 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']);
            $namespaceURI = strpos($token['name'], ':') ? self::NS_XHTML : self::NS_HTML;
            $el = $this->dom->createElementNS($namespaceURI, $token['name']);
    
            if (!empty($token['attr'])) {
                foreach($token['attr'] as $attr) {
                    // mike@macgirvin.com 2011-11-17, check attribute name for
                    // validity (ignoring extenders and combiners) as illegal chars in names
                    // causes everything to abort
    
                    $valid = preg_match('/^[a-zA-Z\_\:]([\-a-zA-Z0-9\_\:\.]+$)/',$attr['name']);
                    if($attr['name'] && (!$el->hasAttribute($attr['name'])) && ($valid)) {
                        $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;
                }
            }
        }
    }
    
    
    ================================================
    FILE: libraries/html5/named-character-references.ser
    ================================================
    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;}}}}}}
    
    ================================================
    FILE: libraries/humble-http-agent/CookieJar.php
    ================================================
    
     *
     * This class should be used to handle cookies (storing cookies from HTTP response messages, and
     * sending out cookies in HTTP request messages). This has been adapted for FiveFilters.org 
     * from the original version used in HTTP Navigator. See http://www.keyvan.net/code/http-navigator/
     * 
     * This class is mainly based on Cookies.pm  from the libwww-perl collection .
     * Unlike Cookies.pm, this class only supports the Netscape cookie spec, not RFC 2965.
     * 
     * @version 0.5
     * @date 2011-03-15
     * @see http://php.net/HttpRequestPool
     * @author Keyvan Minoukadeh
     * @copyright 2011 Keyvan Minoukadeh
     * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3
     */
    
    class CookieJar
    {
        /**
        * Cookies - array containing all cookies.
        *
        * 
        * Cookies are stored like this:
        *   [domain][path][name] = array
        * where array is:
        *   0 => value, 1 => secure, 2 => expires
        * 
    * @var array * @access private */ public $cookies = array(); public $debug = false; /** * Constructor */ function __construct() { } protected function debug($msg, $file=null, $line=null) { if ($this->debug) { $mem = round(memory_get_usage()/1024, 2); $memPeak = round(memory_get_peak_usage()/1024, 2); echo '* ',$msg; if (isset($file, $line)) echo " ($file line $line)"; echo ' - mem used: ',$mem," (peak: $memPeak)\n"; ob_flush(); flush(); } } /** * Get matching cookies * * Only use this method if you cannot use add_cookie_header(), for example, if you want to use * this cookie jar class without using the request class. * * @param array $param associative array containing 'domain', 'path', 'secure' keys * @return string * @see add_cookie_header() */ public function getMatchingCookies($url) { if (($parts = @parse_url($url)) && isset($parts['scheme'], $parts['host'], $parts['path'])) { $param['domain'] = $parts['host']; $param['path'] = $parts['path']; $param['secure'] = (strtolower($parts['scheme']) == 'https'); unset($parts); } else { return false; } // RFC 2965 notes: // If multiple cookies satisfy the criteria above, they are ordered in // the Cookie header such that those with more specific Path attributes // precede those with less specific. Ordering with respect to other // attributes (e.g., Domain) is unspecified. $domain = $param['domain']; if (strpos($domain, '.') === false) $domain .= '.local'; $request_path = $param['path']; if ($request_path == '') $request_path = '/'; $request_secure = $param['secure']; $now = time(); $matched_cookies = array(); // domain - find matching domains $this->debug('Finding matching domains for '.$domain, __FILE__, __LINE__); while (strpos($domain, '.') !== false) { if (isset($this->cookies[$domain])) { $this->debug(' domain match found: '.$domain); $cookies =& $this->cookies[$domain]; } else { $domain = $this->_reduce_domain($domain); continue; } // paths - find matching paths starting from most specific $this->debug(' - Finding matching paths for '.$request_path); $paths = array_keys($cookies); usort($paths, array($this, '_cmp_length')); foreach ($paths as $path) { // continue to next cookie if request path does not path-match cookie path if (!$this->_path_match($request_path, $path)) continue; // loop through cookie names $this->debug(' path match found: '.$path); foreach ($cookies[$path] as $name => $values) { // if this cookie is secure but request isn't, continue to next cookie if ($values[1] && !$request_secure) continue; // if cookie is not a session cookie and has expired, continue to next cookie if (is_int($values[2]) && ($values[2] < $now)) continue; // cookie matches request $this->debug(' cookie match: '.$name.'='.$values[0]); $matched_cookies[] = $name.'='.$values[0]; } } $domain = $this->_reduce_domain($domain); } // return cookies return implode('; ', $matched_cookies); } /** * Parse Set-Cookie values. * * Only use this method if you cannot use extract_cookies(), for example, if you want to use * this cookie jar class without using the response class. * * @param array $set_cookies array holding 1 or more "Set-Cookie" header values * @param array $param associative array containing 'host', 'path' keys * @return void * @see extract_cookies() */ public function storeCookies($url, $set_cookies) { if (count($set_cookies) == 0) return; $param = @parse_url($url); if (!is_array($param) || !isset($param['host'])) return; $request_host = $param['host']; if (strpos($request_host, '.') === false) $request_host .= '.local'; $request_path = @$param['path']; if ($request_path == '') $request_path = '/'; // // loop through set-cookie headers // foreach ($set_cookies as $set_cookie) { $this->debug('Parsing: '.$set_cookie); // temporary cookie store (before adding to jar) $tmp_cookie = array(); $param = explode(';', $set_cookie); // loop through params for ($x=0; $x$key, 'value'=>$val); continue; } $key = strtolower($key); if (in_array($key, array('expires', 'path', 'domain', 'secure'))) { $tmp_cookie[$key] = $val; } } // // set cookie // // check domain if (isset($tmp_cookie['domain']) && ($tmp_cookie['domain'] != $request_host) && ($tmp_cookie['domain'] != ".$request_host")) { $domain = $tmp_cookie['domain']; if ((strpos($domain, '.') === false) && ($domain != 'local')) { $this->debug(' - domain "'.$domain.'" has no dot and is not a local domain'); continue; } if (preg_match('/\.[0-9]+$/', $domain)) { $this->debug(' - domain "'.$domain.'" appears to be an ip address'); continue; } if (substr($domain, 0, 1) != '.') $domain = ".$domain"; if (!$this->_domain_match($request_host, $domain)) { $this->debug(' - request host "'.$request_host.'" does not domain-match "'.$domain.'"'); continue; } } else { // if domain is not specified in the set-cookie header, domain will default to // the request host $domain = $request_host; } // check path if (isset($tmp_cookie['path']) && ($tmp_cookie['path'] != '')) { $path = urldecode($tmp_cookie['path']); if (!$this->_path_match($request_path, $path)) { $this->debug(' - request path "'.$request_path.'" does not path-match "'.$path.'"'); continue; } } else { $path = $request_path; $path = substr($path, 0, strrpos($path, '/')); if ($path == '') $path = '/'; } // check if secure $secure = (isset($tmp_cookie['secure'])) ? true : false; // check expiry if (isset($tmp_cookie['expires'])) { if (($expires = strtotime($tmp_cookie['expires'])) < 0) { $expires = null; } } else { $expires = null; } // set cookie $this->set_cookie($domain, $path, $tmp_cookie['name'], $tmp_cookie['value'], $secure, $expires); } } // return array of set-cookie values extracted from HTTP response headers (string $h) public function extractCookies($h) { $x = 0; $lines = 0; $headers = array(); $last_match = false; $h = explode("\n", $h); foreach ($h as $line) { $line = rtrim($line); $lines++; $trimmed_line = trim($line); if (isset($line_last)) { // check if we have \r\n\r\n (indicating the end of headers) // some servers will not use CRLF (\r\n), so we make CR (\r) optional. // if (preg_match('/\015?\012\015?\012/', $line_last.$line)) { // break; // } // As an alternative, we can check if the current trimmed line is empty if ($trimmed_line == '') { break; } // check for continuation line... // RFC 2616 Section 2.2 "Basic Rules": // HTTP/1.1 header field values can be folded onto multiple lines if the // continuation line begins with a space or horizontal tab. All linear // white space, including folding, has the same semantics as SP. A // recipient MAY replace any linear white space with a single SP before // interpreting the field value or forwarding the message downstream. if ($last_match && preg_match('/^\s+(.*)/', $line, $match)) { // append to previous header value $headers[$x-1] .= ' '.rtrim($match[1]); continue; } } $line_last = $line; // split header name and value if (preg_match('/^Set-Cookie\s*:\s*(.*)/i', $line, $match)) { $headers[$x++] = rtrim($match[1]); $last_match = true; } else { $last_match = false; } } return $headers; } /** * Set Cookie * @param string $domain * @param string $path * @param string $name cookie name * @param string $value cookie value * @param bool $secure * @param int $expires expiry time (null if session cookie, <= 0 will delete cookie) * @return void */ function set_cookie($domain, $path, $name, $value, $secure=false, $expires=null) { if ($domain == '') return; if ($path == '') return; if ($name == '') return; // check if cookie needs to go if (isset($expires) && ($expires <= 0)) { if (isset($this->cookies[$domain][$path][$name])) unset($this->cookies[$domain][$path][$name]); return; } if ($value == '') return; $this->cookies[$domain][$path][$name] = array($value, $secure, $expires); return; } /** * Clear cookies - [domain [,path [,name]]] - call method with no arguments to clear all cookies. * @param string $domain * @param string $path * @param string $name * @return void */ function clear($domain=null, $path=null, $name=null) { if (!isset($domain)) { $this->cookies = array(); } elseif (!isset($path)) { if (isset($this->cookies[$domain])) unset($this->cookies[$domain]); } elseif (!isset($name)) { if (isset($this->cookies[$domain][$path])) unset($this->cookies[$domain][$path]); } elseif (isset($name)) { if (isset($this->cookies[$domain][$path][$name])) unset($this->cookies[$domain][$path][$name]); } } /** * Compare string length - used for sorting * @access private * @return int */ function _cmp_length($a, $b) { $la = strlen($a); $lb = strlen($b); if ($la == $lb) return 0; return ($la > $lb) ? -1 : 1; } /** * Reduce domain * @param string $domain * @return string * @access private */ function _reduce_domain($domain) { if ($domain == '') return ''; if (substr($domain, 0, 1) == '.') return substr($domain, 1); return substr($domain, strpos($domain, '.')); } /** * Path match - check if path1 path-matches path2 * * From RFC 2965: * For two strings that represent paths, P1 and P2, P1 path-matches P2 * if P2 is a prefix of P1 (including the case where P1 and P2 string- * compare equal). Thus, the string /tec/waldo path-matches /tec. * @param string $path1 * @param string $path2 * @return bool * @access private */ function _path_match($path1, $path2) { return (substr($path1, 0, strlen($path2)) == $path2); } /** * Domain match - check if domain1 domain-matches domain2 * * A few extracts from RFC 2965: * - A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com * would be rejected, because H is y.x and contains a dot. * * - A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com * would be accepted. * * - A Set-Cookie2 with Domain=.com or Domain=.com., will always be * rejected, because there is no embedded dot. * * - A Set-Cookie2 from request-host example for Domain=.local will * be accepted, because the effective host name for the request- * host is example.local, and example.local domain-matches .local. * * I'm ignoring the first point for now (must check to see how other browsers handle * this rule for Set-Cookie headers) * * @param string $domain1 * @param string $domain2 * @return bool * @access private */ function _domain_match($domain1, $domain2) { $domain1 = strtolower($domain1); $domain2 = strtolower($domain2); while (strpos($domain1, '.') !== false) { if ($domain1 == $domain2) return true; $domain1 = $this->_reduce_domain($domain1); continue; } return false; } } ?> ================================================ FILE: libraries/humble-http-agent/HumbleHttpAgent.php ================================================ userAgentDefault = self::UA_BROWSER; $this->referer = self::REF_GOOGLE; // set the request method if (in_array($method, array(1,2,4))) { $this->method = $method; } else { if (class_exists('HttpRequestPool')) { $this->method = self::METHOD_REQUEST_POOL; } elseif (function_exists('curl_multi_init')) { $this->method = self::METHOD_CURL_MULTI; } else { $this->method = self::METHOD_FILE_GET_CONTENTS; } } if ($this->method == self::METHOD_CURL_MULTI) { require_once(dirname(__FILE__).'/RollingCurl.php'); } // create cookie jar $this->cookieJar = new CookieJar(); // set request options (redirect must be 0) $this->requestOptions = array( 'timeout' => 15, 'connecttimeout' => 15, 'dns_cache_timeout' => 300, 'redirect' => 0 // we handle redirects manually so we can rewrite the new hashbang URLs that are creeping up over the web // TODO: test onprogress? ); if (is_array($requestOptions)) { $this->requestOptions = array_merge($this->requestOptions, $requestOptions); } $this->httpContext = array( 'http' => array( 'ignore_errors' => true, 'timeout' => $this->requestOptions['timeout'], 'max_redirects' => $this->requestOptions['redirect'], 'header' => "Accept: */*\r\n" ) ); } public function initCache($dir, $level = 0, $cleanup = 100, $life = 3600) { $this->debug('HTTP cache TTL is set to '.$life.' sec.'); $frontendOptions = array( 'lifetime' => $life, // cache lifetime 'automatic_serialization' => false, 'write_control' => false, 'automatic_cleaning_factor' => $cleanup, 'ignore_user_abort' => false ); $backendOptions = array( 'cache_dir' => $dir.'/urls/', // directory where to put the cache files 'file_locking' => false, 'read_control' => true, 'read_control_type' => 'strlen', 'hashed_directory_level' => $level, 'hashed_directory_perm' => 0777, 'cache_file_perm' => 0664, 'file_name_prefix' => 'ff' ); // getting a Zend_Cache_Core object $this->cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions); } private function isCached($url) { if (!$this->cache || !$url) return false; return ($this->cache->test('request'.md5($url)) !== false); } private function setCached($url, $data) { if (!$this->cache || !$url || !$data) return; $this->cache->save(serialize($data), 'request'.md5($url)); } private function getCached($url) { if (!$this->cache || !$url) return null; return unserialize($this->cache->load('request'.md5($url))); } protected function debug($msg) { if ($this->debug) { $mem = round(memory_get_usage()/1024, 2); $memPeak = round(memory_get_peak_usage()/1024, 2); echo '* ',$msg,"
    "; if ($this->debugVerbose) echo ' - mem used: ',$mem," (peak: $memPeak)"; echo "\n"; ob_flush(); flush(); } } protected function getUserAgent($url, $asArray=false) { $host = @parse_url($url, PHP_URL_HOST); if (strtolower(substr($host, 0, 4)) == 'www.') { $host = substr($host, 4); } if ($host) { $try = array($host); $split = explode('.', $host); if (count($split) > 1) { array_shift($split); $try[] = '.'.implode('.', $split); } foreach ($try as $h) { if (isset($this->userAgentMap[$h])) { $ua = $this->userAgentMap[$h]; break; } } } if (!isset($ua)) $ua = $this->userAgentDefault; if ($asArray) { return array('User-Agent' => $ua); } else { return 'User-Agent: '.$ua; } } public function rewriteHashbangFragment($url) { // return $url if there's no '#!' if (strpos($url, '#!') === false) return $url; // split $url and rewrite // TODO: is SimplePie_IRI included? $iri = new SimplePie_IRI($url); $fragment = substr($iri->fragment, 1); // strip '!' $iri->fragment = null; if (isset($iri->query)) { parse_str($iri->query, $query); } else { $query = array(); } $query['_escaped_fragment_'] = (string)$fragment; $iri->query = str_replace('%2F', '/', http_build_query($query)); // needed for some sites return $iri->get_iri(); } public function getRedirectURLfromHTML($url, $html) { $redirect_url = $this->getMetaRefreshURL($url, $html); if (!$redirect_url) { $redirect_url = $this->getUglyURL($url, $html); } return $redirect_url; } public function getMetaRefreshURL($url, $html) { if ($html == '') return false; // if (!preg_match('!]+)["\']*>!i', $html, $match)) { return false; } $redirect_url = $match[1]; if (preg_match('!^https?://!i', $redirect_url)) { // already absolute $this->debug('Meta refresh redirect found (http-equiv="refresh"), new URL: '.$redirect_url); return $redirect_url; } // absolutize redirect URL $base = new SimplePie_IRI($url); // remove '//' in URL path (causes URLs not to resolve properly) if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path); if ($absolute = SimplePie_IRI::absolutize($base, $redirect_url)) { $this->debug('Meta refresh redirect found (http-equiv="refresh"), new URL: '.$absolute); return $absolute; } return false; } 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 $ugly_url = $iri->get_iri(); $this->debug('AJAX trigger (meta name="fragment" content="!") found, new URL: '.$ugly_url); return $ugly_url; } public function removeFragment($url) { $pos = strpos($url, '#'); if ($pos === false) { return $url; } else { return substr($url, 0, $pos); } } public function rewriteUrls($url) { foreach ($this->rewriteUrls as $find => $action) { if (strpos($url, $find) !== false) { if (is_array($action)) { return strtr($url, $action); } } } return $url; } public function enableDebug($bool=true) { $this->debug = (bool)$bool; } public function minimiseMemoryUse($bool = true) { $this->minimiseMemoryUse = $bool; } public function setMaxParallelRequests($max) { $this->maxParallelRequests = $max; } public function validateUrl($url) { $url = filter_var($url, FILTER_SANITIZE_URL); $test = filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); // deal with bug http://bugs.php.net/51192 (present in PHP 5.2.13 and PHP 5.3.2) if ($test === false) { $test = filter_var(strtr($url, '-', '_'), FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); } if ($test !== false && $test !== null && preg_match('!^https?://!', $url)) { return $url; } else { return false; } } public function fetchAll(array $urls) { $this->fetchAllOnce($urls, $isRedirect=false); $redirects = 0; while (!empty($this->redirectQueue) && ++$redirects <= $this->maxRedirects) { $this->debug("Following redirects #$redirects..."); $this->fetchAllOnce($this->redirectQueue, $isRedirect=true); } } // fetch all URLs without following redirects public function fetchAllOnce(array $urls, $isRedirect=false) { if (!$isRedirect) $urls = array_unique($urls); if (empty($urls)) return; ////////////////////////////////////////////////////// // parallel (HttpRequestPool) if ($this->method == self::METHOD_REQUEST_POOL) { $this->debug('Starting parallel fetch (HttpRequestPool)'); try { while (count($urls) > 0) { $this->debug('Processing set of '.min($this->maxParallelRequests, count($urls))); $subset = array_splice($urls, 0, $this->maxParallelRequests); $pool = new HttpRequestPool(); foreach ($subset as $orig => $url) { if (!$isRedirect) $orig = $url; unset($this->redirectQueue[$orig]); $this->debug("...$url"); if (!$isRedirect && isset($this->requests[$url])) { $this->debug("......in memory"); } elseif ($this->isCached($url)) { $this->debug("......is cached"); if (!$this->minimiseMemoryUse) { $this->requests[$url] = $this->getCached($url); } } else { $this->debug("......adding to pool"); $req_url = $this->rewriteUrls($url); $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; $req_url = $this->removeFragment($req_url); if (!empty($this->headerOnlyTypes) && !isset($this->requests[$orig]['wrongGuess']) && $this->possibleUnsupportedType($req_url)) { $_meth = HttpRequest::METH_HEAD; } else { $_meth = HttpRequest::METH_GET; unset($this->requests[$orig]['wrongGuess']); } $httpRequest = new HttpRequest($req_url, $_meth, $this->requestOptions); // send cookies, if we have any if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { $this->debug("......sending cookies: $cookies"); $httpRequest->addHeaders(array('Cookie' => $cookies)); } //$httpRequest->addHeaders(array('User-Agent' => $this->userAgent)); $httpRequest->addHeaders($this->getUserAgent($req_url, true)); // add referer for picky sites $httpRequest->addheaders(array('Referer' => $this->referer)); $this->requests[$orig] = array('headers'=>null, 'body'=>null, 'httpRequest'=>$httpRequest); $this->requests[$orig]['original_url'] = $orig; $pool->attach($httpRequest); } } // did we get anything into the pool? if (count($pool) > 0) { $this->debug('Sending request...'); try { $pool->send(); } catch (HttpRequestPoolException $e) { // do nothing } $this->debug('Received responses'); foreach($subset as $orig => $url) { if (!$isRedirect) $orig = $url; $request = $this->requests[$orig]['httpRequest']; //$this->requests[$orig]['headers'] = $this->headersToString($request->getResponseHeader()); // getResponseHeader() doesn't return status line, so, for consistency... $this->requests[$orig]['headers'] = substr($request->getRawResponseMessage(), 0, $request->getResponseInfo('header_size')); // check content type // TODO: use getResponseHeader('content-type') or getResponseInfo() if ($this->headerOnlyType($this->requests[$orig]['headers'])) { $this->requests[$orig]['body'] = ''; $_header_only_type = true; $this->debug('Header only type returned'); } else { $this->requests[$orig]['body'] = $request->getResponseBody(); $_header_only_type = false; } $this->requests[$orig]['effective_url'] = $request->getResponseInfo('effective_url'); $this->requests[$orig]['status_code'] = $status_code = $request->getResponseCode(); // is redirect? if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && $request->getResponseHeader('location')) { $redirectURL = $request->getResponseHeader('location'); if (!preg_match('!^https?://!i', $redirectURL)) { $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); } if ($this->validateURL($redirectURL)) { $this->debug('Redirect detected. Valid URL: '.$redirectURL); // store any cookies $cookies = $request->getResponseHeader('set-cookie'); if ($cookies && !is_array($cookies)) $cookies = array($cookies); if ($cookies) $this->cookieJar->storeCookies($url, $cookies); $this->redirectQueue[$orig] = $redirectURL; } else { $this->debug('Redirect detected. Invalid URL: '.$redirectURL); } } elseif (!$_header_only_type && $request->getMethod() === HttpRequest::METH_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']; } 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->getRedirectURLfromHTML($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); if ($redirectURL) { $this->redirectQueue[$orig] = $redirectURL; } } } //die($url.' -multi- '.$request->getResponseInfo('effective_url')); if (isset($this->cache) && isset($this->requests[$orig]['body'])) { $this->setCached($orig, $this->requests[$orig]); } $pool->detach($request); unset($this->requests[$orig]['httpRequest'], $request); /* if ($this->minimiseMemoryUse) { if ($this->cache($url)) { unset($this->requests[$url]); } } */ } } } } catch (HttpException $e) { $this->debug($e); return false; } } ////////////////////////////////////////////////////////// // parallel (curl_multi_*) elseif ($this->method == self::METHOD_CURL_MULTI) { $this->debug('Starting parallel fetch (curl_multi_*)'); while (count($urls) > 0) { $this->debug('Processing set of '.min($this->maxParallelRequests, count($urls))); $subset = array_splice($urls, 0, $this->maxParallelRequests); $pool = new RollingCurl(array($this, 'handleCurlResponse')); $pool->window_size = count($subset); foreach ($subset as $orig => $url) { if (!$isRedirect) $orig = $url; unset($this->redirectQueue[$orig]); $this->debug("...$url"); if (!$isRedirect && isset($this->requests[$url])) { $this->debug("......in memory"); } elseif ($this->isCached($url)) { $this->debug("......is cached"); if (!$this->minimiseMemoryUse) { $this->requests[$url] = $this->getCached($url); } } else { $this->debug("......adding to pool"); $req_url = $this->rewriteUrls($url); $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; $req_url = $this->removeFragment($req_url); if (!empty($this->headerOnlyTypes) && !isset($this->requests[$orig]['wrongGuess']) && $this->possibleUnsupportedType($req_url)) { $_meth = 'HEAD'; } else { $_meth = 'GET'; unset($this->requests[$orig]['wrongGuess']); } $headers = array(); //$headers[] = 'User-Agent: '.$this->userAgent; $headers[] = $this->getUserAgent($req_url); // add referer for picky sites $headers[] = 'Referer: '.$this->referer; // send cookies, if we have any if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { $this->debug("......sending cookies: $cookies"); $headers[] = 'Cookie: '.$cookies; } $httpRequest = new RollingCurlRequest($req_url, $_meth, null, $headers, array( CURLOPT_CONNECTTIMEOUT => $this->requestOptions['timeout'], CURLOPT_TIMEOUT => $this->requestOptions['timeout'] )); $httpRequest->set_original_url($orig); $this->requests[$orig] = array('headers'=>null, 'body'=>null, 'httpRequest'=>$httpRequest); $this->requests[$orig]['original_url'] = $orig; // TODO: is this needed anymore? $pool->add($httpRequest); } } // did we get anything into the pool? if (count($pool) > 0) { $this->debug('Sending request...'); $pool->execute(); // this will call handleCurlResponse() and populate $this->requests[$orig] $this->debug('Received responses'); foreach($subset as $orig => $url) { if (!$isRedirect) $orig = $url; // $this->requests[$orig]['headers'] // $this->requests[$orig]['body'] // $this->requests[$orig]['effective_url'] // check content type if ($this->headerOnlyType($this->requests[$orig]['headers'])) { $this->requests[$orig]['body'] = ''; $_header_only_type = true; $this->debug('Header only type returned'); } else { $_header_only_type = false; } $status_code = $this->requests[$orig]['status_code']; if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && isset($this->requests[$orig]['location'])) { $redirectURL = $this->requests[$orig]['location']; if (!preg_match('!^https?://!i', $redirectURL)) { $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); } if ($this->validateURL($redirectURL)) { $this->debug('Redirect detected. Valid URL: '.$redirectURL); // store any cookies $cookies = $this->cookieJar->extractCookies($this->requests[$orig]['headers']); if (!empty($cookies)) $this->cookieJar->storeCookies($url, $cookies); $this->redirectQueue[$orig] = $redirectURL; } else { $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']; } 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->getRedirectURLfromHTML($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); if ($redirectURL) { $this->redirectQueue[$orig] = $redirectURL; } } } if (isset($this->cache) && isset($this->requests[$orig]['body'])) { $this->setCached($orig, $this->requests[$orig]); } // die($url.' -multi- '.$request->getResponseInfo('effective_url')); unset($this->requests[$orig]['httpRequest'], $this->requests[$orig]['method']); } } } } ////////////////////////////////////////////////////// // sequential (file_get_contents) else { $this->debug('Starting sequential fetch (file_get_contents)'); $this->debug('Processing set of '.count($urls)); foreach ($urls as $orig => $url) { if (!$isRedirect) $orig = $url; unset($this->redirectQueue[$orig]); $this->debug("...$url"); if (!$isRedirect && isset($this->requests[$url])) { $this->debug("......in memory"); /* } elseif ($this->isCached($url)) { $this->debug("......is cached"); if (!$this->minimiseMemoryUse) { $this->requests[$url] = $this->getCached($url); } */ } else { $this->debug("Sending request for $url"); $this->requests[$orig]['original_url'] = $orig; $req_url = $this->rewriteUrls($url); $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; $req_url = $this->removeFragment($req_url); // send cookies, if we have any $httpContext = $this->httpContext; $httpContext['http']['header'] .= $this->getUserAgent($req_url)."\r\n"; // add referer for picky sites $httpContext['http']['header'] .= 'Referer: '.$this->referer."\r\n"; if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { $this->debug("......sending cookies: $cookies"); $httpContext['http']['header'] .= 'Cookie: '.$cookies."\r\n"; } if (false !== ($html = @file_get_contents($req_url, false, stream_context_create($httpContext)))) { $this->debug('Received response'); // get status code if (!isset($http_response_header[0]) || !preg_match('!^HTTP/\d+\.\d+\s+(\d+)!', trim($http_response_header[0]), $match)) { $this->debug('Error: no status code found'); // TODO: handle error - no status code } else { $this->requests[$orig]['headers'] = $this->headersToString($http_response_header, false); // check content type if ($this->headerOnlyType($this->requests[$orig]['headers'])) { $this->requests[$orig]['body'] = ''; } else { $this->requests[$orig]['body'] = $html; } $this->requests[$orig]['effective_url'] = $req_url; $this->requests[$orig]['status_code'] = $status_code = (int)$match[1]; unset($match); // handle redirect 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'])) { $redirectURL = $this->requests[$orig]['location']; if (!preg_match('!^https?://!i', $redirectURL)) { $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); } if ($this->validateURL($redirectURL)) { $this->debug('Redirect detected. Valid URL: '.$redirectURL); // store any cookies $cookies = $this->cookieJar->extractCookies($this->requests[$orig]['headers']); if (!empty($cookies)) $this->cookieJar->storeCookies($url, $cookies); $this->redirectQueue[$orig] = $redirectURL; } 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->getRedirectURLfromHTML($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); if ($redirectURL) { $this->redirectQueue[$orig] = $redirectURL; } } } } } else { $this->debug('Error retrieving URL'); //print_r($req_url); //print_r($http_response_header); //print_r($html); // TODO: handle error - failed to retrieve URL } } } } } public function handleCurlResponse($response, $info, $request) { $orig = $request->url_original; $this->requests[$orig]['headers'] = substr($response, 0, $info['header_size']); $this->requests[$orig]['body'] = substr($response, $info['header_size']); $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:(.*?)$/mi', $this->requests[$orig]['headers'], $match)) { $this->requests[$orig]['location'] = trim($match[1]); } } protected function headersToString(array $headers, $associative=true) { if (!$associative) { return implode("\n", $headers); } else { $str = ''; foreach ($headers as $key => $val) { if (is_array($val)) { foreach ($val as $v) $str .= "$key: $v\n"; } else { $str .= "$key: $val\n"; } } return rtrim($str); } } public function get($url, $remove=false, $gzdecode=true) { $url = "$url"; if (isset($this->requests[$url]) && isset($this->requests[$url]['body'])) { $this->debug("URL already fetched - in memory ($url, effective: {$this->requests[$url]['effective_url']})"); $response = $this->requests[$url]; /* } elseif ($this->isCached($url)) { $this->debug("URL already fetched - in disk cache ($url)"); $response = $this->getCached($url); $this->requests[$url] = $response; */ } else { $this->debug("Fetching URL ($url)"); $this->fetchAll(array($url)); if (isset($this->requests[$url]) && isset($this->requests[$url]['body'])) { $response = $this->requests[$url]; } else { $this->debug("Request failed"); $response = false; } } /* if ($this->minimiseMemoryUse && $response) { $this->cache($url); unset($this->requests[$url]); } */ if ($remove && $response) unset($this->requests[$url]); if ($gzdecode && stripos($response['headers'], 'Content-Encoding: gzip')) { if ($html = gzdecode($response['body'])) { $response['body'] = $html; } } return $response; } public function parallelSupport() { return class_exists('HttpRequestPool') || function_exists('curl_multi_init'); } private function headerOnlyType($headers) { if (preg_match('!^Content-Type:\s*(([a-z-]+)/([^;\r\n ]+))!im', $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 (in_array($mime, $this->headerOnlyTypes)) return true; } } return false; } private function possibleUnsupportedType($url) { $path = @parse_url($url, PHP_URL_PATH); if ($path && strpos($path, '.') !== false) { $ext = strtolower(trim(pathinfo($path, PATHINFO_EXTENSION))); return in_array($ext, $this->headerOnlyClues); } return false; } } // gzdecode from http://www.php.net/manual/en/function.gzdecode.php#82930 if (!function_exists('gzdecode')) { function gzdecode($data,&$filename='',&$error='',$maxlength=null) { $len = strlen($data); if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) { $error = "Not in GZIP format."; return null; // Not GZIP format (See RFC 1952) } $method = ord(substr($data,2,1)); // Compression method $flags = ord(substr($data,3,1)); // Flags if ($flags & 31 != $flags) { $error = "Reserved bits not allowed."; return null; } // NOTE: $mtime may be negative (PHP integer limitations) $mtime = unpack("V", substr($data,4,4)); $mtime = $mtime[1]; $xfl = substr($data,8,1); $os = substr($data,8,1); $headerlen = 10; $extralen = 0; $extra = ""; if ($flags & 4) { // 2-byte length prefixed EXTRA data in header if ($len - $headerlen - 2 < 8) { return false; // invalid } $extralen = unpack("v",substr($data,8,2)); $extralen = $extralen[1]; if ($len - $headerlen - 2 - $extralen < 8) { return false; // invalid } $extra = substr($data,10,$extralen); $headerlen += 2 + $extralen; } $filenamelen = 0; $filename = ""; if ($flags & 8) { // C-style string if ($len - $headerlen - 1 < 8) { return false; // invalid } $filenamelen = strpos(substr($data,$headerlen),chr(0)); if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) { return false; // invalid } $filename = substr($data,$headerlen,$filenamelen); $headerlen += $filenamelen + 1; } $commentlen = 0; $comment = ""; if ($flags & 16) { // C-style string COMMENT data in header if ($len - $headerlen - 1 < 8) { return false; // invalid } $commentlen = strpos(substr($data,$headerlen),chr(0)); if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) { return false; // Invalid header format } $comment = substr($data,$headerlen,$commentlen); $headerlen += $commentlen + 1; } $headercrc = ""; if ($flags & 2) { // 2-bytes (lowest order) of CRC32 on header present if ($len - $headerlen - 2 < 8) { return false; // invalid } $calccrc = crc32(substr($data,0,$headerlen)) & 0xffff; $headercrc = unpack("v", substr($data,$headerlen,2)); $headercrc = $headercrc[1]; if ($headercrc != $calccrc) { $error = "Header checksum failed."; return false; // Bad header CRC } $headerlen += 2; } // GZIP FOOTER $datacrc = unpack("V",substr($data,-8,4)); $datacrc = sprintf('%u',$datacrc[1] & 0xFFFFFFFF); $isize = unpack("V",substr($data,-4)); $isize = $isize[1]; // decompression: $bodylen = $len-$headerlen-8; if ($bodylen < 1) { // IMPLEMENTATION BUG! return null; } $body = substr($data,$headerlen,$bodylen); $data = ""; if ($bodylen > 0) { switch ($method) { case 8: // Currently the only supported compression method: $data = gzinflate($body,$maxlength); break; default: $error = "Unknown compression method."; return false; } } // zero-byte body content is allowed // Verifiy CRC32 $crc = sprintf("%u",crc32($data)); $crcOK = $crc == $datacrc; $lenOK = $isize == strlen($data); if (!$lenOK || !$crcOK) { $error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.'); return false; } return $data; } } ?> ================================================ FILE: libraries/humble-http-agent/RollingCurl.php ================================================ url = $url; $this->url_original = $url; $this->method = $method; $this->post_data = $post_data; $this->headers = $headers; $this->options = $options; } /** * @param string $url * @return void */ public function set_original_url($url) { $this->url_original = $url; } /** * @return void */ public function __destruct() { unset($this->url, $this->url_original, $this->method, $this->post_data, $this->headers, $this->options); } } /** * RollingCurl custom exception */ class RollingCurlException extends Exception { } /** * Class that holds a rolling queue of curl requests. * * @throws RollingCurlException */ class RollingCurl implements Countable { /** * @var int * * Window size is the max number of simultaneous connections allowed. * * REMEMBER TO RESPECT THE SERVERS: * Sending too many requests at one time can easily be perceived * as a DOS attack. Increase this window_size if you are making requests * to multiple servers or have permission from the receving server admins. */ private $window_size = 5; /** * @var float * * Timeout is the timeout used for curl_multi_select. */ private $timeout = 10; /** * @var string|array * * Callback function to be applied to each result. */ private $callback; /** * @var array * * Set your base options that you want to be used with EVERY request. */ protected $options = array( CURLOPT_SSL_VERIFYPEER => 0, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 30, CURLOPT_TIMEOUT => 30 ); /** * @var array */ private $headers = array(); /** * @var Request[] * * The request queue */ private $requests = array(); /** * @var RequestMap[] * * Maps handles to request indexes */ private $requestMap = array(); /** * @param $callback * Callback function to be applied to each result. * * Can be specified as 'my_callback_function' * or array($object, 'my_callback_method'). * * Function should take three parameters: $response, $info, $request. * $response is response body, $info is additional curl info. * $request is the original request * * @return void */ function __construct($callback = null) { $this->callback = $callback; } /** * @param string $name * @return mixed */ public function __get($name) { return (isset($this->{$name})) ? $this->{$name} : null; } /** * @param string $name * @param mixed $value * @return bool */ public function __set($name, $value) { // append the base options & headers if ($name == "options" || $name == "headers") { $this->{$name} = $value + $this->{$name}; } else { $this->{$name} = $value; } return true; } /** * Count number of requests added (Countable interface) * * @return int */ public function count() { return count($this->requests); } /** * Add a request to the request queue * * @param Request $request * @return bool */ public function add($request) { $this->requests[] = $request; return true; } /** * Create new Request and add it to the request queue * * @param string $url * @param string $method * @param $post_data * @param $headers * @param $options * @return bool */ public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) { $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options); return true; } /** * Perform GET request * * @param string $url * @param $headers * @param $options * @return bool */ public function get($url, $headers = null, $options = null) { return $this->request($url, "GET", null, $headers, $options); } /** * Perform POST request * * @param string $url * @param $post_data * @param $headers * @param $options * @return bool */ public function post($url, $post_data = null, $headers = null, $options = null) { return $this->request($url, "POST", $post_data, $headers, $options); } /** * Execute processing * * @param int $window_size Max number of simultaneous connections * @return string|bool */ public function execute($window_size = null) { // rolling curl window must always be greater than 1 if (sizeof($this->requests) == 1) { return $this->single_curl(); } else { // start the rolling curl. window_size is the max number of simultaneous connections return $this->rolling_curl($window_size); } } /** * Performs a single curl request * * @access private * @return string */ private function single_curl() { $ch = curl_init(); $request = array_shift($this->requests); $options = $this->get_options($request); curl_setopt_array($ch, $options); $output = curl_exec($ch); $info = curl_getinfo($ch); // it's not neccesary to set a callback for one-off requests if ($this->callback) { $callback = $this->callback; if (is_callable($this->callback)) { call_user_func($callback, $output, $info, $request); } } else return $output; return true; } /** * Performs multiple curl requests * * @access private * @throws RollingCurlException * @param int $window_size Max number of simultaneous connections * @return bool */ private function rolling_curl($window_size = null) { if ($window_size) $this->window_size = $window_size; // make sure the rolling window isn't greater than the # of urls if (sizeof($this->requests) < $this->window_size) $this->window_size = sizeof($this->requests); if ($this->window_size < 2) { throw new RollingCurlException("Window size must be greater than 1"); } $master = curl_multi_init(); // start the first batch of requests for ($i = 0; $i < $this->window_size; $i++) { $ch = curl_init(); $options = $this->get_options($this->requests[$i]); curl_setopt_array($ch, $options); curl_multi_add_handle($master, $ch); // Add to our request Maps $key = (string) $ch; $this->requestMap[$key] = $i; } do { while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ; if ($execrun != CURLM_OK) break; // a request was just completed -- find out which one while ($done = curl_multi_info_read($master)) { // get the info and content returned on the request $info = curl_getinfo($done['handle']); $output = curl_multi_getcontent($done['handle']); // send the return values to the callback function. $callback = $this->callback; if (is_callable($callback)) { $key = (string) $done['handle']; $request = $this->requests[$this->requestMap[$key]]; unset($this->requestMap[$key]); call_user_func($callback, $output, $info, $request); } // start a new request (it's important to do this before removing the old one) if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) { $ch = curl_init(); $options = $this->get_options($this->requests[$i]); curl_setopt_array($ch, $options); curl_multi_add_handle($master, $ch); // Add to our request Maps $key = (string) $ch; $this->requestMap[$key] = $i; $i++; } // remove the curl handle that just completed curl_multi_remove_handle($master, $done['handle']); } // Block for data in / output; error handling is done by curl_multi_exec //if ($running) curl_multi_select($master, $this->timeout); // removing timeout as it causes problems on Windows with PHP 5.3.5 and Curl 7.20.0 if ($running) curl_multi_select($master); } while ($running); curl_multi_close($master); return true; } /** * Helper function to set up a new request by setting the appropriate options * * @access private * @param Request $request * @return array */ private function get_options($request) { // options for this entire curl object $options = $this->__get('options'); // We're managing reirects in PHP - allows us to intervene and rewrite/block URLs // before the next request goes out. $options[CURLOPT_FOLLOWLOCATION] = 0; $options[CURLOPT_MAXREDIRS] = 0; //if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) { // $options[CURLOPT_FOLLOWLOCATION] = 1; // $options[CURLOPT_MAXREDIRS] = 5; //} $headers = $this->__get('headers'); // append custom headers for this specific request if ($request->headers) { $headers = $headers + $request->headers; } // append custom options for this specific request if ($request->options) { $options = $request->options + $options; } // set the request URL $options[CURLOPT_URL] = $request->url; if ($headers) { $options[CURLOPT_HTTPHEADER] = $headers; } // return response headers $options[CURLOPT_HEADER] = 1; // send HEAD request? if ($request->method == 'HEAD') { $options[CURLOPT_NOBODY] = 1; } return $options; } /** * @return void */ public function __destruct() { unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests); } } ================================================ FILE: libraries/humble-http-agent/SimplePie_HumbleHttpAgent.php ================================================ encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); } $this->url = $url; $this->useragent = $useragent; if (preg_match('/^http(s)?:\/\//i', $url)) { if (!is_array($headers)) { $headers = array(); } $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL; $headers2 = array(); foreach ($headers as $key => $value) { $headers2[] = "$key: $value"; } //TODO: allow for HTTP headers // curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); $response = self::$agent->get($url); if ($response === false || !isset($response['status_code'])) { $this->error = 'failed to fetch URL'; $this->success = false; } else { // The extra lines at the end are there to satisfy SimplePie's HTTP parser. // The class expects a full HTTP message, whereas we're giving it only // headers - the new lines indicate the start of the body. $parser = new SimplePie_HTTP_Parser($response['headers']."\r\n\r\n"); if ($parser->parse()) { $this->headers = $parser->headers; //$this->body = $parser->body; $this->body = $response['body']; $this->status_code = $parser->status_code; } } } else { $this->error = 'invalid URL'; $this->success = false; } } } ?> ================================================ FILE: libraries/language-detect/LanguageDetect/Exception.php ================================================ * @copyright 2011 Christian Weiske * @license http://www.debian.org/misc/bsd.license BSD * @version SVN: $Id$ * @link http://pear.php.net/package/Text_LanguageDetect/ */ /** * Provides a mapping between the languages from lang.dat and the * ISO 639-1 and ISO-639-2 codes. * * Note that this class contains only languages that exist in lang.dat. * * @category Text * @package Text_LanguageDetect * @author Christian Weiske * @copyright 2011 Christian Weiske * @license http://www.debian.org/misc/bsd.license BSD * @link http://www.loc.gov/standards/iso639-2/php/code_list.php */ class Text_LanguageDetect_ISO639 { /** * Maps all language names from the language database to the * ISO 639-1 2-letter language code. * * NULL indicates that there is no 2-letter code. * * @var array */ public static $nameToCode2 = array( 'albanian' => 'sq', 'arabic' => 'ar', 'azeri' => 'az', 'bengali' => 'bn', 'bulgarian' => 'bg', 'cebuano' => null, 'croatian' => 'hr', 'czech' => 'cs', 'danish' => 'da', 'dutch' => 'nl', 'english' => 'en', 'estonian' => 'et', 'farsi' => 'fa', 'finnish' => 'fi', 'french' => 'fr', 'german' => 'de', 'hausa' => 'ha', 'hawaiian' => null, 'hindi' => 'hi', 'hungarian' => 'hu', 'icelandic' => 'is', 'indonesian' => 'id', 'italian' => 'it', 'kazakh' => 'kk', 'kyrgyz' => 'ky', 'latin' => 'la', 'latvian' => 'lv', 'lithuanian' => 'lt', 'macedonian' => 'mk', 'mongolian' => 'mn', 'nepali' => 'ne', 'norwegian' => 'no', 'pashto' => 'ps', 'pidgin' => null, 'polish' => 'pl', 'portuguese' => 'pt', 'romanian' => 'ro', 'russian' => 'ru', 'serbian' => 'sr', 'slovak' => 'sk', 'slovene' => 'sl', 'somali' => 'so', 'spanish' => 'es', 'swahili' => 'sw', 'swedish' => 'sv', 'tagalog' => 'tl', 'turkish' => 'tr', 'ukrainian' => 'uk', 'urdu' => 'ur', 'uzbek' => 'uz', 'vietnamese' => 'vi', 'welsh' => 'cy', ); /** * Maps all language names from the language database to the * ISO 639-2 3-letter language code. * * @var array */ public static $nameToCode3 = array( 'albanian' => 'sqi', 'arabic' => 'ara', 'azeri' => 'aze', 'bengali' => 'ben', 'bulgarian' => 'bul', 'cebuano' => 'ceb', 'croatian' => 'hrv', 'czech' => 'ces', 'danish' => 'dan', 'dutch' => 'nld', 'english' => 'eng', 'estonian' => 'est', 'farsi' => 'fas', 'finnish' => 'fin', 'french' => 'fra', 'german' => 'deu', 'hausa' => 'hau', 'hawaiian' => 'haw', 'hindi' => 'hin', 'hungarian' => 'hun', 'icelandic' => 'isl', 'indonesian' => 'ind', 'italian' => 'ita', 'kazakh' => 'kaz', 'kyrgyz' => 'kir', 'latin' => 'lat', 'latvian' => 'lav', 'lithuanian' => 'lit', 'macedonian' => 'mkd', 'mongolian' => 'mon', 'nepali' => 'nep', 'norwegian' => 'nor', 'pashto' => 'pus', 'pidgin' => 'crp', 'polish' => 'pol', 'portuguese' => 'por', 'romanian' => 'ron', 'russian' => 'rus', 'serbian' => 'srp', 'slovak' => 'slk', 'slovene' => 'slv', 'somali' => 'som', 'spanish' => 'spa', 'swahili' => 'swa', 'swedish' => 'swe', 'tagalog' => 'tgl', 'turkish' => 'tur', 'ukrainian' => 'ukr', 'urdu' => 'urd', 'uzbek' => 'uzb', 'vietnamese' => 'vie', 'welsh' => 'cym', ); /** * Maps ISO 639-1 2-letter language codes to the language names * in the language database * * Not all languages have a 2 letter code, so some are missing * * @var array */ public static $code2ToName = array( 'ar' => 'arabic', 'az' => 'azeri', 'bg' => 'bulgarian', 'bn' => 'bengali', 'cs' => 'czech', 'cy' => 'welsh', 'da' => 'danish', 'de' => 'german', 'en' => 'english', 'es' => 'spanish', 'et' => 'estonian', 'fa' => 'farsi', 'fi' => 'finnish', 'fr' => 'french', 'ha' => 'hausa', 'hi' => 'hindi', 'hr' => 'croatian', 'hu' => 'hungarian', 'id' => 'indonesian', 'is' => 'icelandic', 'it' => 'italian', 'kk' => 'kazakh', 'ky' => 'kyrgyz', 'la' => 'latin', 'lt' => 'lithuanian', 'lv' => 'latvian', 'mk' => 'macedonian', 'mn' => 'mongolian', 'ne' => 'nepali', 'nl' => 'dutch', 'no' => 'norwegian', 'pl' => 'polish', 'ps' => 'pashto', 'pt' => 'portuguese', 'ro' => 'romanian', 'ru' => 'russian', 'sk' => 'slovak', 'sl' => 'slovene', 'so' => 'somali', 'sq' => 'albanian', 'sr' => 'serbian', 'sv' => 'swedish', 'sw' => 'swahili', 'tl' => 'tagalog', 'tr' => 'turkish', 'uk' => 'ukrainian', 'ur' => 'urdu', 'uz' => 'uzbek', 'vi' => 'vietnamese', ); /** * Maps ISO 639-2 3-letter language codes to the language names * in the language database. * * @var array */ public static $code3ToName = array( 'ara' => 'arabic', 'aze' => 'azeri', 'ben' => 'bengali', 'bul' => 'bulgarian', 'ceb' => 'cebuano', 'ces' => 'czech', 'crp' => 'pidgin', 'cym' => 'welsh', 'dan' => 'danish', 'deu' => 'german', 'eng' => 'english', 'est' => 'estonian', 'fas' => 'farsi', 'fin' => 'finnish', 'fra' => 'french', 'hau' => 'hausa', 'haw' => 'hawaiian', 'hin' => 'hindi', 'hrv' => 'croatian', 'hun' => 'hungarian', 'ind' => 'indonesian', 'isl' => 'icelandic', 'ita' => 'italian', 'kaz' => 'kazakh', 'kir' => 'kyrgyz', 'lat' => 'latin', 'lav' => 'latvian', 'lit' => 'lithuanian', 'mkd' => 'macedonian', 'mon' => 'mongolian', 'nep' => 'nepali', 'nld' => 'dutch', 'nor' => 'norwegian', 'pol' => 'polish', 'por' => 'portuguese', 'pus' => 'pashto', 'rom' => 'romanian', 'rus' => 'russian', 'slk' => 'slovak', 'slv' => 'slovene', 'som' => 'somali', 'spa' => 'spanish', 'sqi' => 'albanian', 'srp' => 'serbian', 'swa' => 'swahili', 'swe' => 'swedish', 'tgl' => 'tagalog', 'tur' => 'turkish', 'ukr' => 'ukrainian', 'urd' => 'urdu', 'uzb' => 'uzbek', 'vie' => 'vietnamese', ); /** * Returns the 2-letter ISO 639-1 code for the given language name. * * @param string $lang English language name like "swedish" * * @return string Two-letter language code (e.g. "sv") or NULL if not found */ public static function nameToCode2($lang) { $lang = strtolower($lang); if (!isset(self::$nameToCode2[$lang])) { return null; } return self::$nameToCode2[$lang]; } /** * Returns the 3-letter ISO 639-2 code for the given language name. * * @param string $lang English language name like "swedish" * * @return string Three-letter language code (e.g. "swe") or NULL if not found */ public static function nameToCode3($lang) { $lang = strtolower($lang); if (!isset(self::$nameToCode3[$lang])) { return null; } return self::$nameToCode3[$lang]; } /** * Returns the language name for the given 2-letter ISO 639-1 code. * * @param string $code Two-letter language code (e.g. "sv") * * @return string English language name like "swedish" */ public static function code2ToName($code) { $lang = strtolower($code); if (!isset(self::$code2ToName[$code])) { return null; } return self::$code2ToName[$code]; } /** * Returns the language name for the given 3-letter ISO 639-2 code. * * @param string $code Three-letter language code (e.g. "swe") * * @return string English language name like "swedish" */ public static function code3ToName($code) { $lang = strtolower($code); if (!isset(self::$code3ToName[$code])) { return null; } return self::$code3ToName[$code]; } } ================================================ FILE: libraries/language-detect/LanguageDetect/Parser.php ================================================ _string = $string; } /** * Returns true if a string is suitable for parsing * * @param string $str input string to test * @return bool true if acceptable, false if not */ public static function validateString($str) { if (!empty($str) && strlen($str) > 3 && preg_match('/\S/', $str)) { return true; } else { return false; } } /** * turn on/off trigram counting * * @access public * @param bool $bool true for on, false for off */ function prepareTrigram($bool = true) { $this->_compile_trigram = $bool; } /** * turn on/off unicode block counting * * @access public * @param bool $bool true for on, false for off */ function prepareUnicode($bool = true) { $this->_compile_unicode = $bool; } /** * turn on/off padding the beginning of the sample string * * @access public * @param bool $bool true for on, false for off */ function setPadStart($bool = true) { $this->_trigram_pad_start = $bool; } /** * Should the unicode block counter skip non-alphabetical ascii chars? * * @access public * @param bool $bool true for on, false for off */ function setUnicodeSkipSymbols($bool = true) { $this->_unicode_skip_symbols = $bool; } /** * Returns the trigram ranks for the text sample * * @access public * @return array trigram ranks in the text sample */ function &getTrigramRanks() { return $this->_trigram_ranks; } /** * Return the trigram freqency table * * only used in testing to make sure the parser is working * * @access public * @return array trigram freqencies in the text sample */ function &getTrigramFreqs() { return $this->_trigram; } /** * returns the array of unicode blocks * * @access public * @return array unicode blocks in the text sample */ function &getUnicodeBlocks() { return $this->_unicode_blocks; } /** * Executes the parsing operation * * Be sure to call the set*() functions to set options and the * prepare*() functions first to tell it what kind of data to compute * * Afterwards the get*() functions can be used to access the compiled * information. * * @access public */ function analyze() { $len = strlen($this->_string); $byte_counter = 0; // unicode startup if ($this->_compile_unicode) { $blocks = $this->_read_unicode_block_db(); $block_count = count($blocks); $skipped_count = 0; $unicode_chars = array(); } // trigram startup if ($this->_compile_trigram) { // initialize them as blank so the parser will skip the first two // (since it skips trigrams with more than 2 contiguous spaces) $a = ' '; $b = ' '; // kludge // if it finds a valid trigram to start and the start pad option is // off, then set a variable that will be used to reduce this // trigram after parsing has finished if (!$this->_trigram_pad_start) { $a = $this->_next_char($this->_string, $byte_counter, true); if ($a != ' ') { $b = $this->_next_char($this->_string, $byte_counter, true); $dropone = " $a$b"; } $byte_counter = 0; $a = ' '; $b = ' '; } } while ($byte_counter < $len) { $char = $this->_next_char($this->_string, $byte_counter, true); // language trigram detection if ($this->_compile_trigram) { if (!($b == ' ' && ($a == ' ' || $char == ' '))) { if (!isset($this->_trigram[$a . $b . $char])) { $this->_trigram[$a . $b . $char] = 1; } else { $this->_trigram[$a . $b . $char]++; } } $a = $b; $b = $char; } // unicode block detection if ($this->_compile_unicode) { if ($this->_unicode_skip_symbols && strlen($char) == 1 && ($char < 'A' || $char > 'z' || ($char > 'Z' && $char < 'a')) && $char != "'") { // does not skip the apostrophe // since it's included in the language // models $skipped_count++; continue; } // build an array of all the characters if (isset($unicode_chars[$char])) { $unicode_chars[$char]++; } else { $unicode_chars[$char] = 1; } } // todo: add byte detection here } // unicode cleanup if ($this->_compile_unicode) { foreach ($unicode_chars as $utf8_char => $count) { $search_result = $this->_unicode_block_name( $this->_utf8char2unicode($utf8_char), $blocks, $block_count); if ($search_result != -1) { $block_name = $search_result[2]; } else { $block_name = '[Malformatted]'; } if (isset($this->_unicode_blocks[$block_name])) { $this->_unicode_blocks[$block_name] += $count; } else { $this->_unicode_blocks[$block_name] = $count; } } } // trigram cleanup if ($this->_compile_trigram) { // pad the end if ($b != ' ') { if (!isset($this->_trigram["$a$b "])) { $this->_trigram["$a$b "] = 1; } else { $this->_trigram["$a$b "]++; } } // perl compatibility; Language::Guess does not pad the beginning // kludge if (isset($dropone)) { if ($this->_trigram[$dropone] == 1) { unset($this->_trigram[$dropone]); } else { $this->_trigram[$dropone]--; } } if (!empty($this->_trigram)) { $this->_trigram_ranks = $this->_arr_rank($this->_trigram); } else { $this->_trigram_ranks = array(); } } } } /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ ================================================ FILE: libraries/language-detect/LanguageDetect.php ================================================ * @copyright 2005-2006 Nicholas Pisarro * @license http://www.debian.org/misc/bsd.license BSD * @version SVN: $Id: LanguageDetect.php 322353 2012-01-16 08:41:43Z cweiske $ * @link http://pear.php.net/package/Text_LanguageDetect/ * @link http://langdetect.blogspot.com/ */ require_once 'LanguageDetect/Exception.php'; require_once 'LanguageDetect/Parser.php'; require_once 'LanguageDetect/ISO639.php'; /** * Language detection class * * Requires the langauge model database (lang.dat) that should have * accompanied this class definition in order to be instantiated. * * Example usage: * * * require_once 'Text/LanguageDetect.php'; * * $l = new Text_LanguageDetect; * * $stdin = fopen('php://stdin', 'r'); * * echo "Supported languages:\n"; * * try { * $langs = $l->getLanguages(); * } catch (Text_LanguageDetect_Exception $e) { * die($e->getMessage()); * } * * sort($langs); * echo join(', ', $langs); * * while ($line = fgets($stdin)) { * print_r($l->detect($line, 4)); * } * * * @category Text * @package Text_LanguageDetect * @author Nicholas Pisarro * @copyright 2005 Nicholas Pisarro * @license http://www.debian.org/misc/bsd.license BSD * @version Release: @package_version@ * @link http://pear.php.net/package/Text_LanguageDetect/ * @todo allow users to generate their own language models */ class Text_LanguageDetect { /** * The filename that stores the trigram data for the detector * * If this value starts with a slash (/) or a dot (.) the value of * $this->_data_dir will be ignored * * @var string * @access private */ var $_db_filename = 'lang.dat'; /** * The filename that stores the unicode block definitions * * If this value starts with a slash (/) or a dot (.) the value of * $this->_data_dir will be ignored * * @var string * @access private */ var $_unicode_db_filename = 'unicode_blocks.dat'; /** * The data directory * * Should be set by PEAR installer * * @var string * @access private */ var $_data_dir = '@data_dir@'; /** * The trigram data for comparison * * Will be loaded on start from $this->_db_filename * * @var array * @access private */ var $_lang_db = array(); /** * stores the map of the trigram data to unicode characters * * @access private * @var array */ var $_unicode_map; /** * The size of the trigram data arrays * * @var int * @access private */ var $_threshold = 300; /** * the maximum possible score. * * needed for score normalization. Different depending on the * perl compatibility setting * * @access private * @var int * @see setPerlCompatible() */ var $_max_score = 0; /** * Whether or not to simulate perl's Language::Guess exactly * * @access private * @var bool * @see setPerlCompatible() */ var $_perl_compatible = false; /** * Whether to use the unicode block detection to speed up processing * * @access private * @var bool */ var $_use_unicode_narrowing = true; /** * stores the result of the clustering operation * * @access private * @var array * @see clusterLanguages() */ var $_clusters; /** * Which type of "language names" are accepted and returned: * * 0 - language name ("english") * 2 - 2-letter ISO 639-1 code ("en") * 3 - 3-letter ISO 639-2 code ("eng") */ var $_name_mode = 0; /** * Constructor * * Will attempt to load the language database. If it fails, you will get * an exception. */ function __construct() { $data = $this->_readdb($this->_db_filename); $this->_checkTrigram($data['trigram']); $this->_lang_db = $data['trigram']; if (isset($data['trigram-unicodemap'])) { $this->_unicode_map = $data['trigram-unicodemap']; } // Not yet implemented: if (isset($data['trigram-clusters'])) { $this->_clusters = $data['trigram-clusters']; } } /** * Returns the path to the location of the database * * @param string $fname File name to load * * @return string expected path to the language model database * @access private */ function _get_data_loc($fname) { return dirname(__FILE__).'/'.$fname; } /** * Loads the language trigram database from filename * * Trigram datbase should be a serialize()'d array * * @param string $fname the filename where the data is stored * * @return array the language model data * @throws Text_LanguageDetect_Exception * @access private */ function _readdb($fname) { // finds the correct data dir $fname = $this->_get_data_loc($fname); // input check if (!file_exists($fname)) { throw new Text_LanguageDetect_Exception( 'Language database does not exist: ' . $fname, Text_LanguageDetect_Exception::DB_NOT_FOUND ); } elseif (!is_readable($fname)) { throw new Text_LanguageDetect_Exception( 'Language database is not readable: ' . $fname, Text_LanguageDetect_Exception::DB_NOT_READABLE ); } return unserialize(file_get_contents($fname)); } /** * Checks if this object is ready to detect languages * * @param array $trigram Trigram data from database * * @return void * @access private */ function _checkTrigram($trigram) { if (!is_array($trigram)) { if (ini_get('magic_quotes_runtime')) { throw new Text_LanguageDetect_Exception( 'Error loading database. Try turning magic_quotes_runtime off.', Text_LanguageDetect_Exception::MAGIC_QUOTES ); } throw new Text_LanguageDetect_Exception( 'Language database is not an array.', Text_LanguageDetect_Exception::DB_NOT_ARRAY ); } elseif (empty($trigram)) { throw new Text_LanguageDetect_Exception( 'Language database has no elements.', Text_LanguageDetect_Exception::DB_EMPTY ); } } /** * Omits languages * * Pass this function the name of or an array of names of * languages that you don't want considered * * If you're only expecting a limited set of languages, this can greatly * speed up processing * * @param mixed $omit_list language name or array of names to omit * @param bool $include_only if true will include (rather than * exclude) only those in the list * * @return int number of languages successfully deleted * @throws Text_LanguageDetect_Exception */ public function omitLanguages($omit_list, $include_only = false) { $deleted = 0; $omit_list = $this->_convertFromNameMode($omit_list); if (!$include_only) { // deleting the given languages if (!is_array($omit_list)) { $omit_list = strtolower($omit_list); // case desensitize if (isset($this->_lang_db[$omit_list])) { unset($this->_lang_db[$omit_list]); $deleted++; } } else { foreach ($omit_list as $omit_lang) { if (isset($this->_lang_db[$omit_lang])) { unset($this->_lang_db[$omit_lang]); $deleted++; } } } } else { // deleting all except the given languages if (!is_array($omit_list)) { $omit_list = array($omit_list); } // case desensitize foreach ($omit_list as $key => $omit_lang) { $omit_list[$key] = strtolower($omit_lang); } foreach (array_keys($this->_lang_db) as $lang) { if (!in_array($lang, $omit_list)) { unset($this->_lang_db[$lang]); $deleted++; } } } // reset the cluster cache if the number of languages changes // this will then have to be recalculated if (isset($this->_clusters) && $deleted > 0) { $this->_clusters = null; } return $deleted; } /** * Returns the number of languages that this object can detect * * @access public * @return int the number of languages * @throws Text_LanguageDetect_Exception */ function getLanguageCount() { return count($this->_lang_db); } /** * Checks if the language with the given name exists in the database * * @param mixed $lang Language name or array of language names * * @return bool true if language model exists */ public function languageExists($lang) { $lang = $this->_convertFromNameMode($lang); if (is_string($lang)) { return isset($this->_lang_db[strtolower($lang)]); } elseif (is_array($lang)) { foreach ($lang as $test_lang) { if (!isset($this->_lang_db[strtolower($test_lang)])) { return false; } } return true; } else { throw new Text_LanguageDetect_Exception( 'Unsupported parameter type passed to languageExists()', Text_LanguageDetect_Exception::PARAM_TYPE ); } } /** * Returns the list of detectable languages * * @access public * @return array the names of the languages known to this object<<<<<<< * @throws Text_LanguageDetect_Exception */ function getLanguages() { return $this->_convertToNameMode( array_keys($this->_lang_db) ); } /** * Make this object behave like Language::Guess * * @param bool $setting false to turn off perl compatibility * * @return void */ public function setPerlCompatible($setting = true) { if (is_bool($setting)) { // input check $this->_perl_compatible = $setting; if ($setting == true) { $this->_max_score = $this->_threshold; } else { $this->_max_score = 0; } } } /** * Sets the way how language names are accepted and returned. * * @param integer $name_mode One of the following modes: * 0 - language name ("english") * 2 - 2-letter ISO 639-1 code ("en") * 3 - 3-letter ISO 639-2 code ("eng") * * @return void */ function setNameMode($name_mode) { $this->_name_mode = $name_mode; } /** * Whether to use unicode block ranges in detection * * Should speed up most detections if turned on (detault is on). In some * circumstances it may be slower, such as for large text samples (> 10K) * in languages that use latin scripts. In other cases it should speed up * detection noticeably. * * @param bool $setting false to turn off * * @return void */ public function useUnicodeBlocks($setting = true) { if (is_bool($setting)) { $this->_use_unicode_narrowing = $setting; } } /** * Converts a piece of text into trigrams * * @param string $text text to convert * * @return array array of trigram frequencies * @access private * @deprecated Superceded by the Text_LanguageDetect_Parser class */ function _trigram($text) { $s = new Text_LanguageDetect_Parser($text); $s->prepareTrigram(); $s->prepareUnicode(false); $s->setPadStart(!$this->_perl_compatible); $s->analyze(); return $s->getTrigramFreqs(); } /** * Converts a set of trigrams from frequencies to ranks * * Thresholds (cuts off) the list at $this->_threshold * * @param array $arr array of trigram * * @return array ranks of trigrams * @access protected */ function _arr_rank($arr) { // sorts alphabetically first as a standard way of breaking rank ties $this->_bub_sort($arr); // below might also work, but seemed to introduce errors in testing //ksort($arr); //asort($arr); $rank = array(); $i = 0; foreach ($arr as $key => $value) { $rank[$key] = $i++; // cut off at a standard threshold if ($i >= $this->_threshold) { break; } } return $rank; } /** * Sorts an array by value breaking ties alphabetically * * @param array &$arr the array to sort * * @return void * @access private */ function _bub_sort(&$arr) { // should do the same as this perl statement: // sort { $trigrams{$b} == $trigrams{$a} // ? $a cmp $b : $trigrams{$b} <=> $trigrams{$a} } // needs to sort by both key and value at once // using the key to break ties for the value // converts array into an array of arrays of each key and value // may be a better way of doing this $combined = array(); foreach ($arr as $key => $value) { $combined[] = array($key, $value); } usort($combined, array($this, '_sort_func')); $replacement = array(); foreach ($combined as $key => $value) { list($new_key, $new_value) = $value; $replacement[$new_key] = $new_value; } $arr = $replacement; } /** * Sort function used by bubble sort * * Callback function for usort(). * * @param array $a first param passed by usort() * @param array $b second param passed by usort() * * @return int 1 if $a is greater, -1 if not * @see _bub_sort() * @access private */ function _sort_func($a, $b) { // each is actually a key/value pair, so that it can compare using both list($a_key, $a_value) = $a; list($b_key, $b_value) = $b; if ($a_value == $b_value) { // if the values are the same, break ties using the key return strcmp($a_key, $b_key); } else { // if not, just sort normally if ($a_value > $b_value) { return -1; } else { return 1; } } // 0 should not be possible because keys must be unique } /** * Calculates a linear rank-order distance statistic between two sets of * ranked trigrams * * Sums the differences in rank for each trigram. If the trigram does not * appear in both, consider it a difference of $this->_threshold. * * This distance measure was proposed by Cavnar & Trenkle (1994). Despite * its simplicity it has been shown to be highly accurate for language * identification tasks. * * @param array $arr1 the reference set of trigram ranks * @param array $arr2 the target set of trigram ranks * * @return int the sum of the differences between the ranks of * the two trigram sets * @access private */ function _distance($arr1, $arr2) { $sumdist = 0; foreach ($arr2 as $key => $value) { if (isset($arr1[$key])) { $distance = abs($value - $arr1[$key]); } else { // $this->_threshold sets the maximum possible distance value // for any one pair of trigrams $distance = $this->_threshold; } $sumdist += $distance; } return $sumdist; // todo: there are other distance statistics to try, e.g. relative // entropy, but they're probably more costly to compute } /** * Normalizes the score returned by _distance() * * Different if perl compatible or not * * @param int $score the score from _distance() * @param int $base_count the number of trigrams being considered * * @return float the normalized score * @see _distance() * @access private */ function _normalize_score($score, $base_count = null) { if ($base_count === null) { $base_count = $this->_threshold; } if (!$this->_perl_compatible) { return 1 - ($score / $base_count / $this->_threshold); } else { return floor($score / $base_count); } } /** * Detects the closeness of a sample of text to the known languages * * Calculates the statistical difference between the text and * the trigrams for each language, normalizes the score then * returns results for all languages in sorted order * * If perl compatible, the score is 300-0, 0 being most similar. * Otherwise, it's 0-1 with 1 being most similar. * * The $sample text should be at least a few sentences in length; * should be ascii-7 or utf8 encoded, if another and the mbstring extension * is present it will try to detect and convert. However, experience has * shown that mb_detect_encoding() *does not work very well* with at least * some types of encoding. * * @param string $sample a sample of text to compare. * @param int $limit if specified, return an array of the most likely * $limit languages and their scores. * * @return mixed sorted array of language scores, blank array if no * useable text was found * @see _distance() * @throws Text_LanguageDetect_Exception */ public function detect($sample, $limit = 0) { // input check if (!Text_LanguageDetect_Parser::validateString($sample)) { return array(); } // check char encoding // (only if mbstring extension is compiled and PHP > 4.0.6) if (function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding') ) { // mb_detect_encoding isn't very reliable, to say the least // detection should still work with a sufficient sample // of ascii characters $encoding = mb_detect_encoding($sample); // mb_detect_encoding() will return FALSE if detection fails // don't attempt conversion if that's the case if ($encoding != 'ASCII' && $encoding != 'UTF-8' && $encoding !== false ) { // verify the encoding exists in mb_list_encodings if (in_array($encoding, mb_list_encodings())) { $sample = mb_convert_encoding($sample, 'UTF-8', $encoding); } } } $sample_obj = new Text_LanguageDetect_Parser($sample); $sample_obj->prepareTrigram(); if ($this->_use_unicode_narrowing) { $sample_obj->prepareUnicode(); } $sample_obj->setPadStart(!$this->_perl_compatible); $sample_obj->analyze(); $trigram_freqs =& $sample_obj->getTrigramRanks(); $trigram_count = count($trigram_freqs); if ($trigram_count == 0) { return array(); } $scores = array(); // use unicode block detection to narrow down the possibilities if ($this->_use_unicode_narrowing) { $blocks =& $sample_obj->getUnicodeBlocks(); if (is_array($blocks)) { $present_blocks = array_keys($blocks); } else { throw new Text_LanguageDetect_Exception( 'Error during block detection', Text_LanguageDetect_Exception::BLOCK_DETECTION ); } $possible_langs = array(); foreach ($present_blocks as $blockname) { if (isset($this->_unicode_map[$blockname])) { $possible_langs = array_merge( $possible_langs, array_keys($this->_unicode_map[$blockname]) ); // todo: faster way to do this? } } // could also try an intersect operation rather than a union // in other words, choose languages whose trigrams contain // ALL of the unicode blocks found in this sample // would improve speed but would be completely thrown off by an // unexpected character, like an umlaut appearing in english text $possible_langs = array_intersect( array_keys($this->_lang_db), array_unique($possible_langs) ); // needs to intersect it with the keys of _lang_db in case // languages have been omitted } else { // or just try 'em all $possible_langs = array_keys($this->_lang_db); } foreach ($possible_langs as $lang) { $scores[$lang] = $this->_normalize_score( $this->_distance($this->_lang_db[$lang], $trigram_freqs), $trigram_count ); } unset($sample_obj); if ($this->_perl_compatible) { asort($scores); } else { arsort($scores); } // todo: drop languages with a score of $this->_max_score? // limit the number of returned scores if ($limit && is_numeric($limit)) { $limited_scores = array(); $i = 0; foreach ($scores as $key => $value) { if ($i++ >= $limit) { break; } $limited_scores[$key] = $value; } return $this->_convertToNameMode($limited_scores, true); } else { return $this->_convertToNameMode($scores, true); } } /** * Returns only the most similar language to the text sample * * Calls $this->detect() and returns only the top result * * @param string $sample text to detect the language of * * @return string the name of the most likely language * or null if no language is similar * @see detect() * @throws Text_LanguageDetect_Exception */ public function detectSimple($sample) { $scores = $this->detect($sample, 1); // if top language has the maximum possible score, // then the top score will have been picked at random if (!is_array($scores) || empty($scores) || current($scores) == $this->_max_score ) { return null; } else { return key($scores); } } /** * Returns an array containing the most similar language and a confidence * rating * * Confidence is a simple measure calculated from the similarity score * minus the similarity score from the next most similar language * divided by the highest possible score. Languages that have closely * related cousins (e.g. Norwegian and Danish) should generally have lower * confidence scores. * * The similarity score answers the question "How likely is the text the * returned language regardless of the other languages considered?" The * confidence score is one way of answering the question "how likely is the * text the detected language relative to the rest of the language model * set?" * * To see how similar languages are a priori, see languageSimilarity() * * @param string $sample text for which language will be detected * * @return array most similar language, score and confidence rating * or null if no language is similar * @see detect() * @throws Text_LanguageDetect_Exception */ public function detectConfidence($sample) { $scores = $this->detect($sample, 2); // if most similar language has the max score, it // will have been picked at random if (!is_array($scores) || empty($scores) || current($scores) == $this->_max_score ) { return null; } $arr['language'] = key($scores); $arr['similarity'] = current($scores); if (next($scores) !== false) { // if false then no next element // the goal is to return a higher value if the distance between // the similarity of the first score and the second score is high if ($this->_perl_compatible) { $arr['confidence'] = (current($scores) - $arr['similarity']) / $this->_max_score; } else { $arr['confidence'] = $arr['similarity'] - current($scores); } } else { $arr['confidence'] = null; } return $arr; } /** * Returns the distribution of unicode blocks in a given utf8 string * * For the block name of a single char, use unicodeBlockName() * * @param string $str input string. Must be ascii or utf8 * @param bool $skip_symbols if true, skip ascii digits, symbols and * non-printing characters. Includes spaces, * newlines and common punctutation characters. * * @return array * @throws Text_LanguageDetect_Exception */ public function detectUnicodeBlocks($str, $skip_symbols) { $skip_symbols = (bool)$skip_symbols; $str = (string)$str; $sample_obj = new Text_LanguageDetect_Parser($str); $sample_obj->prepareUnicode(); $sample_obj->prepareTrigram(false); $sample_obj->setUnicodeSkipSymbols($skip_symbols); $sample_obj->analyze(); $blocks = $sample_obj->getUnicodeBlocks(); unset($sample_obj); return $blocks; } /** * Returns the block name for a given unicode value * * If passed a string, will assume it is being passed a UTF8-formatted * character and will automatically convert. Otherwise it will assume it * is being passed a numeric unicode value. * * Make sure input is of the correct type! * * @param mixed $unicode unicode value or utf8 char * * @return mixed the block name string or false if not found * @throws Text_LanguageDetect_Exception */ public function unicodeBlockName($unicode) { if (is_string($unicode)) { // assume it is being passed a utf8 char, so convert it if (self::utf8strlen($unicode) > 1) { throw new Text_LanguageDetect_Exception( 'Pass a single char only to this method', Text_LanguageDetect_Exception::PARAM_TYPE ); } $unicode = $this->_utf8char2unicode($unicode); } elseif (!is_int($unicode)) { throw new Text_LanguageDetect_Exception( 'Input must be of type string or int.', Text_LanguageDetect_Exception::PARAM_TYPE ); } $blocks = $this->_read_unicode_block_db(); $result = $this->_unicode_block_name($unicode, $blocks); if ($result == -1) { return false; } else { return $result[2]; } } /** * Searches the unicode block database * * Returns the block name for a given unicode value. unicodeBlockName() is * the public interface for this function, which does input checks which * this function omits for speed. * * @param int $unicode the unicode value * @param array $blocks the block database * @param int $block_count the number of defined blocks in the database * * @return mixed Block name, -1 if it failed * @see unicodeBlockName() * @access protected */ function _unicode_block_name($unicode, $blocks, $block_count = -1) { // for a reference, see // http://www.unicode.org/Public/UNIDATA/Blocks.txt // assume that ascii characters are the most common // so try it first for efficiency if ($unicode <= $blocks[0][1]) { return $blocks[0]; } // the optional $block_count param is for efficiency // so we this function doesn't have to run count() every time if ($block_count != -1) { $high = $block_count - 1; } else { $high = count($blocks) - 1; } $low = 1; // start with 1 because ascii was 0 // your average binary search algorithm while ($low <= $high) { $mid = floor(($low + $high) / 2); if ($unicode < $blocks[$mid][0]) { // if it's lower than the lower bound $high = $mid - 1; } elseif ($unicode > $blocks[$mid][1]) { // if it's higher than the upper bound $low = $mid + 1; } else { // found it return $blocks[$mid]; } } // failed to find the block return -1; // todo: differentiate when it's out of range or when it falls // into an unassigned range? } /** * Brings up the unicode block database * * @return array the database of unicode block definitions * @throws Text_LanguageDetect_Exception * @access protected */ function _read_unicode_block_db() { // since the unicode definitions are always going to be the same, // might as well share the memory for the db with all other instances // of this class static $data; if (!isset($data)) { $data = $this->_readdb($this->_unicode_db_filename); } return $data; } /** * Calculate the similarities between the language models * * Use this function to see how similar languages are to each other. * * If passed 2 language names, will return just those languages compared. * If passed 1 language name, will return that language compared to * all others. * If passed none, will return an array of every language model compared * to every other one. * * @param string $lang1 the name of the first language to be compared * @param string $lang2 the name of the second language to be compared * * @return array scores of every language compared * or the score of just the provided languages * or null if one of the supplied languages does not exist * @throws Text_LanguageDetect_Exception */ public function languageSimilarity($lang1 = null, $lang2 = null) { $lang1 = $this->_convertFromNameMode($lang1); $lang2 = $this->_convertFromNameMode($lang2); if ($lang1 != null) { $lang1 = strtolower($lang1); // check if language model exists if (!isset($this->_lang_db[$lang1])) { return null; } if ($lang2 != null) { if (!isset($this->_lang_db[$lang2])) { // check if language model exists return null; } $lang2 = strtolower($lang2); // compare just these two languages return $this->_normalize_score( $this->_distance( $this->_lang_db[$lang1], $this->_lang_db[$lang2] ) ); } else { // compare just $lang1 to all languages $return_arr = array(); foreach ($this->_lang_db as $key => $value) { if ($key != $lang1) { // don't compare a language to itself $return_arr[$key] = $this->_normalize_score( $this->_distance($this->_lang_db[$lang1], $value) ); } } asort($return_arr); return $return_arr; } } else { // compare all languages to each other $return_arr = array(); foreach (array_keys($this->_lang_db) as $lang1) { foreach (array_keys($this->_lang_db) as $lang2) { // skip comparing languages to themselves if ($lang1 != $lang2) { if (isset($return_arr[$lang2][$lang1])) { // don't re-calculate what's already been done $return_arr[$lang1][$lang2] = $return_arr[$lang2][$lang1]; } else { // calculate $return_arr[$lang1][$lang2] = $this->_normalize_score( $this->_distance( $this->_lang_db[$lang1], $this->_lang_db[$lang2] ) ); } } } } return $return_arr; } } /** * Cluster known languages according to languageSimilarity() * * WARNING: this method is EXPERIMENTAL. It is not recommended for common * use, and it may disappear or its functionality may change in future * releases without notice. * * Uses a nearest neighbor technique to generate the maximum possible * number of dendograms from the similarity data. * * @access public * @return array language cluster data * @throws Text_LanguageDetect_Exception * @see languageSimilarity() * @deprecated this function will eventually be removed and placed into * the model generation class */ function clusterLanguages() { // todo: set the maximum number of clusters // return cached result, if any if (isset($this->_clusters)) { return $this->_clusters; } $langs = array_keys($this->_lang_db); $arr = $this->languageSimilarity(); sort($langs); foreach ($langs as $lang) { if (!isset($this->_lang_db[$lang])) { throw new Text_LanguageDetect_Exception( "missing $lang!", Text_LanguageDetect_Exception::UNKNOWN_LANGUAGE ); } } // http://www.psychstat.missouristate.edu/multibook/mlt04m.html foreach ($langs as $old_key => $lang1) { $langs[$lang1] = $lang1; unset($langs[$old_key]); } $result_data = $really_map = array(); $i = 0; while (count($langs) > 2 && $i++ < 200) { $highest_score = -1; $highest_key1 = ''; $highest_key2 = ''; foreach ($langs as $lang1) { foreach ($langs as $lang2) { if ($lang1 != $lang2 && $arr[$lang1][$lang2] > $highest_score ) { $highest_score = $arr[$lang1][$lang2]; $highest_key1 = $lang1; $highest_key2 = $lang2; } } } if (!$highest_key1) { // should not ever happen throw new Text_LanguageDetect_Exception( "no highest key? (step: $i)", Text_LanguageDetect_Exception::NO_HIGHEST_KEY ); } if ($highest_score == 0) { // languages are perfectly dissimilar break; } // $highest_key1 and $highest_key2 are most similar $sum1 = array_sum($arr[$highest_key1]); $sum2 = array_sum($arr[$highest_key2]); // use the score for the one that is most similar to the rest of // the field as the score for the group // todo: could try averaging or "centroid" method instead // seems like that might make more sense // actually nearest neighbor may be better for binary searching // for "Complete Linkage"/"furthest neighbor" // sign should be < // for "Single Linkage"/"nearest neighbor" method // should should be > // results seem to be pretty much the same with either method // figure out which to delete and which to replace if ($sum1 > $sum2) { $replaceme = $highest_key1; $deleteme = $highest_key2; } else { $replaceme = $highest_key2; $deleteme = $highest_key1; } $newkey = $replaceme . ':' . $deleteme; // $replaceme is most similar to remaining languages // replace $replaceme with '$newkey', deleting $deleteme // keep a record of which fork is really which language $really_lang = $replaceme; while (isset($really_map[$really_lang])) { $really_lang = $really_map[$really_lang]; } $really_map[$newkey] = $really_lang; // replace the best fitting key, delete the other foreach ($arr as $key1 => $arr2) { foreach ($arr2 as $key2 => $value2) { if ($key2 == $replaceme) { $arr[$key1][$newkey] = $arr[$key1][$key2]; unset($arr[$key1][$key2]); // replacing $arr[$key1][$key2] with $arr[$key1][$newkey] } if ($key1 == $replaceme) { $arr[$newkey][$key2] = $arr[$key1][$key2]; unset($arr[$key1][$key2]); // replacing $arr[$key1][$key2] with $arr[$newkey][$key2] } if ($key1 == $deleteme || $key2 == $deleteme) { // deleting $arr[$key1][$key2] unset($arr[$key1][$key2]); } } } unset($langs[$highest_key1]); unset($langs[$highest_key2]); $langs[$newkey] = $newkey; // some of these may be overkill $result_data[$newkey] = array( 'newkey' => $newkey, 'count' => $i, 'diff' => abs($sum1 - $sum2), 'score' => $highest_score, 'bestfit' => $replaceme, 'otherfit' => $deleteme, 'really' => $really_lang, ); } $return_val = array( 'open_forks' => $langs, // the top level of clusters // clusters that are mutually exclusive // or specified by a specific maximum 'fork_data' => $result_data, // data for each split 'name_map' => $really_map, // which cluster is really which language // using the nearest neighbor technique, the cluster // inherits all of the properties of its most-similar member // this keeps track ); // saves the result in the object $this->_clusters = $return_val; return $return_val; } /** * Perform an intelligent detection based on clusterLanguages() * * WARNING: this method is EXPERIMENTAL. It is not recommended for common * use, and it may disappear or its functionality may change in future * releases without notice. * * This compares the sample text to top the top level of clusters. If the * sample is similar to the cluster it will drop down and compare it to the * languages in the cluster, and so on until it hits a leaf node. * * this should find the language in considerably fewer compares * (the equivalent of a binary search), however clusterLanguages() is costly * and the loss of accuracy from this technique is significant. * * This method may need to be 'fuzzier' in order to become more accurate. * * This function could be more useful if the universe of possible languages * was very large, however in such cases some method of Bayesian inference * might be more helpful. * * @param string $str input string * * @return array language scores (only those compared) * @throws Text_LanguageDetect_Exception * @see clusterLanguages() */ public function clusteredSearch($str) { // input check if (!Text_LanguageDetect_Parser::validateString($str)) { return array(); } // clusterLanguages() will return a cached result if possible // so it's safe to call it every time $result = $this->clusterLanguages(); $dendogram_start = $result['open_forks']; $dendogram_data = $result['fork_data']; $dendogram_alias = $result['name_map']; $sample_obj = new Text_LanguageDetect_Parser($str); $sample_obj->prepareTrigram(); $sample_obj->setPadStart(!$this->_perl_compatible); $sample_obj->analyze(); $sample_result = $sample_obj->getTrigramRanks(); $sample_count = count($sample_result); // input check if ($sample_count == 0) { return array(); } $i = 0; // counts the number of steps foreach ($dendogram_start as $lang) { if (isset($dendogram_alias[$lang])) { $lang_key = $dendogram_alias[$lang]; } else { $lang_key = $lang; } $scores[$lang] = $this->_normalize_score( $this->_distance($this->_lang_db[$lang_key], $sample_result), $sample_count ); $i++; } if ($this->_perl_compatible) { asort($scores); } else { arsort($scores); } $top_score = current($scores); $top_key = key($scores); // of starting forks, $top_key is the most similar to the sample $cur_key = $top_key; while (isset($dendogram_data[$cur_key])) { $lang1 = $dendogram_data[$cur_key]['bestfit']; $lang2 = $dendogram_data[$cur_key]['otherfit']; foreach (array($lang1, $lang2) as $lang) { if (isset($dendogram_alias[$lang])) { $lang_key = $dendogram_alias[$lang]; } else { $lang_key = $lang; } $scores[$lang] = $this->_normalize_score( $this->_distance($this->_lang_db[$lang_key], $sample_result), $sample_count ); //todo: does not need to do same comparison again } $i++; if ($scores[$lang1] > $scores[$lang2]) { $cur_key = $lang1; $loser_key = $lang2; } else { $cur_key = $lang2; $loser_key = $lang1; } $diff = $scores[$cur_key] - $scores[$loser_key]; // $cur_key ({$dendogram_alias[$cur_key]}) wins // over $loser_key ({$dendogram_alias[$loser_key]}) // with a difference of $diff } // found result in $i compares // rather than sorting the result, preserve it so that you can see // which paths the algorithm decided to take along the tree // but sometimes the last item is only the second highest if (($this->_perl_compatible && (end($scores) > prev($scores))) || (!$this->_perl_compatible && (end($scores) < prev($scores))) ) { $real_last_score = current($scores); $real_last_key = key($scores); // swaps the 2nd-to-last item for the last item unset($scores[$real_last_key]); $scores[$real_last_key] = $real_last_score; } if (!$this->_perl_compatible) { $scores = array_reverse($scores, true); // second param requires php > 4.0.3 } return $scores; } /** * ut8-safe strlen() * * Returns the numbers of characters (not bytes) in a utf8 string * * @param string $str string to get the length of * * @return int number of chars */ public static function utf8strlen($str) { // utf8_decode() will convert unknown chars to '?', which is actually // ideal for counting. return strlen(utf8_decode($str)); // idea stolen from dokuwiki } /** * Returns the unicode value of a utf8 char * * @param string $char a utf8 (possibly multi-byte) char * * @return int unicode value * @access protected * @link http://en.wikipedia.org/wiki/UTF-8 */ function _utf8char2unicode($char) { // strlen() here will actually get the binary length of a single char switch (strlen($char)) { case 1: // normal ASCII-7 byte // 0xxxxxxx --> 0xxxxxxx return ord($char{0}); case 2: // 2 byte unicode // 110zzzzx 10xxxxxx --> 00000zzz zxxxxxxx $z = (ord($char{0}) & 0x000001F) << 6; $x = (ord($char{1}) & 0x0000003F); return ($z | $x); case 3: // 3 byte unicode // 1110zzzz 10zxxxxx 10xxxxxx --> zzzzzxxx xxxxxxxx $z = (ord($char{0}) & 0x0000000F) << 12; $x1 = (ord($char{1}) & 0x0000003F) << 6; $x2 = (ord($char{2}) & 0x0000003F); return ($z | $x1 | $x2); case 4: // 4 byte unicode // 11110zzz 10zzxxxx 10xxxxxx 10xxxxxx --> // 000zzzzz xxxxxxxx xxxxxxxx $z1 = (ord($char{0}) & 0x00000007) << 18; $z2 = (ord($char{1}) & 0x0000003F) << 12; $x1 = (ord($char{2}) & 0x0000003F) << 6; $x2 = (ord($char{3}) & 0x0000003F); return ($z1 | $z2 | $x1 | $x2); } } /** * utf8-safe fast character iterator * * Will get the next character starting from $counter, which will then be * incremented. If a multi-byte char the bytes will be concatenated and * $counter will be incremeted by the number of bytes in the char. * * @param string $str the string being iterated over * @param int &$counter the iterator, will increment by reference * @param bool $special_convert whether to do special conversions * * @return char the next (possibly multi-byte) char from $counter * @access private */ static function _next_char($str, &$counter, $special_convert = false) { $char = $str{$counter++}; $ord = ord($char); // for a description of the utf8 system see // http://www.phpclasses.org/browse/file/5131.html // normal ascii one byte char if ($ord <= 127) { // special conversions needed for this package // (that only apply to regular ascii characters) // lower case, and convert all non-alphanumeric characters // other than "'" to space if ($special_convert && $char != ' ' && $char != "'") { if ($ord >= 65 && $ord <= 90) { // A-Z $char = chr($ord + 32); // lower case } elseif ($ord < 97 || $ord > 122) { // NOT a-z $char = ' '; // convert to space } } return $char; } elseif ($ord >> 5 == 6) { // two-byte char // multi-byte chars $nextchar = $str{$counter++}; // get next byte // lower-casing of non-ascii characters is still incomplete if ($special_convert) { // lower case latin accented characters if ($ord == 195) { $nextord = ord($nextchar); $nextord_adj = $nextord + 64; // for a reference, see // http://www.ramsch.org/martin/uni/fmi-hp/iso8859-1.html // À - Þ but not × if ($nextord_adj >= 192 && $nextord_adj <= 222 && $nextord_adj != 215 ) { $nextchar = chr($nextord + 32); } } elseif ($ord == 208) { // lower case cyrillic alphabet $nextord = ord($nextchar); // if A - Pe if ($nextord >= 144 && $nextord <= 159) { // lower case $nextchar = chr($nextord + 32); } elseif ($nextord >= 160 && $nextord <= 175) { // if Er - Ya // lower case $char = chr(209); // == $ord++ $nextchar = chr($nextord - 32); } } } // tag on next byte return $char . $nextchar; } elseif ($ord >> 4 == 14) { // three-byte char // tag on next 2 bytes return $char . $str{$counter++} . $str{$counter++}; } elseif ($ord >> 3 == 30) { // four-byte char // tag on next 3 bytes return $char . $str{$counter++} . $str{$counter++} . $str{$counter++}; } else { // error? } } /** * Converts an $language input parameter from the configured mode * to the language name that is used internally. * * Works for strings and arrays. * * @param string|array $lang A language description ("english"/"en"/"eng") * @param boolean $convertKey If $lang is an array, setting $key * converts the keys to the language name. * * @return string|array Language name */ function _convertFromNameMode($lang, $convertKey = false) { if ($this->_name_mode == 0) { return $lang; } if ($this->_name_mode == 2) { $method = 'code2ToName'; } else { $method = 'code3ToName'; } if (is_string($lang)) { return (string)Text_LanguageDetect_ISO639::$method($lang); } $newlang = array(); foreach ($lang as $key => $val) { if ($convertKey) { $newkey = (string)Text_LanguageDetect_ISO639::$method($key); $newlang[$newkey] = $val; } else { $newlang[$key] = (string)Text_LanguageDetect_ISO639::$method($val); } } return $newlang; } /** * Converts an $language output parameter from the language name that is * used internally to the configured mode. * * Works for strings and arrays. * * @param string|array $lang A language description ("english"/"en"/"eng") * @param boolean $convertKey If $lang is an array, setting $key * converts the keys to the language name. * * @return string|array Language name */ function _convertToNameMode($lang, $convertKey = false) { if ($this->_name_mode == 0) { return $lang; } if ($this->_name_mode == 2) { $method = 'nameToCode2'; } else { $method = 'nameToCode3'; } if (is_string($lang)) { return Text_LanguageDetect_ISO639::$method($lang); } $newlang = array(); foreach ($lang as $key => $val) { if ($convertKey) { $newkey = Text_LanguageDetect_ISO639::$method($key); $newlang[$newkey] = $val; } else { $newlang[$key] = Text_LanguageDetect_ISO639::$method($val); } } return $newlang; } } /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ ?> ================================================ FILE: libraries/readability/ImageCaching.php ================================================ lower_limit_setting = self::LOWER_LIMIT; $this->upper_limit_setting = self::UPPER_LIMIT; $opts = array( 'http' => array( 'method'=>"GET", 'header'=>"Accept-language: en\r\n" . "Connection: close\r\n" . "User-Agent: Opera/9.80 (Windows NT 6.1; Win64; x64; Edition Next) Presto/2.12.388 Version/12.15\r\n" ) ); $this->context_get = stream_context_create($opts); } public function setAllowedSizes($lower, $upper) { if ($lower > $upper) { $tmp = $upper; $upper = $lower; $lower = $tmp; } if ($lower && $lower > self::LOWER_MIN && $lower < self::UPPER_MAX) $this->lower_limit_setting = $lower; if ($upper && $upper > self::LOWER_MIN && $upper < self::UPPER_MAX) $this->upper_limit_setting = $upper; } public function cacheFromString(&$html) { $callback = function ($matches) { $data_uri = ''; $url = $matches[1]; if (!preg_match('!^https?://!i', $url) || strlen($url) < 12) return $matches[0]; $url = filter_var($url, FILTER_SANITIZE_URL); if (!$this->images[$url]){ $data_uri = $this->getImageDataURI($url); if (strlen($data_uri) > 1) $this->images[$url] = $data_uri; else return $matches[0]; } else { $data_uri = $this->images[$url]; } if (strlen($data_uri) > 1) return '' . $matches[1] . ''; else return $matches[0]; }; $html = preg_replace_callback('/< *img[^>]+src *= *["\']?([^"\'>]*)[^>]*>/i', $callback, $html); } public function cacheFromDocument(&$document) { $imageNodes = $document->getElementsByTagName('img'); for ($node = null, $nodeIndex = 0; ($node = $imageNodes->item($nodeIndex)); $nodeIndex++) { $url = $node->getAttribute('src'); $node->removeAttribute('class'); $node->removeAttribute('id'); if (!preg_match('!^https?://!i', $url) || strlen($url) < 12) continue; $url = filter_var($url, FILTER_SANITIZE_URL); $data_uri = ''; if (!$this->images[$url]){ $data_uri = $this->getImageDataURI($url); if (strlen($data_uri) > 1) $this->images[$url] = $data_uri; else continue; } else { $data_uri = $this->images[$url]; } if (strlen($data_uri) > 1) { $node->setAttribute('src', $data_uri); $node->setAttribute('alt', $url); } } } protected function getFileSize($image) { $contentLength = 0; $status = 0; $matches = array(); if (!function_exists('curl_init')) return 0; $ch = curl_init($image); curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_USERAGENT, "Opera/9.80 (Windows NT 6.1; Win64; x64; Edition Next) Presto/2.12.388 Version/12.15"); $data = curl_exec($ch); curl_close($ch); if ($data === false) return 0; if (preg_match('/^HTTP\/1\.[01] (\d\d\d)/', $data, $matches)) $status = (int) $matches[1]; if ($status === 200 && preg_match('/Content-Length: (\d+)/', $data, $matches)) $contentLength = (int) $matches[1]; return $contentLength; } protected function getImageDataURI($image, $mime = '') { $image = filter_var($image, FILTER_SANITIZE_URL); if (!preg_match('!^https?://!i', $image)) return ''; $ext = substr(strrchr($image, ".") , 1, 3); if (strcasecmp($ext, 'jpg') === 0 || strcasecmp($ext, 'png') === 0 || strcasecmp($ext, 'jpe') === 0 || strcasecmp($ext, 'gif') === 0) { $size = $this->getFileSize($image); if ($size < $this->lower_limit_setting || $size > $this->upper_limit_setting) return ''; $content = @file_get_contents($image, false, $this->context_get); if (function_exists('finfo_open')) $mime = (new finfo(FILEINFO_MIME_TYPE))->buffer($content); if (!preg_match('/^image/i', $mime)) return ''; return 'data:' . $mime . ';base64,' . base64_encode($content); } return ''; } } ?> ================================================ FILE: libraries/readability/JSLikeHTMLElement.php ================================================ registerNodeClass('DOMElement', 'JSLikeHTMLElement'); * $doc->loadHTML('

    Para 1

    Para 2

    '); * $elem = $doc->getElementsByTagName('div')->item(0); * * // print innerHTML * echo $elem->innerHTML; // prints '

    Para 1

    Para 2

    ' * echo "\n\n"; * * // set innerHTML * $elem->innerHTML = 'FiveFilters.org'; * echo $elem->innerHTML; // prints 'FiveFilters.org' * echo "\n\n"; * * // print document (with our changes) * echo $doc->saveXML(); * @endcode * * @author Keyvan Minoukadeh - http://www.keyvan.net - keyvan@keyvan.net * @see http://fivefilters.org (the project this was written for) */ class JSLikeHTMLElement extends DOMElement { /** * Used for setting innerHTML like it's done in JavaScript: * @code * $div->innerHTML = '

    Chapter 2

    The story begins...

    '; * @endcode */ public function __set($name, $value) { if ($name == 'innerHTML') { // first, empty the element for ($x=$this->childNodes->length-1; $x>=0; $x--) { $this->removeChild($this->childNodes->item($x)); } // $value holds our new inner HTML if ($value != '') { $f = $this->ownerDocument->createDocumentFragment(); // appendXML() expects well-formed markup (XHTML) $result = @$f->appendXML($value); // @ to suppress PHP warnings if ($result) { if ($f->hasChildNodes()) $this->appendChild($f); } else { // $value is probably ill-formed $f = new DOMDocument(); $value = mb_convert_encoding($value, 'HTML-ENTITIES', 'UTF-8'); // Using will generate a warning, but so will bad HTML // (and by this point, bad HTML is what we've got). // We use it (and suppress the warning) because an HTML fragment will // be wrapped around tags which we don't really want to keep. // Note: despite the warning, if loadHTML succeeds it will return true. $result = @$f->loadHTML(''.$value.''); if ($result) { $import = $f->getElementsByTagName('htmlfragment')->item(0); foreach ($import->childNodes as $child) { $importedNode = $this->ownerDocument->importNode($child, true); $this->appendChild($importedNode); } } else { // oh well, we tried, we really did. :( // this element is now empty } } } } else { $trace = debug_backtrace(); trigger_error('Undefined property via __set(): '.$name.' in '.$trace[0]['file'].' on line '.$trace[0]['line'], E_USER_NOTICE); } } /** * Used for getting innerHTML like it's done in JavaScript: * @code * $string = $div->innerHTML; * @endcode */ public function __get($name) { if ($name == 'innerHTML') { $inner = ''; foreach ($this->childNodes as $child) { $inner .= $this->ownerDocument->saveXML($child); } return $inner; } $trace = debug_backtrace(); trigger_error('Undefined property via __get(): '.$name.' in '.$trace[0]['file'].' on line '.$trace[0]['line'], E_USER_NOTICE); return null; } public function __toString() { return '['.$this->tagName.']'; } } ?> ================================================ FILE: libraries/readability/Readability.php ================================================ debug = true; $r->init(); //$r->preserveImages(); unset($html); echo $r->articleContent->innerHTML; } /**/ class Readability { public $version = '1.7.2-without-multi-page'; public $convertLinksToFootnotes = false; public $revertForcedParagraphElements = true; public $articleTitle; public $articleContent; public $dom; public $url = null; // optional - URL where HTML was retrieved public $lightClean = true; // preserves more content (experimental) public $debug = false; public $tidied = false; public $imageCache = null; // Cache the images from the article protected $debugText = ''; // error text for one time output protected $domainRegExp = null; // article domain regexp for calibration protected $body = null; // protected $bodyCache = null; // Cache the body HTML in case we need to re-use it later protected $flags = 7; // 1 | 2 | 4; // Start with all processing flags set. protected $success = false; // indicates whether we were able to extract or not /** * All of the regular expressions in use within readability. * Defined up here so we don't instantiate them repeatedly in loops. **/ public $regexps = array( 'unlikelyCandidates' => '/display\s*:\s*none|ignore|\binfo|annoy|clock|date|time|author|intro|links|hidd?e|about|archive|\bprint|bookmark|tags|share|search|social|robot|published|combx|comment|mast(?:head)|subscri|community|category|disqus|extra|head(?:er|note)|floor|foot(?:er|note)|menu|tool|function|nav|remark|rss|shoutbox|tool|widget|meta|banner|sponsor|adsense|inner-?ad|ad-|sponsor|\badv\b|\bads\b|agr?egate?|pager|sidebar|popup|tweet|twitter/i', 'okMaybeItsACandidate' => '/article\b|contain|\bcontent|column|general|detail|shadow|lightbox|blog|body|entry|main|page/i', 'positive' => '/read|full|article|body|\bcontent|contain|entry|main|markdown|page|attach|pagination|post|text|blog|story/i', 'negative' => '/bottom|stat|info|discuss|e[\-]?mail|comment|reply|log.{2}(n|ed)|sign|single|combx|com-|contact|_nav|link|media|\bout|promo|\bad-|related|scroll|shoutbox|sidebar|sponsor|shopping|teaser/i', 'divToPElements' => '/<(?:blockquote|code|div|article|footer|aside|img|p|pre|dl|ol|ul)/mi', 'killBreaks' => '/(([ \r\n\s]| ?)*)+/', 'media' => '!//(?:[^\.\?/]+\.)?(?:youtu(?:be)?|soundcloud|vimeo|pornhub|xvideos|twitvid|rutube|viddler)\.(?:com|be|org|net)/!i', 'skipFootnoteLink' => '/^\s*(\[?[a-z0-9]{1,2}\]?|^|edit|citation needed)\s*$/i' ); public $tidy_config = array( 'tidy-mark' => false, 'vertical-space' => false, 'doctype' => 'omit', 'numeric-entities' => false, 'preserve-entities' => true, 'break-before-br' => false, 'clean' => true, 'output-xhtml' => true, 'logical-emphasis' => true, 'show-body-only' => false, 'new-blocklevel-tags' => 'article,aside,audio,details,figcaption,figure,footer,header,hgroup,nav,section,source,summary,temp,track,video', 'new-empty-tags' => 'command,embed,keygen,source,track,wbr', 'new-inline-tags' => 'audio,canvas,command,datalist,embed,keygen,mark,meter,output,progress,time,video,wbr', 'wrap' => 0, 'drop-empty-paras' => true, 'drop-proprietary-attributes' => false, 'enclose-text' => true, 'enclose-block-text' => true, 'merge-divs' => true, 'merge-spans' => true, 'input-encoding' => '????', 'output-encoding' => 'utf8', 'hide-comments' => true ); // raw HTML filters protected $pre_filters = array( '!]*>(.*?)!is' => '', // remove obvious scripts '!]*>(.*?)!is' => '', // remove obvious styles '!]*>!is' => '', // remove spans as we redefine styles and they're probably special-styled '!]*>\s*\[AD\]\s*!is' => '', // HACK: firewall-filtered content '!(]*>[ \r\n\s]*){2,}!i' => '

    ', // HACK: replace linebreaks plus br's with p's //'!!is' => '', // replace noscripts '!<(/?)font[^>]*>!is' => '<\\1span>' // replace fonts to spans ); // output HTML filters protected $post_filters = array( '/\s*

    ']+/>!is' => '', // replace empty tags that break layouts //'!<(\s*/?\s*(?:blockquote|br|hr|code|div|article|span|footer|aside|p|pre|dl|li|ul|ol)) [^>]+>!is' => "<\\1>", // remove all attributes on text tags "/\n+/" => "\n", //single newlines cleanup '!]*>\s* '\s*

    !is' => '', '!<[hb]r>!is' => '<\\1 />' ); // flags const FLAG_STRIP_UNLIKELYS = 1; const FLAG_WEIGHT_ATTRIBUTES = 2; const FLAG_CLEAN_CONDITIONALLY = 4; const FLAG_DISABLE_PREFILTER = 8; const FLAG_DISABLE_POSTFILTER = 16; // constants const SCORE_CHARS_IN_PARAGRAPH = 100; const SCORE_WORDS_IN_PARAGRAPH = 20; const GRANDPARENT_SCORE_DIVISOR = 2.2; const MIN_PARAGRAPH_LENGTH = 20; const MIN_COMMAS_IN_PARAGRAPH = 6; const MIN_ARTICLE_LENGTH = 200; const MIN_NODE_LENGTH = 80; const MAX_LINK_DENSITY = 0.25; /** * Create instance of Readability * @param string UTF-8 encoded string * @param string (optional) URL associated with HTML (for footnotes) * @param string (optional) Which parser to use for turning raw HTML into a DOMDocument * @param boolean (optional) Use tidy */ function __construct($html, $url=null, $parser='libxml', $use_tidy=true) { $this->url = $url; $this->debugText = 'Parsing URL: '.$url."\n"; if ($url) $this->domainRegExp = '/'.strtr(preg_replace('/www\d*\./', '', parse_url($url)['host']), array('.' => '\.')).'/'; mb_internal_encoding("UTF-8"); mb_http_output("UTF-8"); mb_regex_encoding("UTF-8"); $this->imageCache = new ImageCaching(); // HACK: dirty cleanup to replace some stuff; shouldn't use regexps with HTML but well... if (!$this->flagIsActive(self::FLAG_DISABLE_PREFILTER)) { try { foreach ($this->pre_filters as $search => $replace) { $html = preg_replace($search, $replace, $html); } unset($search, $replace); } catch (Exception $e) { $this->debugText .= "Cleaning raw HTML failed. Ignoring: " . $e->getMessage(); } } if (trim($html) === '') $html = ''; /** * Use tidy (if it exists). * This fixes problems with some sites which would otherwise trouble DOMDocument's HTML parsing. * Although sometimes it makes matters worse, which is why there is an option to disable it. * **/ if ($use_tidy && function_exists('tidy_parse_string')) { $this->debugText .= 'Tidying document'."\n"; $tidy = tidy_parse_string($html, $this->tidy_config, 'UTF8'); if (tidy_clean_repair($tidy)) { $original_html = $html; $this->tidied = true; $html = $tidy->value; $html = preg_replace('/]+>/i', '', $html); $html = preg_replace('/[\r\n]+/is', "\n", $html); } unset($tidy); } $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8"); if ($parser=='html5lib' && ($this->dom = HTML5_Parser::parse($html))) { // all good } else { libxml_use_internal_errors(true); $this->dom = new DOMDocument(); $this->dom->preserveWhiteSpace = false; @$this->dom->loadHTML($html, LIBXML_NOBLANKS | LIBXML_COMPACT | LIBXML_NOERROR); } $this->dom->registerNodeClass('DOMElement', 'JSLikeHTMLElement'); } /** * Get article title element * @return DOMElement */ public function getTitle() { return $this->articleTitle; } /** * Get article content element * @return DOMElement */ public function getContent() { return $this->articleContent; } /** * Embed images as data-URI. **/ public function preserveImages() { if ($this->success && $this->articleContent) { $this->imageCache->cacheFromDocument($this->articleContent); } } /** * Add pre filter for raw input HTML processing * @param string RegExp for replace * @param string (optional) Replacer */ public function addPreFilter($filter, $replacer='') { $this->pre_filters[$filter] = $replacer; } /** * Add post filter for raw output HTML processing * @param string RegExp for replace * @param string (optional) Replacer */ public function addPostFilter($filter, $replacer='') { $this->post_filters[$filter] = $replacer; } /** * Runs readability. * * Workflow: * 1. Prep the document by removing script tags, css, etc. * 2. Build readability's DOM tree. * 3. Grab the article content from the current dom tree. * 4. Replace the current DOM tree with the new one. * 5. Read peacefully. * * @return boolean true if we found content, false otherwise **/ public function init() { if (!isset($this->dom->documentElement)) return false; $this->removeScripts($this->dom); // Assume successful outcome $this->success = true; $bodyElems = $this->dom->getElementsByTagName('body'); // WTF multiple body nodes? if ($this->bodyCache == null) { $this->bodyCache = ''; foreach ($bodyElems as $bodyNode) $this->bodyCache += $bodyNode->innerHTML; } if ($bodyElems->length > 0 && $this->body == null) { $this->body = $bodyElems->item(0); } $this->prepDocument(); // Build readability's DOM tree. $overlay = $this->dom->createElement('div'); $innerDiv = $this->dom->createElement('div'); $articleTitle = $this->getArticleTitle(); $articleContent = $this->grabArticle(); if (!$articleContent) { $this->success = false; $articleContent = $this->dom->createElement('div'); $articleContent->setAttribute('id', 'readability-content'); $articleContent->innerHTML = '

    Sorry, Readability was unable to parse this page for content.

    '; } $overlay->setAttribute('id', 'readOverlay'); $innerDiv->setAttribute('id', 'readInner'); // Glue the structure of our document together. $innerDiv->appendChild($articleTitle); $innerDiv->appendChild($articleContent); $overlay->appendChild($innerDiv); // Clear the old HTML, insert the new content. $this->body->innerHTML = ''; $this->body->appendChild($overlay); $this->body->removeAttribute('style'); $this->postProcessContent($articleContent); // Set title and content instance variables. $this->articleTitle = $articleTitle; $this->articleContent = $articleContent; $this->dump_dbg(); return $this->success; } /** * Debug */ protected function dbg($msg) {//, $error=false) { if ($this->debug) $this->debugText .= $msg."\n"; //echo '* ',, "\n"; } /** * Dump debug info */ protected function dump_dbg() { if ($this->debug) { if ($this->debug) openlog("Readability PHP ", LOG_PID | LOG_PERROR, 0); syslog(6, $this->debugText); // 1 - error 6 - info } } /** * Run any post-process modifications to article content as necessary. * * @param DOMElement * @return void */ public function postProcessContent($articleContent) { if ($this->convertLinksToFootnotes && !preg_match('/\bwiki/', @$this->url)) { $this->addFootnotes($articleContent); } } /** * Get the article title as an H1. * * @return DOMElement */ protected function getArticleTitle() { $curTitle = ''; $origTitle = ''; try { $curTitle = $origTitle = $this->getInnerText($this->dom->getElementsByTagName('title')->item(0)); } catch(Exception $e) {} if (preg_match('/ [\|\-] /', $curTitle)) { $curTitle = preg_replace('/(.*)[\|\-] .*/i', '$1', $origTitle); if (count(explode(' ', $curTitle)) < 3) { $curTitle = preg_replace('/[^\|\-]*[\|\-](.*)/i', '$1', $origTitle); } } else if (strpos($curTitle, ': ') !== false) { $curTitle = preg_replace('/.*:(.*)/i', '$1', $origTitle); if (count(explode(' ', $curTitle)) < 3) { $curTitle = preg_replace('/[^:]*[:](.*)/i','$1', $origTitle); } } else if(mb_strlen($curTitle) > 150 || mb_strlen($curTitle) < 15) { $hOnes = $this->dom->getElementsByTagName('h1'); if($hOnes->length == 1) { $curTitle = $this->getInnerText($hOnes->item(0)); } } $curTitle = trim($curTitle); if (count(explode(' ', $curTitle)) <= 4) { $curTitle = $origTitle; } $articleTitle = $this->dom->createElement('h1'); $articleTitle->innerHTML = $curTitle; return $articleTitle; } /** * Prepare the HTML document for readability to scrape it. * This includes things like stripping javascript, CSS, and handling terrible markup. * * @return void **/ protected function prepDocument() { /** * In some cases a body element can't be found (if the HTML is totally hosed for example) * so we create a new body node and append it to the document. */ if ($this->body == null) { $this->body = $this->dom->createElement('body'); $this->dom->documentElement->appendChild($this->body); } $this->body->setAttribute('id', 'readabilityBody'); // Remove all style tags in head. $styleTags = $this->dom->getElementsByTagName('style'); for ($i = $styleTags->length-1; $i >= 0; $i--) { $styleTags->item($i)->parentNode->removeChild($styleTags->item($i)); } $linkTags = $this->dom->getElementsByTagName('link'); for ($i = $linkTags->length-1; $i >= 0; $i--) { $linkTags->item($i)->parentNode->removeChild($linkTags->item($i)); } } /** * For easier reading, convert this document to have footnotes at the bottom rather than inline links. * @see http://www.roughtype.com/archives/2010/05/experiments_in.php * * @return void **/ public function addFootnotes($articleContent) { $footnotesWrapper = $this->dom->createElement('footer'); $footnotesWrapper->setAttribute('id', 'readability-footnotes'); $footnotesWrapper->innerHTML = '

    References

    '; $articleFootnotes = $this->dom->createElement('ol'); $articleFootnotes->setAttribute('id', 'readability-footnotes-list'); $footnotesWrapper->appendChild($articleFootnotes); $articleLinks = $articleContent->getElementsByTagName('a'); $linkCount = 0; for ($i = 0; $i < $articleLinks->length; $i++) { $articleLink = $articleLinks->item($i); $footnoteLink = $articleLink->cloneNode(true); $refLink = $this->dom->createElement('a'); $footnote = $this->dom->createElement('li'); $linkDomain = @parse_url($footnoteLink->getAttribute('href'), PHP_URL_HOST); if (!$linkDomain && isset($this->url)) $linkDomain = @parse_url($this->url, PHP_URL_HOST); $linkText = $this->getInnerText($articleLink); if ((strpos($articleLink->getAttribute('class'), 'readability-DoNotFootnote') !== false) || preg_match($this->regexps['skipFootnoteLink'], $linkText)) { continue; } $linkCount++; // Add a superscript reference after the article link. $refLink->setAttribute('href', '#readabilityFootnoteLink-' . $linkCount); $refLink->innerHTML = '[' . $linkCount . ']'; $refLink->setAttribute('class', 'readability-DoNotFootnote'); $refLink->setAttribute('style', 'color: inherit;'); if ($articleLink->parentNode->lastChild->isSameNode($articleLink)) { $articleLink->parentNode->appendChild($refLink); } else { $articleLink->parentNode->insertBefore($refLink, $articleLink->nextSibling); } $articleLink->setAttribute('style', 'color: inherit; text-decoration: none;'); $articleLink->setAttribute('name', 'readabilityLink-' . $linkCount); $footnote->innerHTML = '^ '; $footnoteLink->innerHTML = ($footnoteLink->getAttribute('title') != '' ? $footnoteLink->getAttribute('title') : $linkText); $footnoteLink->setAttribute('name', 'readabilityFootnoteLink-' . $linkCount); $footnote->appendChild($footnoteLink); if ($linkDomain) $footnote->innerHTML = $footnote->innerHTML . ' (' . $linkDomain . ')'; $articleFootnotes->appendChild($footnote); } if ($linkCount > 0) { $articleContent->appendChild($footnotesWrapper); } } /** * Prepare the article node for display. Clean out any inline styles, * iframes, forms, strip extraneous

    tags, etc. * * @param DOMElement * @return void */ function prepArticle($articleContent) { if ($this->lightClean) { $this->dbg('Light clean enabled.'); } else { $this->dbg('Standard clean enabled.'); } $this->cleanStyles($articleContent); $this->killBreaks($articleContent); $xpath = new DOMXPath($articleContent->ownerDocument); if ($this->revertForcedParagraphElements) { /* * Reverts P elements with class 'readability-styled' to text nodes: * which is what they were before. */ $elems = $xpath->query('.//p[@data-readability-styled]', $articleContent); for ($i = $elems->length-1; $i >= 0; $i--) { $e = $elems->item($i); $e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e); } } // Remove service data-candidate attribute. $elems = $xpath->query('.//*[@data-candidate]', $articleContent); for ($i = $elems->length-1; $i >= 0; $i--) { $elems->item($i)->removeAttribute('data-candidate'); } // Remove unrelated links and other unneded stuff. // (not(*) and not(text()[normalize-space()])) or // What's wrong here? $elems = $xpath->query('.//a[@rel="nofollow"]', $articleContent); for ($i = $elems->length-1; $i >= 0; $i--) { $elems->item($i)->parentNode->removeChild($elems->item($i)); } // Clean out junk from the article content. $this->clean($articleContent, 'input'); $this->clean($articleContent, 'button'); $this->clean($articleContent, 'nav'); $this->clean($articleContent, 'object'); $this->clean($articleContent, 'iframe'); $this->clean($articleContent, 'canvas'); $this->clean($articleContent, 'h1'); /** * If there is only one h2, they are probably using it as a main header, so remove it since we * already have a header. ***/ $h2s = $articleContent->getElementsByTagName('h2'); if ($h2s->length == 1 && mb_strlen($this->getInnerText($h2s->item(0), true, true)) < 100) { $this->clean($articleContent, 'h2'); } $this->cleanHeaders($articleContent); // Do these last as the previous stuff may have removed junk that will affect these. $this->cleanConditionally($articleContent, 'form'); $this->cleanConditionally($articleContent, 'table'); $this->cleanConditionally($articleContent, 'ul'); //if (!$this->lightClean) $this->cleanConditionally($articleContent, 'div'); // Remove extra paragraphs. $articleParagraphs = $articleContent->getElementsByTagName('p'); for ($i = $articleParagraphs->length-1; $i >= 0; $i--) { $imgCount = $articleParagraphs->item($i)->getElementsByTagName('img')->length; $embedCount = $articleParagraphs->item($i)->getElementsByTagName('embed')->length; $objectCount = $articleParagraphs->item($i)->getElementsByTagName('object')->length; $videoCount = $articleParagraphs->item($i)->getElementsByTagName('video')->length; $audioCount = $articleParagraphs->item($i)->getElementsByTagName('audio')->length; if ($imgCount === 0 && $embedCount === 0 && $objectCount === 0 && $videoCount === 0 && $audioCount === 0 && mb_strlen(preg_replace('/\s+/is', '', $this->getInnerText($articleParagraphs->item($i), false, false))) === 0) { $articleParagraphs->item($i)->parentNode->removeChild($articleParagraphs->item($i)); } } if (!$this->flagIsActive(self::FLAG_DISABLE_POSTFILTER)) { try { foreach ($this->post_filters as $search => $replace) { $articleContent->innerHTML = preg_replace($search, $replace, $articleContent->innerHTML); } unset($search, $replace); } catch (Exception $e) { $this->dbg("Cleaning output HTML failed. Ignoring: " . $e->getMessage()); } } } /** * Initialize a node with the readability object. Also checks the * className/id for special names to add to its score. * * @param Element * @return void **/ protected function initializeNode($node) { if (!isset($node->tagName)) return; $readability = $this->dom->createAttribute('readability'); $readability->value = 0; // this is our contentScore $node->setAttributeNode($readability); switch (strtoupper($node->tagName)) { // using strtoupper just in case case 'ARTICLE': $readability->value += 15; case 'DIV': $readability->value += 5; break; case 'PRE': case 'CODE': case 'TD': case 'BLOCKQUOTE': case 'FIGURE': $readability->value += 3; break; /* case 'SECTION': // often misused $readability->value += 2; break; */ case 'OL': case 'UL': case 'DL': case 'DD': case 'DT': case 'LI': $readability->value -= 2 * round($this->getLinkDensity($node), 0, PHP_ROUND_HALF_UP); break; case 'ASIDE': case 'FOOTER': case 'HEADER': case 'ADDRESS': case 'FORM': case 'BUTTON': case 'TEXTAREA': case 'INPUT': case 'NAV': $readability->value -= 3; break; case 'H1': case 'H2': case 'H3': case 'H4': case 'H5': case 'H6': case 'TH': case 'HGROUP': $readability->value -= 5; break; } $readability->value += $this->getWeight($node); } /*** * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is * most likely to be the stuff a user wants to read. Then return it wrapped up in a div. * * @return DOMElement **/ protected function grabArticle($page=null) { if (!$page) $page = $this->dom; $xpath = null; $nodesToScore = array(); if ($page instanceof DOMDocument && isset($page->documentElement)) { $xpath = new DOMXPath($page); } $allElements = $page->getElementsByTagName('*'); for ($nodeIndex = 0; ($node = $allElements->item($nodeIndex)); $nodeIndex++) { $tagName = $node->tagName; // Some well known site uses sections as paragraphs. if (strcasecmp($tagName, 'p') === 0 || strcasecmp($tagName, 'td') === 0 || strcasecmp($tagName, 'section') === 0) { $nodesToScore[] = $node; } // Turn divs into P tags where they have been used inappropriately // (as in, where they contain no other block level elements). if (strcasecmp($tagName, 'div') === 0 || strcasecmp($tagName, 'article') === 0 || strcasecmp($tagName, 'section') === 0) { if (!preg_match($this->regexps['divToPElements'], $node->innerHTML)) { //$this->dbg('Altering '.$node->getNodePath().' to p'); $newNode = $this->dom->createElement('p'); try { $newNode->innerHTML = $node->innerHTML; // It's easier to debug using original attributes. //$newNode->setAttribute('class', $node->getAttribute('class')); //$newNode->setAttribute('id', $node->getAttribute('id')); $node = $node->parentNode->replaceChild($newNode, $node); $nodeIndex--; $nodesToScore[] = $newNode; } catch (Exception $e) { $this->dbg('Could not alter div/article to p, reverting back to div: ' . $e->getMessage()); } } else { // Will change these P elements back to text nodes after processing. for ($i = 0, $il = $node->childNodes->length; $i < $il; $i++) { $childNode = $node->childNodes->item($i); if (is_object($childNode) && get_class($childNode) === 'DOMProcessingInstruction') { //executable tags (parentNode->removeChild($childNode); continue; } if ($childNode->nodeType == 3) { // XML_TEXT_NODE //$this->dbg('replacing text node with a P tag with the same content.'); $p = $this->dom->createElement('p'); $p->innerHTML = $childNode->nodeValue; $p->setAttribute('data-readability-styled', 'true'); $childNode->parentNode->replaceChild($p, $childNode); } } } } } /** * Loop through all paragraphs, and assign a score to them based on how content-y they look. * Then add their score to their parent node. * * A score is determined by things like number of commas, class names, etc. * Maybe eventually link density. **/ for ($pt=0, $scored = count($nodesToScore); $pt < $scored; $pt++) { $parentNode = $nodesToScore[$pt]->parentNode; // No parent node? Move on... if (!$parentNode) continue; $grandParentNode = ($parentNode->parentNode instanceof DOMElement) ? $parentNode->parentNode : null; $innerText = $this->getInnerText($nodesToScore[$pt]); // If this paragraph is less than MIN_PARAGRAPH_LENGTH (default:20) characters, don't even count it. if(mb_strlen($innerText) < self::MIN_PARAGRAPH_LENGTH) continue; // Initialize readability data for the parent. if (!$parentNode->hasAttribute('readability')) { $this->initializeNode($parentNode); $parentNode->setAttribute('data-candidate','true'); } // Initialize readability data for the grandparent. if ($grandParentNode && !$grandParentNode->hasAttribute('readability') && isset($grandParentNode->tagName)) { $this->initializeNode($grandParentNode); $grandParentNode->setAttribute('data-candidate','true'); } // Add a point for the paragraph itself as a base. $contentScore = 1; // Add points for any commas within this paragraph. $contentScore += $this->getCommaCount($innerText); // For every SCORE_CHARS_IN_PARAGRAPH (default:100) characters in this paragraph, add another point. Up to 3 points. $contentScore += min(floor(mb_strlen($innerText) / self::SCORE_CHARS_IN_PARAGRAPH), 3); // For every SCORE_WORDS_IN_PARAGRAPH (default:20) words in this paragraph, add another point. Up to 3 points. $contentScore += min(floor($this->getWordCount($innerText)/ self::SCORE_WORDS_IN_PARAGRAPH), 3); /* TEST: For every positive/negative parent tag, add/substract half point. Up to 3 points. *\/ $up = $nodesToScore[$pt]; $score = 0; while ($up->parentNode instanceof DOMElement) { $up = $up->parentNode; if (preg_match($this->regexps['positive'], $up->getAttribute('class') . ' ' . $up->getAttribute('id'))) { $score += 0.5; } else if (preg_match($this->regexps['negative'], $up->getAttribute('class') . ' ' . $up->getAttribute('id'))) { $score -= 0.5; } } $score = floor($score); $contentScore += max(min($score, 3), -3);/**/ // Add the score to the parent. The grandparent gets half. $parentNode->getAttributeNode('readability')->value += $contentScore; if ($grandParentNode) { $grandParentNode->getAttributeNode('readability')->value += $contentScore / self::GRANDPARENT_SCORE_DIVISOR; } } /** * Node prepping: trash nodes that look cruddy (like ones with the class name "comment", etc). * This is faster to do before scoring but safer after. */ if ($this->flagIsActive(self::FLAG_STRIP_UNLIKELYS) && $xpath) { $candidates = $xpath->query('.//*[(self::footer and count(//footer)<2) or (self::aside and count(//aside)<2)]', $page->documentElement); for ($node = null, $c = $candidates->length-1; $c >= 0; $c--) { $node = $candidates->item($c); // node should be readable but not inside of an article otherwise it's probably non-readable block if ($node->hasAttribute('readability') && (int)$node->getAttributeNode('readability')->value < 40 && ($node->parentNode ? strcasecmp($node->parentNode->tagName, 'article') !== 0 : true)) { $this->dbg('Removing unlikely candidate '.$node->getNodePath().' by "'.$node->tagName.'" with readability '.($node->hasAttribute('readability') ? (int)$node->getAttributeNode('readability')->value : 0)); $node->parentNode->removeChild($node); } } $candidates = $xpath->query('.//*[not(self::body) and (@class or @id or @style) and ((number(@readability) < 40) or not(@readability))]', $page->documentElement); for ($node = null, $c = $candidates->length-1; $c >= 0; $c--) { $node = $candidates->item($c); $tagName = $node->tagName; /* Remove unlikely candidates */ $unlikelyMatchString = $node->getAttribute('class')." ".$node->getAttribute('id')." ".$node->getAttribute('style'); //$this->dbg('Processing '.$node->getNodePath().' by "'. $unlikelyMatchString.'" with readability '.($node->hasAttribute('readability') ? (int)$node->getAttributeNode('readability')->value : 0)); if (mb_strlen($unlikelyMatchString) > 3 && // don't process "empty" strings preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString) && !preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString) ) { $this->dbg('Removing unlikely candidate '.$node->getNodePath().' by "'. $unlikelyMatchString.'" with readability '. ($node->hasAttribute('readability') ? (int)$node->getAttributeNode('readability')->value : 0)); $node->parentNode->removeChild($node); $nodeIndex--; } } unset($candidates); } /** * After we've calculated scores, loop through all of the possible candidate nodes we found * and find the one with the highest score. **/ $topCandidate = null; if ($xpath) { // Using array of DOMElements after deletion is a path to DOOMElement. $candidates = $xpath->query('.//*[@data-candidate]', $page->documentElement); for ($c = $candidates->length-1; $c >= 0; $c--) { // Scale the final candidates score based on link density. Good content should have a // relatively small link density (5% or less) and be mostly unaffected by this operation. // If not for this we would have used XPath to find maximum @readability. $readability = $candidates->item($c)->getAttributeNode('readability'); $readability->value = round($readability->value * (1 - $this->getLinkDensity($candidates->item($c))), 0, PHP_ROUND_HALF_UP); if (!$topCandidate || $readability->value > (int)$topCandidate->getAttribute('readability')) { $this->dbg('Candidate: '.$candidates->item($c)->getNodePath().' ('.$candidates->item($c)->getAttribute('class').':'.$candidates->item($c)->getAttribute('id').') with score '.$readability->value); $topCandidate = $candidates->item($c); } } unset($candidates); } /** * If we still have no top candidate, just use the body as a last resort. * We also have to copy the body node so it is something we can modify. **/ if ($topCandidate === null || strcasecmp($topCandidate->tagName, 'body') === 0) { $topCandidate = $this->dom->createElement('div'); if ($page instanceof DOMDocument) { if (!isset($page->documentElement)) { // we don't have a body either? what a mess! :) $this->dbg('The page has no body!'); } else { $this->dbg('Setting body to a raw HTML of original page!'); $topCandidate->innerHTML = $page->documentElement->innerHTML; $page->documentElement->innerHTML = ''; $page->documentElement->appendChild($topCandidate); } } else { $topCandidate->innerHTML = $page->innerHTML; $page->innerHTML = ''; $page->appendChild($topCandidate); } $this->initializeNode($topCandidate); } // Set table as the main node if resulted data is table element. $tagName = $topCandidate->tagName; if (strcasecmp($tagName, 'td') === 0 || strcasecmp($tagName, 'tr') === 0) { $up = $topCandidate; while ($up->parentNode instanceof DOMElement) { $up = $up->parentNode; if (strcasecmp($up->tagName, 'table') === 0) { $topCandidate = $up; break; } } } $this->dbg('Top candidate: '.$topCandidate->getNodePath()); /** * Now that we have the top candidate, look through its siblings for content that might also be related. * Things like preambles, content split by ads that we removed, etc. **/ $articleContent = $this->dom->createElement('div'); $articleContent->setAttribute('id', 'readability-content'); $siblingScoreThreshold = max(10, ((int)$topCandidate->getAttribute('readability')) * 0.2); $siblingNodes = $topCandidate->parentNode->childNodes; if (!isset($siblingNodes)) { $siblingNodes = new stdClass; $siblingNodes->length = 0; } for ($s = 0, $sl = $siblingNodes->length; $s < $sl; $s++) { $siblingNode = $siblingNodes->item($s); $siblingNodeName = $siblingNode->nodeName; $append = false; $this->dbg('Looking at sibling node: ' . $siblingNode->getNodePath() . (($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability')) ? (' with score ' . $siblingNode->getAttribute('readability')) : '')); //$this->dbg('Sibling has score ' . ($siblingNode->readability ? siblingNode.readability.contentScore : 'Unknown')); if ($siblingNode->isSameNode($topCandidate)) { $append = true; } $contentBonus = 0; // Give a bonus if sibling nodes and top candidates have the same classname. if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->getAttribute('class') == $topCandidate->getAttribute('class') && $topCandidate->getAttribute('class') != '') { $contentBonus += ((int)$topCandidate->getAttribute('readability')) * 0.2; } if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability') && (((int)$siblingNode->getAttribute('readability')) + $contentBonus) >= $siblingScoreThreshold) { $append = true; } if (strcasecmp($siblingNodeName, 'p') === 0) { $linkDensity = $this->getLinkDensity($siblingNode); $nodeContent = $this->getInnerText($siblingNode, true, true); $nodeLength = mb_strlen($nodeContent); if ($nodeLength > self::MIN_NODE_LENGTH && $linkDensity < self::MAX_LINK_DENSITY) { $append = true; } else if ($nodeLength < self::MIN_NODE_LENGTH && $linkDensity === 0 && preg_match('/\.( |$)/', $nodeContent)) { $append = true; } } if ($append) { $this->dbg('Appending node: ' . $siblingNode->getNodePath()); $nodeToAppend = null; if (strcasecmp($siblingNodeName, 'div') !== 0 && strcasecmp($siblingNodeName, 'p') !== 0) { /* We have a node that isn't a common block level element, like a form or td tag. Turn it into a div so it doesn't get filtered out later by accident. */ $this->dbg('Altering siblingNode ' . $siblingNodeName . ' to div.'); $nodeToAppend = $this->dom->createElement('div'); try { $nodeToAppend->setAttribute('id', $siblingNode->getAttribute('id')); $nodeToAppend->setAttribute('alt', $siblingNodeName); $nodeToAppend->innerHTML = $siblingNode->innerHTML; } catch(Exception $e) { $this->dbg('Could not alter siblingNode ' . $siblingNodeName . ' to div, reverting to original.'); $nodeToAppend = $siblingNode; $s--; $sl--; } } else { $nodeToAppend = $siblingNode; $s--; $sl--; } // To ensure a node does not interfere with readability styles, remove its classnames & ids. // Now done via RegExp post_filter. //$nodeToAppend->removeAttribute('class'); //$nodeToAppend->removeAttribute('id'); // Append sibling and subtract from our list as appending removes a node. $articleContent->appendChild($nodeToAppend); } } unset($xpath); // So we have all of the content that we need. Now we clean it up for presentation. $this->prepArticle($articleContent); /** * Now that we've gone through the full algorithm, check to see if we got any meaningful content. * If we didn't, we may need to re-run grabArticle with different flags set. This gives us a higher * likelihood of finding the content, and the sieve approach gives us a higher likelihood of * finding the -right- content. **/ if (mb_strlen($this->getInnerText($articleContent, false)) < self::MIN_ARTICLE_LENGTH) { if (!$this->body->hasChildNodes()) $this->body = $this->dom->createElement('body'); $this->body->innerHTML = $this->bodyCache; if ($this->flagIsActive(self::FLAG_STRIP_UNLIKELYS)) { $this->removeFlag(self::FLAG_STRIP_UNLIKELYS); $this->dbg("...content is shorter than ".self::MIN_ARTICLE_LENGTH." letters, trying not to strip unlikely content.\n"); return $this->grabArticle($this->body); } else if ($this->flagIsActive(self::FLAG_WEIGHT_ATTRIBUTES)) { $this->removeFlag(self::FLAG_WEIGHT_ATTRIBUTES); $this->dbg("...content is shorter than ".self::MIN_ARTICLE_LENGTH." letters, trying not to weight attributes.\n"); return $this->grabArticle($this->body); } else if ($this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) { $this->removeFlag(self::FLAG_CLEAN_CONDITIONALLY); $this->dbg("...content is shorter than ".self::MIN_ARTICLE_LENGTH." letters, trying not to clean at all.\n"); return $this->grabArticle($this->body); } else { return false; } } return $articleContent; } /** * Remove script tags from document * * @param DOMElement * @return void */ public function removeScripts($doc) { $scripts = $doc->getElementsByTagName('script'); for($i = $scripts->length-1; $i >= 0; $i--) { $scripts->item($i)->parentNode->removeChild($scripts->item($i)); } } /** * Get the inner text of a node. * This also strips out any excess whitespace to be found. * * @param DOMElement $e * @param boolean $normalizeSpaces (default: true) * @param boolean $flattenLines (default: false) * @return string **/ public function getInnerText($e, $normalizeSpaces=true, $flattenLines=false) { if (!isset($e->textContent) || $e->textContent === '') return ''; $textContent = trim($e->textContent); if ($flattenLines) { $textContent = mb_ereg_replace('(?:[\r\n](?:\s| )*)+', '', $textContent); } else if ($normalizeSpaces) { $textContent = mb_ereg_replace('\s\s+', ' ', $textContent); } return $textContent; } /** * Remove the style attribute on every $e and under. * * @param DOMElement $e * @return void */ public function cleanStyles($e) { if (!is_object($e)) return; $elems = $e->getElementsByTagName('*'); foreach ($elems as $elem) { $elem->removeAttribute('style'); } } /** * Get comma number for a given text. * * @param string $text * @return number (integer) */ public function getCommaCount($text) { return substr_count($text, ','); } /** * Get words number for a given text if words separated by a space. * Input string should be normalized. * * @param string $text * @return number (integer) */ public function getWordCount($text) { return substr_count($text, ' '); } /** * Get the density of links as a percentage of the content * This is the amount of text that is inside a link divided by the total text in the node. * Can exclude external references to differentiate between simple text and menus/infoblocks. * * @param DOMElement $e * @param string $excludeExternal * @return number (float) */ public function getLinkDensity($e, $excludeExternal=false) { $links = $e->getElementsByTagName('a'); $textLength = mb_strlen($this->getInnerText($e, true, true)); $linkLength = 0; for ($dRe = $this->domainRegExp, $i=0, $il=$links->length; $i < $il; $i++) { if ($excludeExternal && $dRe && !preg_match($dRe, $links->item($i)->getAttribute('href'))) continue; $linkLength += mb_strlen($this->getInnerText($links->item($i))); } if ($textLength > 0 && $linkLength > 0) { return $linkLength / $textLength; } else { return 0; } } /** * Get an element weight by attribute. * Uses regular expressions to tell if this element looks good or bad. * * @param DOMElement $element * @param string $attribute * @return number (Integer) */ protected function weightAttribute($element, $attribute) { if (!$element->hasAttribute($attribute)) { return 0; } $weight = 0; //$attribute_val = trim($element->getAttribute('class')." ".$element->getAttribute('id')); $attribute_val = trim($element->getAttribute($attribute)); if ($attribute_val != '') { if (preg_match($this->regexps['negative'], $attribute_val)) { $weight -= 25; } if (preg_match($this->regexps['positive'], $attribute_val)) { $weight += 25; } if (preg_match($this->regexps['unlikelyCandidates'], $attribute_val)) { $weight -= 5; } if (preg_match($this->regexps['okMaybeItsACandidate'], $attribute_val)) { $weight += 5; } } return $weight; } /** * Get an element relative weight. * * @param DOMElement $e * @return number (Integer) */ public function getWeight($e) { if(!$this->flagIsActive(self::FLAG_WEIGHT_ATTRIBUTES)) { return 0; } $weight = 0; /* Look for a special classname */ $weight += $this->weightAttribute($e, 'class'); /* Look for a special ID */ $weight += $this->weightAttribute($e, 'id'); return $weight; } /** * Remove extraneous break tags from a node. * * @param DOMElement $node * @return void */ public function killBreaks($node) { $html = $node->innerHTML; $html = preg_replace($this->regexps['killBreaks'], '
    ', $html); $node->innerHTML = $html; } /** * Clean a node of all elements of type "tag". * (Unless it's a youtube/vimeo video. People love movies.) * * Updated 2012-09-18 to preserve youtube/vimeo iframes * * @param DOMElement $e * @param string $tag * @return void */ public function clean($e, $tag) { $targetList = $e->getElementsByTagName($tag); $isEmbed = ($tag === 'audio' || $tag === 'video' || $tag === 'iframe' || $tag === 'object' || $tag === 'embed'); for ($cur_item = null, $y = $targetList->length-1; $y >= 0; $y--) { /* Allow youtube and vimeo videos through as people usually want to see those. */ $cur_item = $targetList->item($y); if ($isEmbed) { $attributeValues = $cur_item->getAttribute('src').' '.$cur_item->getAttribute('href'); /* First, check the elements attributes to see if any of them contain known media hosts */ if (preg_match($this->regexps['media'], $attributeValues)) continue; /* Then check the elements inside this element for the same. */ if (preg_match($this->regexps['media'], $targetList->item($y)->innerHTML)) continue; } $cur_item->parentNode->removeChild($cur_item); } } /** * Clean an element of all tags of type "tag" if they look fishy. * "Fishy" is an algorithm based on content length, classnames, * link density, number of images & embeds, etc. * * @param DOMElement $e * @param string $tag * @return void */ public function cleanConditionally($e, $tag) { if (!$this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) { return; } $tagsList = $e->getElementsByTagName($tag); $curTagsLength = $tagsList->length; /** * Gather counts for other typical elements embedded within. * Traverse backwards so we can remove nodes at the same time without effecting the traversal. * * TODO: Consider taking into account original contentScore here. */ for ($node = null, $i = $curTagsLength - 1; $i >= 0; $i--) { $node = $tagsList->item($i); //$class = $node->getAttribute('class').' '.$node->getAttribute('id'); //debug $weight = $this->getWeight($node); $contentScore = ($node->hasAttribute('readability')) ? (int)$node->getAttribute('readability') : 0; $this->dbg('Start conditional cleaning of ' . $node->getNodePath() . ' (class=' . $node->getAttribute('class') . '; id=' . $node->getAttribute('id') . ')' . (($node->hasAttribute('readability')) ? (' with score ' . $node->getAttribute('readability')) : '')); if ($weight + $contentScore < 0) { $this->dbg('Removing...'); $node->parentNode->removeChild($node); } else if ($this->getCommaCount($this->getInnerText($node)) < self::MIN_COMMAS_IN_PARAGRAPH) { /** * If there are not very many commas, and the number of * non-paragraph elements is more than paragraphs or other ominous signs, remove the element. **/ $p = $node->getElementsByTagName('p')->length; $img = $node->getElementsByTagName('img')->length; $li = $node->getElementsByTagName('li')->length-100; $input = $node->getElementsByTagName('input')->length; $a = $node->getElementsByTagName('a')->length; $embedCount = 0; $embeds = $node->getElementsByTagName('embed'); for ($ei=0, $il=$embeds->length; $ei < $il; $ei++) { if (preg_match($this->regexps['media'], $embeds->item($ei)->getAttribute('src'))) { $embedCount++; } } $embeds = $node->getElementsByTagName('iframe'); for ($ei=0, $il=$embeds->length; $ei < $il; $ei++) { if (preg_match($this->regexps['media'], $embeds->item($ei)->getAttribute('src'))) { $embedCount++; } } $linkDensity = $this->getLinkDensity($node, true); $contentLength = mb_strlen($this->getInnerText($node)); $toRemove = false; if ($this->lightClean) { if ($li > $p && $tag != 'ul' && $tag != 'ol') { $this->dbg(' too many

  • elements, and parent is not
      or
        '); $toRemove = true; } else if ( $input > floor($p/3) ) { $this->dbg(' too many elements'); $toRemove = true; } else if ($contentLength < 6 && ($embedCount === 0 && ($img === 0 || $img > 2))) { $this->dbg(' content length less than 6 chars, 0 embeds and either 0 images or more than 2 images'); $toRemove = true; } else if($weight < 25 && $linkDensity > 0.25) { $this->dbg(' weight is '.$weight.' < 25 and link density is '.sprintf("%.2f", $linkDensity).' > 0.25'); $toRemove = true; } else if($a > 2 && ($weight >= 25 && $linkDensity > 0.5)) { $this->dbg(' more than 2 links and weight is '.$weight.' > 25 but link density is '.sprintf("%.2f", $linkDensity).' > 0.5'); $toRemove = true; } else if($embedCount > 3) { $this->dbg(' more than 3 embeds'); $toRemove = true; } } else { if ( $img > $p ) { $this->dbg(' more image elements than paragraph elements'); $toRemove = true; } else if ($li > $p && $tag != 'ul' && $tag != 'ol') { $this->dbg(' too many
      1. elements, and parent is not
          or
            '); $toRemove = true; } else if ( $input > floor($p/3) ) { $this->dbg(' too many elements'); $toRemove = true; } else if ($contentLength < 25 && ($img === 0 || $img > 2) ) { $this->dbg(' content length less than 25 chars and 0 images, or more than 2 images'); $toRemove = true; } else if($weight < 25 && $linkDensity > 0.2) { $this->dbg(' weight is '.$weight.' lower than 0 and link density is '.sprintf("%.2f", $linkDensity).' > 0.2'); $toRemove = true; } else if($weight >= 25 && $linkDensity > 0.5) { $this->dbg(' weight above 25 but link density is '.sprintf("%.2f", $linkDensity).' > 0.5'); $toRemove = true; } else if(($embedCount == 1 && $contentLength < 75) || $embedCount > 1) { $this->dbg(' 1 embed and content length smaller than 75 chars, or more than one embed'); $toRemove = true; } } if ($toRemove) { //$this->dbg('Removing: '.$node->innerHTML); $this->dbg('Removing...'); $node->parentNode->removeChild($node); } } } } /** * Clean out spurious headers from an Element. Checks things like classnames and link density. * * @param DOMElement $e * @return void */ public function cleanHeaders($e) { for ($headerIndex = 1; $headerIndex < 3; $headerIndex++) { $headers = $e->getElementsByTagName('h' . $headerIndex); for ($i=$headers->length-1; $i >=0; $i--) { if ($this->getWeight($headers->item($i)) < 0 || $this->getLinkDensity($headers->item($i)) > 0.33) { $headers->item($i)->parentNode->removeChild($headers->item($i)); } } } } public function flagIsActive($flag) { return ($this->flags & $flag) > 0; } public function addFlag($flag) { $this->flags = $this->flags | $flag; } public function removeFlag($flag) { $this->flags = $this->flags & ~$flag; } } ?> ================================================ FILE: libraries/simplepie/LICENSE.txt ================================================ Copyright (c) 2004-2007, Ryan Parman and Geoffrey Sneddon. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the SimplePie Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: libraries/simplepie/autoloader.php ================================================ path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library'; } /** * Autoloader * * @param string $class The name of the class to attempt to load. */ public function autoload($class) { // Only load the class if it starts with "SimplePie" if (strpos($class, 'SimplePie') !== 0) { return; } $filename = $this->path . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; include $filename; } } ================================================ FILE: libraries/simplepie/library/SimplePie/Author.php ================================================ name = $name; $this->link = $link; $this->email = $email; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Author's name * * @return string|null */ public function get_name() { if ($this->name !== null) { return $this->name; } else { return null; } } /** * Author's link * * @return string|null */ public function get_link() { if ($this->link !== null) { return $this->link; } else { return null; } } /** * Author's email address * * @return string|null */ public function get_email() { if ($this->email !== null) { return $this->email; } else { return null; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Cache/Base.php ================================================ get_items(); $items_by_id = array(); if (!empty($items)) { foreach ($items as $item) { $items_by_id[$item->get_id()] = $item; } if (count($items_by_id) !== count($items)) { $items_by_id = array(); foreach ($items as $item) { $items_by_id[$item->get_id(true)] = $item; } } if (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0])) { $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]; } elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0])) { $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]; } elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0])) { $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]; } elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0])) { $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0]; } else { $channel = null; } if ($channel !== null) { if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'])) { unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']); } if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry'])) { unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']); } if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item'])) { unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']); } if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item'])) { unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']); } if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item'])) { unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']); } } if (isset($data->data['items'])) { unset($data->data['items']); } if (isset($data->data['ordered_items'])) { unset($data->data['ordered_items']); } } return array(serialize($data->data), $items_by_id); } } ================================================ FILE: libraries/simplepie/library/SimplePie/Cache/File.php ================================================ location = $location; $this->filename = $name; $this->extension = $type; $this->name = "$this->location/$this->filename.$this->extension"; } /** * Save data to the cache * * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data) { if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location)) { if ($data instanceof SimplePie) { $data = $data->data; } $data = serialize($data); return (bool) file_put_contents($this->name, $data); } return false; } /** * Retrieve the data saved to the cache * * @return array Data for SimplePie::$data */ public function load() { if (file_exists($this->name) && is_readable($this->name)) { return unserialize(file_get_contents($this->name)); } return false; } /** * Retrieve the last modified time for the cache * * @return int Timestamp */ public function mtime() { if (file_exists($this->name)) { return filemtime($this->name); } return false; } /** * Set the last modified time to the current time * * @return bool Success status */ public function touch() { if (file_exists($this->name)) { return touch($this->name); } return false; } /** * Remove the cache * * @return bool Success status */ public function unlink() { if (file_exists($this->name)) { return unlink($this->name); } return false; } } ================================================ FILE: libraries/simplepie/library/SimplePie/Cache/Memcache.php ================================================ options = array( 'host' => '127.0.0.1', 'port' => 11211, 'extras' => array( 'timeout' => 3600, // one hour 'prefix' => 'simplepie_', ), ); $parsed = SimplePie_Cache::parse_URL($location); $this->options['host'] = empty($parsed['host']) ? $this->options['host'] : $parsed['host']; $this->options['port'] = empty($parsed['port']) ? $this->options['port'] : $parsed['port']; $this->options['extras'] = array_merge($this->options['extras'], $parsed['extras']); $this->name = $this->options['extras']['prefix'] . md5("$name:$type"); $this->cache = new Memcache(); $this->cache->addServer($this->options['host'], (int) $this->options['port']); } /** * Save data to the cache * * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data) { if ($data instanceof SimplePie) { $data = $data->data; } return $this->cache->set($this->name, serialize($data), MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']); } /** * Retrieve the data saved to the cache * * @return array Data for SimplePie::$data */ public function load() { $data = $this->cache->get($this->name); if ($data !== false) { return unserialize($data); } return false; } /** * Retrieve the last modified time for the cache * * @return int Timestamp */ public function mtime() { $data = $this->cache->get($this->name); if ($data !== false) { // essentially ignore the mtime because Memcache expires on it's own return time(); } return false; } /** * Set the last modified time to the current time * * @return bool Success status */ public function touch() { $data = $this->cache->get($this->name); if ($data !== false) { return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->duration); } return false; } /** * Remove the cache * * @return bool Success status */ public function unlink() { return $this->cache->delete($this->name, 0); } } ================================================ FILE: libraries/simplepie/library/SimplePie/Cache/MySQL.php ================================================ options = array( 'user' => null, 'pass' => null, 'host' => '127.0.0.1', 'port' => '3306', 'path' => '', 'extras' => array( 'prefix' => '', ), ); $this->options = array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); // Path is prefixed with a "/" $this->options['dbname'] = substr($this->options['path'], 1); try { $this->mysql = new PDO("mysql:dbname={$this->options['dbname']};host={$this->options['host']};port={$this->options['port']}", $this->options['user'], $this->options['pass'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')); } catch (PDOException $e) { $this->mysql = null; return; } $this->id = $name . $type; if (!$query = $this->mysql->query('SHOW TABLES')) { $this->mysql = null; return; } $db = array(); while ($row = $query->fetchColumn()) { $db[] = $row; } if (!in_array($this->options['extras']['prefix'] . 'cache_data', $db)) { $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))'); if ($query === false) { $this->mysql = null; } } if (!in_array($this->options['extras']['prefix'] . 'items', $db)) { $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'); if ($query === false) { $this->mysql = null; } } } /** * Save data to the cache * * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data) { if ($this->mysql === null) { return false; } if ($data instanceof SimplePie) { $data = clone $data; $prepared = self::prepare_simplepie_object_for_cache($data); $query = $this->mysql->prepare('SELECT COUNT(*) FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed'); $query->bindValue(':feed', $this->id); if ($query->execute()) { if ($query->fetchColumn() > 0) { $items = count($prepared[1]); if ($items) { $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = :items, `data` = :data, `mtime` = :time WHERE `id` = :feed'; $query = $this->mysql->prepare($sql); $query->bindValue(':items', $items); } else { $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `data` = :data, `mtime` = :time WHERE `id` = :feed'; $query = $this->mysql->prepare($sql); } $query->bindValue(':data', $prepared[0]); $query->bindValue(':time', time()); $query->bindValue(':feed', $this->id); if (!$query->execute()) { return false; } } else { $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:feed, :count, :data, :time)'); $query->bindValue(':feed', $this->id); $query->bindValue(':count', count($prepared[1])); $query->bindValue(':data', $prepared[0]); $query->bindValue(':time', time()); if (!$query->execute()) { return false; } } $ids = array_keys($prepared[1]); if (!empty($ids)) { foreach ($ids as $id) { $database_ids[] = $this->mysql->quote($id); } $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `id` = ' . implode(' OR `id` = ', $database_ids) . ' AND `feed_id` = :feed'); $query->bindValue(':feed', $this->id); if ($query->execute()) { $existing_ids = array(); while ($row = $query->fetchColumn()) { $existing_ids[] = $row; } $new_ids = array_diff($ids, $existing_ids); foreach ($new_ids as $new_id) { if (!($date = $prepared[1][$new_id]->get_date('U'))) { $date = time(); } $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(:feed, :id, :data, :date)'); $query->bindValue(':feed', $this->id); $query->bindValue(':id', $new_id); $query->bindValue(':data', serialize($prepared[1][$new_id]->data)); $query->bindValue(':date', $date); if (!$query->execute()) { return false; } } return true; } } else { return true; } } } else { $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed'); $query->bindValue(':feed', $this->id); if ($query->execute()) { if ($query->rowCount() > 0) { $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = 0, `data` = :data, `mtime` = :time WHERE `id` = :feed'); $query->bindValue(':data', serialize($data)); $query->bindValue(':time', time()); $query->bindValue(':feed', $this->id); if ($this->execute()) { return true; } } else { $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:id, 0, :data, :time)'); $query->bindValue(':id', $this->id); $query->bindValue(':data', serialize($data)); $query->bindValue(':time', time()); if ($query->execute()) { return true; } } } } return false; } /** * Retrieve the data saved to the cache * * @return array Data for SimplePie::$data */ public function load() { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('SELECT `items`, `data` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); $query->bindValue(':id', $this->id); if ($query->execute() && ($row = $query->fetch())) { $data = unserialize($row[1]); if (isset($this->options['items'][0])) { $items = (int) $this->options['items'][0]; } else { $items = (int) $row[0]; } if ($items !== 0) { if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0])) { $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]; } elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0])) { $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]; } elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0])) { $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]; } elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0])) { $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]; } else { $feed = null; } if ($feed !== null) { $sql = 'SELECT `data` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :feed ORDER BY `posted` DESC'; if ($items > 0) { $sql .= ' LIMIT ' . $items; } $query = $this->mysql->prepare($sql); $query->bindValue(':feed', $this->id); if ($query->execute()) { while ($row = $query->fetchColumn()) { $feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row); } } else { return false; } } } return $data; } return false; } /** * Retrieve the last modified time for the cache * * @return int Timestamp */ public function mtime() { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); $query->bindValue(':id', $this->id); if ($query->execute() && ($time = $query->fetchColumn())) { return $time; } else { return false; } } /** * Set the last modified time to the current time * * @return bool Success status */ public function touch() { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id'); $query->bindValue(':time', time()); $query->bindValue(':id', $this->id); if ($query->execute() && $query->rowCount() > 0) { return true; } else { return false; } } /** * Remove the cache * * @return bool Success status */ public function unlink() { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); $query->bindValue(':id', $this->id); $query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id'); $query2->bindValue(':id', $this->id); if ($query->execute() && $query2->execute()) { return true; } else { return false; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Cache.php ================================================ 'SimplePie_Cache_MySQL', 'memcache' => 'SimplePie_Cache_Memcache', ); /** * Don't call the constructor. Please. */ private function __construct() { } /** * Create a new SimplePie_Cache object * * @param string $location URL location (scheme is used to determine handler) * @param string $filename Unique identifier for cache object * @param string $extension 'spi' or 'spc' * @return SimplePie_Cache_Base Type of object depends on scheme of `$location` */ public static function get_handler($location, $filename, $extension) { $type = explode(':', $location, 2); $type = $type[0]; if (!empty(self::$handlers[$type])) { $class = self::$handlers[$type]; return new $class($location, $filename, $extension); } return new SimplePie_Cache_File($location, $filename, $extension); } /** * Create a new SimplePie_Cache object * * @deprecated Use {@see get_handler} instead */ public function create($location, $filename, $extension) { trigger_error('Cache::create() has been replaced with Cache::get_handler(). Switch to the registry system to use this.', E_USER_DEPRECATED); return self::get_handler($location, $filename, $extension); } /** * Register a handler * * @param string $type DSN type to register for * @param string $class Name of handler class. Must implement SimplePie_Cache_Base */ public static function register($type, $class) { self::$handlers[$type] = $class; } /** * Parse a URL into an array * * @param string $url * @return array */ public static function parse_URL($url) { $params = parse_url($url); $params['extras'] = array(); if (isset($params['query'])) { parse_str($params['query'], $params['extras']); } return $params; } } ================================================ FILE: libraries/simplepie/library/SimplePie/Caption.php ================================================ ` captions as defined in Media RSS. * * Used by {@see SimplePie_Enclosure::get_caption()} and {@see SimplePie_Enclosure::get_captions()} * * This class can be overloaded with {@see SimplePie::set_caption_class()} * * @package SimplePie * @subpackage API */ class SimplePie_Caption { /** * Content type * * @var string * @see get_type() */ var $type; /** * Language * * @var string * @see get_language() */ var $lang; /** * Start time * * @var string * @see get_starttime() */ var $startTime; /** * End time * * @var string * @see get_endtime() */ var $endTime; /** * Caption text * * @var string * @see get_text() */ var $text; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct($type = null, $lang = null, $startTime = null, $endTime = null, $text = null) { $this->type = $type; $this->lang = $lang; $this->startTime = $startTime; $this->endTime = $endTime; $this->text = $text; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the end time * * @return string|null Time in the format 'hh:mm:ss.SSS' */ public function get_endtime() { if ($this->endTime !== null) { return $this->endTime; } else { return null; } } /** * Get the language * * @link http://tools.ietf.org/html/rfc3066 * @return string|null Language code as per RFC 3066 */ public function get_language() { if ($this->lang !== null) { return $this->lang; } else { return null; } } /** * Get the start time * * @return string|null Time in the format 'hh:mm:ss.SSS' */ public function get_starttime() { if ($this->startTime !== null) { return $this->startTime; } else { return null; } } /** * Get the text of the caption * * @return string|null */ public function get_text() { if ($this->text !== null) { return $this->text; } else { return null; } } /** * Get the content type (not MIME type) * * @return string|null Either 'text' or 'html' */ public function get_type() { if ($this->type !== null) { return $this->type; } else { return null; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Category.php ================================================ term = $term; $this->scheme = $scheme; $this->label = $label; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the category identifier * * @return string|null */ public function get_term() { if ($this->term !== null) { return $this->term; } else { return null; } } /** * Get the categorization scheme identifier * * @return string|null */ public function get_scheme() { if ($this->scheme !== null) { return $this->scheme; } else { return null; } } /** * Get the human readable label * * @return string|null */ public function get_label() { if ($this->label !== null) { return $this->label; } else { return $this->get_term(); } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Content/Type/Sniffer.php ================================================ file = $file; } /** * Get the Content-Type of the specified file * * @return string Actual Content-Type */ public function get_type() { if (isset($this->file->headers['content-type'])) { if (!isset($this->file->headers['content-encoding']) && ($this->file->headers['content-type'] === 'text/plain' || $this->file->headers['content-type'] === 'text/plain; charset=ISO-8859-1' || $this->file->headers['content-type'] === 'text/plain; charset=iso-8859-1' || $this->file->headers['content-type'] === 'text/plain; charset=UTF-8')) { return $this->text_or_binary(); } if (($pos = strpos($this->file->headers['content-type'], ';')) !== false) { $official = substr($this->file->headers['content-type'], 0, $pos); } else { $official = $this->file->headers['content-type']; } $official = trim(strtolower($official)); if ($official === 'unknown/unknown' || $official === 'application/unknown') { return $this->unknown(); } elseif (substr($official, -4) === '+xml' || $official === 'text/xml' || $official === 'application/xml') { return $official; } elseif (substr($official, 0, 6) === 'image/') { if ($return = $this->image()) { return $return; } else { return $official; } } elseif ($official === 'text/html') { return $this->feed_or_html(); } else { return $official; } } else { return $this->unknown(); } } /** * Sniff text or binary * * @return string Actual Content-Type */ public function text_or_binary() { if (substr($this->file->body, 0, 2) === "\xFE\xFF" || substr($this->file->body, 0, 2) === "\xFF\xFE" || substr($this->file->body, 0, 4) === "\x00\x00\xFE\xFF" || substr($this->file->body, 0, 3) === "\xEF\xBB\xBF") { return 'text/plain'; } elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $this->file->body)) { return 'application/octect-stream'; } else { return 'text/plain'; } } /** * Sniff unknown * * @return string Actual Content-Type */ public function unknown() { $ws = strspn($this->file->body, "\x09\x0A\x0B\x0C\x0D\x20"); if (strtolower(substr($this->file->body, $ws, 14)) === 'file->body, $ws, 5)) === 'file->body, $ws, 7)) === 'file->body, 0, 5) === '%PDF-') { return 'application/pdf'; } elseif (substr($this->file->body, 0, 11) === '%!PS-Adobe-') { return 'application/postscript'; } elseif (substr($this->file->body, 0, 6) === 'GIF87a' || substr($this->file->body, 0, 6) === 'GIF89a') { return 'image/gif'; } elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { return 'image/png'; } elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF") { return 'image/jpeg'; } elseif (substr($this->file->body, 0, 2) === "\x42\x4D") { return 'image/bmp'; } elseif (substr($this->file->body, 0, 4) === "\x00\x00\x01\x00") { return 'image/vnd.microsoft.icon'; } else { return $this->text_or_binary(); } } /** * Sniff images * * @return string Actual Content-Type */ public function image() { if (substr($this->file->body, 0, 6) === 'GIF87a' || substr($this->file->body, 0, 6) === 'GIF89a') { return 'image/gif'; } elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { return 'image/png'; } elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF") { return 'image/jpeg'; } elseif (substr($this->file->body, 0, 2) === "\x42\x4D") { return 'image/bmp'; } elseif (substr($this->file->body, 0, 4) === "\x00\x00\x01\x00") { return 'image/vnd.microsoft.icon'; } else { return false; } } /** * Sniff HTML * * @return string Actual Content-Type */ public function feed_or_html() { $len = strlen($this->file->body); $pos = strspn($this->file->body, "\x09\x0A\x0D\x20"); while ($pos < $len) { switch ($this->file->body[$pos]) { case "\x09": case "\x0A": case "\x0D": case "\x20": $pos += strspn($this->file->body, "\x09\x0A\x0D\x20", $pos); continue 2; case '<': $pos++; break; default: return 'text/html'; } if (substr($this->file->body, $pos, 3) === '!--') { $pos += 3; if ($pos < $len && ($pos = strpos($this->file->body, '-->', $pos)) !== false) { $pos += 3; } else { return 'text/html'; } } elseif (substr($this->file->body, $pos, 1) === '!') { if ($pos < $len && ($pos = strpos($this->file->body, '>', $pos)) !== false) { $pos++; } else { return 'text/html'; } } elseif (substr($this->file->body, $pos, 1) === '?') { if ($pos < $len && ($pos = strpos($this->file->body, '?>', $pos)) !== false) { $pos += 2; } else { return 'text/html'; } } elseif (substr($this->file->body, $pos, 3) === 'rss' || substr($this->file->body, $pos, 7) === 'rdf:RDF') { return 'application/rss+xml'; } elseif (substr($this->file->body, $pos, 4) === 'feed') { return 'application/atom+xml'; } else { return 'text/html'; } } return 'text/html'; } } ================================================ FILE: libraries/simplepie/library/SimplePie/Copyright.php ================================================ ` copyright tags as defined in Media RSS * * Used by {@see SimplePie_Enclosure::get_copyright()} * * This class can be overloaded with {@see SimplePie::set_copyright_class()} * * @package SimplePie * @subpackage API */ class SimplePie_Copyright { /** * Copyright URL * * @var string * @see get_url() */ var $url; /** * Attribution * * @var string * @see get_attribution() */ var $label; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct($url = null, $label = null) { $this->url = $url; $this->label = $label; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the copyright URL * * @return string|null URL to copyright information */ public function get_url() { if ($this->url !== null) { return $this->url; } else { return null; } } /** * Get the attribution text * * @return string|null */ public function get_attribution() { if ($this->label !== null) { return $this->label; } else { return null; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Core.php ================================================ ` as defined in Media RSS * * Used by {@see SimplePie_Enclosure::get_credit()} and {@see SimplePie_Enclosure::get_credits()} * * This class can be overloaded with {@see SimplePie::set_credit_class()} * * @package SimplePie * @subpackage API */ class SimplePie_Credit { /** * Credited role * * @var string * @see get_role() */ var $role; /** * Organizational scheme * * @var string * @see get_scheme() */ var $scheme; /** * Credited name * * @var string * @see get_name() */ var $name; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct($role = null, $scheme = null, $name = null) { $this->role = $role; $this->scheme = $scheme; $this->name = $name; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the role of the person receiving credit * * @return string|null */ public function get_role() { if ($this->role !== null) { return $this->role; } else { return null; } } /** * Get the organizational scheme * * @return string|null */ public function get_scheme() { if ($this->scheme !== null) { return $this->scheme; } else { return null; } } /** * Get the credited person/entity's name * * @return string|null */ public function get_name() { if ($this->name !== null) { return $this->name; } else { return null; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Decode/HTML/Entities.php ================================================ data = $data; } /** * Parse the input data * * @access public * @return string Output data */ public function parse() { while (($this->position = strpos($this->data, '&', $this->position)) !== false) { $this->consume(); $this->entity(); $this->consumed = ''; } return $this->data; } /** * Consume the next byte * * @access private * @return mixed The next byte, or false, if there is no more data */ public function consume() { if (isset($this->data[$this->position])) { $this->consumed .= $this->data[$this->position]; return $this->data[$this->position++]; } else { return false; } } /** * Consume a range of characters * * @access private * @param string $chars Characters to consume * @return mixed A series of characters that match the range, or false */ public function consume_range($chars) { if ($len = strspn($this->data, $chars, $this->position)) { $data = substr($this->data, $this->position, $len); $this->consumed .= $data; $this->position += $len; return $data; } else { return false; } } /** * Unconsume one byte * * @access private */ public function unconsume() { $this->consumed = substr($this->consumed, 0, -1); $this->position--; } /** * Decode an entity * * @access private */ public function entity() { switch ($this->consume()) { case "\x09": case "\x0A": case "\x0B": case "\x0B": case "\x0C": case "\x20": case "\x3C": case "\x26": case false: break; case "\x23": switch ($this->consume()) { case "\x78": case "\x58": $range = '0123456789ABCDEFabcdef'; $hex = true; break; default: $range = '0123456789'; $hex = false; $this->unconsume(); break; } if ($codepoint = $this->consume_range($range)) { static $windows_1252_specials = array(0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99", 0x93 => "\xE2\x80\x9C", 0x94 => "\xE2\x80\x9D", 0x95 => "\xE2\x80\xA2", 0x96 => "\xE2\x80\x93", 0x97 => "\xE2\x80\x94", 0x98 => "\xCB\x9C", 0x99 => "\xE2\x84\xA2", 0x9A => "\xC5\xA1", 0x9B => "\xE2\x80\xBA", 0x9C => "\xC5\x93", 0x9D => "\xEF\xBF\xBD", 0x9E => "\xC5\xBE", 0x9F => "\xC5\xB8"); if ($hex) { $codepoint = hexdec($codepoint); } else { $codepoint = intval($codepoint); } if (isset($windows_1252_specials[$codepoint])) { $replacement = $windows_1252_specials[$codepoint]; } else { $replacement = SimplePie_Misc::codepoint_to_utf8($codepoint); } if (!in_array($this->consume(), array(';', false), true)) { $this->unconsume(); } $consumed_length = strlen($this->consumed); $this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length); $this->position += strlen($replacement) - $consumed_length; } break; default: static $entities = array( 'Aacute' => "\xC3\x81", 'aacute' => "\xC3\xA1", 'Aacute;' => "\xC3\x81", 'aacute;' => "\xC3\xA1", 'Acirc' => "\xC3\x82", 'acirc' => "\xC3\xA2", 'Acirc;' => "\xC3\x82", 'acirc;' => "\xC3\xA2", 'acute' => "\xC2\xB4", 'acute;' => "\xC2\xB4", 'AElig' => "\xC3\x86", 'aelig' => "\xC3\xA6", 'AElig;' => "\xC3\x86", 'aelig;' => "\xC3\xA6", 'Agrave' => "\xC3\x80", 'agrave' => "\xC3\xA0", 'Agrave;' => "\xC3\x80", 'agrave;' => "\xC3\xA0", 'alefsym;' => "\xE2\x84\xB5", 'Alpha;' => "\xCE\x91", 'alpha;' => "\xCE\xB1", 'AMP' => "\x26", 'amp' => "\x26", 'AMP;' => "\x26", 'amp;' => "\x26", 'and;' => "\xE2\x88\xA7", 'ang;' => "\xE2\x88\xA0", 'apos;' => "\x27", 'Aring' => "\xC3\x85", 'aring' => "\xC3\xA5", 'Aring;' => "\xC3\x85", 'aring;' => "\xC3\xA5", 'asymp;' => "\xE2\x89\x88", 'Atilde' => "\xC3\x83", 'atilde' => "\xC3\xA3", 'Atilde;' => "\xC3\x83", 'atilde;' => "\xC3\xA3", 'Auml' => "\xC3\x84", 'auml' => "\xC3\xA4", 'Auml;' => "\xC3\x84", 'auml;' => "\xC3\xA4", 'bdquo;' => "\xE2\x80\x9E", 'Beta;' => "\xCE\x92", 'beta;' => "\xCE\xB2", 'brvbar' => "\xC2\xA6", 'brvbar;' => "\xC2\xA6", 'bull;' => "\xE2\x80\xA2", 'cap;' => "\xE2\x88\xA9", 'Ccedil' => "\xC3\x87", 'ccedil' => "\xC3\xA7", 'Ccedil;' => "\xC3\x87", 'ccedil;' => "\xC3\xA7", 'cedil' => "\xC2\xB8", 'cedil;' => "\xC2\xB8", 'cent' => "\xC2\xA2", 'cent;' => "\xC2\xA2", 'Chi;' => "\xCE\xA7", 'chi;' => "\xCF\x87", 'circ;' => "\xCB\x86", 'clubs;' => "\xE2\x99\xA3", 'cong;' => "\xE2\x89\x85", 'COPY' => "\xC2\xA9", 'copy' => "\xC2\xA9", 'COPY;' => "\xC2\xA9", 'copy;' => "\xC2\xA9", 'crarr;' => "\xE2\x86\xB5", 'cup;' => "\xE2\x88\xAA", 'curren' => "\xC2\xA4", 'curren;' => "\xC2\xA4", 'Dagger;' => "\xE2\x80\xA1", 'dagger;' => "\xE2\x80\xA0", 'dArr;' => "\xE2\x87\x93", 'darr;' => "\xE2\x86\x93", 'deg' => "\xC2\xB0", 'deg;' => "\xC2\xB0", 'Delta;' => "\xCE\x94", 'delta;' => "\xCE\xB4", 'diams;' => "\xE2\x99\xA6", 'divide' => "\xC3\xB7", 'divide;' => "\xC3\xB7", 'Eacute' => "\xC3\x89", 'eacute' => "\xC3\xA9", 'Eacute;' => "\xC3\x89", 'eacute;' => "\xC3\xA9", 'Ecirc' => "\xC3\x8A", 'ecirc' => "\xC3\xAA", 'Ecirc;' => "\xC3\x8A", 'ecirc;' => "\xC3\xAA", 'Egrave' => "\xC3\x88", 'egrave' => "\xC3\xA8", 'Egrave;' => "\xC3\x88", 'egrave;' => "\xC3\xA8", 'empty;' => "\xE2\x88\x85", 'emsp;' => "\xE2\x80\x83", 'ensp;' => "\xE2\x80\x82", 'Epsilon;' => "\xCE\x95", 'epsilon;' => "\xCE\xB5", 'equiv;' => "\xE2\x89\xA1", 'Eta;' => "\xCE\x97", 'eta;' => "\xCE\xB7", 'ETH' => "\xC3\x90", 'eth' => "\xC3\xB0", 'ETH;' => "\xC3\x90", 'eth;' => "\xC3\xB0", 'Euml' => "\xC3\x8B", 'euml' => "\xC3\xAB", 'Euml;' => "\xC3\x8B", 'euml;' => "\xC3\xAB", 'euro;' => "\xE2\x82\xAC", 'exist;' => "\xE2\x88\x83", 'fnof;' => "\xC6\x92", 'forall;' => "\xE2\x88\x80", 'frac12' => "\xC2\xBD", 'frac12;' => "\xC2\xBD", 'frac14' => "\xC2\xBC", 'frac14;' => "\xC2\xBC", 'frac34' => "\xC2\xBE", 'frac34;' => "\xC2\xBE", 'frasl;' => "\xE2\x81\x84", 'Gamma;' => "\xCE\x93", 'gamma;' => "\xCE\xB3", 'ge;' => "\xE2\x89\xA5", 'GT' => "\x3E", 'gt' => "\x3E", 'GT;' => "\x3E", 'gt;' => "\x3E", 'hArr;' => "\xE2\x87\x94", 'harr;' => "\xE2\x86\x94", 'hearts;' => "\xE2\x99\xA5", 'hellip;' => "\xE2\x80\xA6", 'Iacute' => "\xC3\x8D", 'iacute' => "\xC3\xAD", 'Iacute;' => "\xC3\x8D", 'iacute;' => "\xC3\xAD", 'Icirc' => "\xC3\x8E", 'icirc' => "\xC3\xAE", 'Icirc;' => "\xC3\x8E", 'icirc;' => "\xC3\xAE", 'iexcl' => "\xC2\xA1", 'iexcl;' => "\xC2\xA1", 'Igrave' => "\xC3\x8C", 'igrave' => "\xC3\xAC", 'Igrave;' => "\xC3\x8C", 'igrave;' => "\xC3\xAC", 'image;' => "\xE2\x84\x91", 'infin;' => "\xE2\x88\x9E", 'int;' => "\xE2\x88\xAB", 'Iota;' => "\xCE\x99", 'iota;' => "\xCE\xB9", 'iquest' => "\xC2\xBF", 'iquest;' => "\xC2\xBF", 'isin;' => "\xE2\x88\x88", 'Iuml' => "\xC3\x8F", 'iuml' => "\xC3\xAF", 'Iuml;' => "\xC3\x8F", 'iuml;' => "\xC3\xAF", 'Kappa;' => "\xCE\x9A", 'kappa;' => "\xCE\xBA", 'Lambda;' => "\xCE\x9B", 'lambda;' => "\xCE\xBB", 'lang;' => "\xE3\x80\x88", 'laquo' => "\xC2\xAB", 'laquo;' => "\xC2\xAB", 'lArr;' => "\xE2\x87\x90", 'larr;' => "\xE2\x86\x90", 'lceil;' => "\xE2\x8C\x88", 'ldquo;' => "\xE2\x80\x9C", 'le;' => "\xE2\x89\xA4", 'lfloor;' => "\xE2\x8C\x8A", 'lowast;' => "\xE2\x88\x97", 'loz;' => "\xE2\x97\x8A", 'lrm;' => "\xE2\x80\x8E", 'lsaquo;' => "\xE2\x80\xB9", 'lsquo;' => "\xE2\x80\x98", 'LT' => "\x3C", 'lt' => "\x3C", 'LT;' => "\x3C", 'lt;' => "\x3C", 'macr' => "\xC2\xAF", 'macr;' => "\xC2\xAF", 'mdash;' => "\xE2\x80\x94", 'micro' => "\xC2\xB5", 'micro;' => "\xC2\xB5", 'middot' => "\xC2\xB7", 'middot;' => "\xC2\xB7", 'minus;' => "\xE2\x88\x92", 'Mu;' => "\xCE\x9C", 'mu;' => "\xCE\xBC", 'nabla;' => "\xE2\x88\x87", 'nbsp' => "\xC2\xA0", 'nbsp;' => "\xC2\xA0", 'ndash;' => "\xE2\x80\x93", 'ne;' => "\xE2\x89\xA0", 'ni;' => "\xE2\x88\x8B", 'not' => "\xC2\xAC", 'not;' => "\xC2\xAC", 'notin;' => "\xE2\x88\x89", 'nsub;' => "\xE2\x8A\x84", 'Ntilde' => "\xC3\x91", 'ntilde' => "\xC3\xB1", 'Ntilde;' => "\xC3\x91", 'ntilde;' => "\xC3\xB1", 'Nu;' => "\xCE\x9D", 'nu;' => "\xCE\xBD", 'Oacute' => "\xC3\x93", 'oacute' => "\xC3\xB3", 'Oacute;' => "\xC3\x93", 'oacute;' => "\xC3\xB3", 'Ocirc' => "\xC3\x94", 'ocirc' => "\xC3\xB4", 'Ocirc;' => "\xC3\x94", 'ocirc;' => "\xC3\xB4", 'OElig;' => "\xC5\x92", 'oelig;' => "\xC5\x93", 'Ograve' => "\xC3\x92", 'ograve' => "\xC3\xB2", 'Ograve;' => "\xC3\x92", 'ograve;' => "\xC3\xB2", 'oline;' => "\xE2\x80\xBE", 'Omega;' => "\xCE\xA9", 'omega;' => "\xCF\x89", 'Omicron;' => "\xCE\x9F", 'omicron;' => "\xCE\xBF", 'oplus;' => "\xE2\x8A\x95", 'or;' => "\xE2\x88\xA8", 'ordf' => "\xC2\xAA", 'ordf;' => "\xC2\xAA", 'ordm' => "\xC2\xBA", 'ordm;' => "\xC2\xBA", 'Oslash' => "\xC3\x98", 'oslash' => "\xC3\xB8", 'Oslash;' => "\xC3\x98", 'oslash;' => "\xC3\xB8", 'Otilde' => "\xC3\x95", 'otilde' => "\xC3\xB5", 'Otilde;' => "\xC3\x95", 'otilde;' => "\xC3\xB5", 'otimes;' => "\xE2\x8A\x97", 'Ouml' => "\xC3\x96", 'ouml' => "\xC3\xB6", 'Ouml;' => "\xC3\x96", 'ouml;' => "\xC3\xB6", 'para' => "\xC2\xB6", 'para;' => "\xC2\xB6", 'part;' => "\xE2\x88\x82", 'permil;' => "\xE2\x80\xB0", 'perp;' => "\xE2\x8A\xA5", 'Phi;' => "\xCE\xA6", 'phi;' => "\xCF\x86", 'Pi;' => "\xCE\xA0", 'pi;' => "\xCF\x80", 'piv;' => "\xCF\x96", 'plusmn' => "\xC2\xB1", 'plusmn;' => "\xC2\xB1", 'pound' => "\xC2\xA3", 'pound;' => "\xC2\xA3", 'Prime;' => "\xE2\x80\xB3", 'prime;' => "\xE2\x80\xB2", 'prod;' => "\xE2\x88\x8F", 'prop;' => "\xE2\x88\x9D", 'Psi;' => "\xCE\xA8", 'psi;' => "\xCF\x88", 'QUOT' => "\x22", 'quot' => "\x22", 'QUOT;' => "\x22", 'quot;' => "\x22", 'radic;' => "\xE2\x88\x9A", 'rang;' => "\xE3\x80\x89", 'raquo' => "\xC2\xBB", 'raquo;' => "\xC2\xBB", 'rArr;' => "\xE2\x87\x92", 'rarr;' => "\xE2\x86\x92", 'rceil;' => "\xE2\x8C\x89", 'rdquo;' => "\xE2\x80\x9D", 'real;' => "\xE2\x84\x9C", 'REG' => "\xC2\xAE", 'reg' => "\xC2\xAE", 'REG;' => "\xC2\xAE", 'reg;' => "\xC2\xAE", 'rfloor;' => "\xE2\x8C\x8B", 'Rho;' => "\xCE\xA1", 'rho;' => "\xCF\x81", 'rlm;' => "\xE2\x80\x8F", 'rsaquo;' => "\xE2\x80\xBA", 'rsquo;' => "\xE2\x80\x99", 'sbquo;' => "\xE2\x80\x9A", 'Scaron;' => "\xC5\xA0", 'scaron;' => "\xC5\xA1", 'sdot;' => "\xE2\x8B\x85", 'sect' => "\xC2\xA7", 'sect;' => "\xC2\xA7", 'shy' => "\xC2\xAD", 'shy;' => "\xC2\xAD", 'Sigma;' => "\xCE\xA3", 'sigma;' => "\xCF\x83", 'sigmaf;' => "\xCF\x82", 'sim;' => "\xE2\x88\xBC", 'spades;' => "\xE2\x99\xA0", 'sub;' => "\xE2\x8A\x82", 'sube;' => "\xE2\x8A\x86", 'sum;' => "\xE2\x88\x91", 'sup;' => "\xE2\x8A\x83", 'sup1' => "\xC2\xB9", 'sup1;' => "\xC2\xB9", 'sup2' => "\xC2\xB2", 'sup2;' => "\xC2\xB2", 'sup3' => "\xC2\xB3", 'sup3;' => "\xC2\xB3", 'supe;' => "\xE2\x8A\x87", 'szlig' => "\xC3\x9F", 'szlig;' => "\xC3\x9F", 'Tau;' => "\xCE\xA4", 'tau;' => "\xCF\x84", 'there4;' => "\xE2\x88\xB4", 'Theta;' => "\xCE\x98", 'theta;' => "\xCE\xB8", 'thetasym;' => "\xCF\x91", 'thinsp;' => "\xE2\x80\x89", 'THORN' => "\xC3\x9E", 'thorn' => "\xC3\xBE", 'THORN;' => "\xC3\x9E", 'thorn;' => "\xC3\xBE", 'tilde;' => "\xCB\x9C", 'times' => "\xC3\x97", 'times;' => "\xC3\x97", 'TRADE;' => "\xE2\x84\xA2", 'trade;' => "\xE2\x84\xA2", 'Uacute' => "\xC3\x9A", 'uacute' => "\xC3\xBA", 'Uacute;' => "\xC3\x9A", 'uacute;' => "\xC3\xBA", 'uArr;' => "\xE2\x87\x91", 'uarr;' => "\xE2\x86\x91", 'Ucirc' => "\xC3\x9B", 'ucirc' => "\xC3\xBB", 'Ucirc;' => "\xC3\x9B", 'ucirc;' => "\xC3\xBB", 'Ugrave' => "\xC3\x99", 'ugrave' => "\xC3\xB9", 'Ugrave;' => "\xC3\x99", 'ugrave;' => "\xC3\xB9", 'uml' => "\xC2\xA8", 'uml;' => "\xC2\xA8", 'upsih;' => "\xCF\x92", 'Upsilon;' => "\xCE\xA5", 'upsilon;' => "\xCF\x85", 'Uuml' => "\xC3\x9C", 'uuml' => "\xC3\xBC", 'Uuml;' => "\xC3\x9C", 'uuml;' => "\xC3\xBC", 'weierp;' => "\xE2\x84\x98", 'Xi;' => "\xCE\x9E", 'xi;' => "\xCE\xBE", 'Yacute' => "\xC3\x9D", 'yacute' => "\xC3\xBD", 'Yacute;' => "\xC3\x9D", 'yacute;' => "\xC3\xBD", 'yen' => "\xC2\xA5", 'yen;' => "\xC2\xA5", 'yuml' => "\xC3\xBF", 'Yuml;' => "\xC5\xB8", 'yuml;' => "\xC3\xBF", 'Zeta;' => "\xCE\x96", 'zeta;' => "\xCE\xB6", 'zwj;' => "\xE2\x80\x8D", 'zwnj;' => "\xE2\x80\x8C" ); for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++) { $consumed = substr($this->consumed, 1); if (isset($entities[$consumed])) { $match = $consumed; } } if ($match !== null) { $this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1); $this->position += strlen($entities[$match]) - strlen($consumed) - 1; } break; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Enclosure.php ================================================ bitrate = $bitrate; $this->captions = $captions; $this->categories = $categories; $this->channels = $channels; $this->copyright = $copyright; $this->credits = $credits; $this->description = $description; $this->duration = $duration; $this->expression = $expression; $this->framerate = $framerate; $this->hashes = $hashes; $this->height = $height; $this->keywords = $keywords; $this->lang = $lang; $this->length = $length; $this->link = $link; $this->medium = $medium; $this->player = $player; $this->ratings = $ratings; $this->restrictions = $restrictions; $this->samplingrate = $samplingrate; $this->thumbnails = $thumbnails; $this->title = $title; $this->type = $type; $this->width = $width; if (class_exists('idna_convert')) { $idn = new idna_convert(); $parsed = SimplePie_Misc::parse_url($link); $this->link = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); } $this->handler = $this->get_handler(); // Needs to load last } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the bitrate * * @return string|null */ public function get_bitrate() { if ($this->bitrate !== null) { return $this->bitrate; } else { return null; } } /** * Get a single caption * * @param int $key * @return SimplePie_Caption|null */ public function get_caption($key = 0) { $captions = $this->get_captions(); if (isset($captions[$key])) { return $captions[$key]; } else { return null; } } /** * Get all captions * * @return array|null Array of {@see SimplePie_Caption} objects */ public function get_captions() { if ($this->captions !== null) { return $this->captions; } else { return null; } } /** * Get a single category * * @param int $key * @return SimplePie_Category|null */ public function get_category($key = 0) { $categories = $this->get_categories(); if (isset($categories[$key])) { return $categories[$key]; } else { return null; } } /** * Get all categories * * @return array|null Array of {@see SimplePie_Category} objects */ public function get_categories() { if ($this->categories !== null) { return $this->categories; } else { return null; } } /** * Get the number of audio channels * * @return int|null */ public function get_channels() { if ($this->channels !== null) { return $this->channels; } else { return null; } } /** * Get the copyright information * * @return SimplePie_Copyright|null */ public function get_copyright() { if ($this->copyright !== null) { return $this->copyright; } else { return null; } } /** * Get a single credit * * @param int $key * @return SimplePie_Credit|null */ public function get_credit($key = 0) { $credits = $this->get_credits(); if (isset($credits[$key])) { return $credits[$key]; } else { return null; } } /** * Get all credits * * @return array|null Array of {@see SimplePie_Credit} objects */ public function get_credits() { if ($this->credits !== null) { return $this->credits; } else { return null; } } /** * Get the description of the enclosure * * @return string|null */ public function get_description() { if ($this->description !== null) { return $this->description; } else { return null; } } /** * Get the duration of the enclosure * * @param string $convert Convert seconds into hh:mm:ss * @return string|int|null 'hh:mm:ss' string if `$convert` was specified, otherwise integer (or null if none found) */ public function get_duration($convert = false) { if ($this->duration !== null) { if ($convert) { $time = SimplePie_Misc::time_hms($this->duration); return $time; } else { return $this->duration; } } else { return null; } } /** * Get the expression * * @return string Probably one of 'sample', 'full', 'nonstop', 'clip'. Defaults to 'full' */ public function get_expression() { if ($this->expression !== null) { return $this->expression; } else { return 'full'; } } /** * Get the file extension * * @return string|null */ public function get_extension() { if ($this->link !== null) { $url = SimplePie_Misc::parse_url($this->link); if ($url['path'] !== '') { return pathinfo($url['path'], PATHINFO_EXTENSION); } } return null; } /** * Get the framerate (in frames-per-second) * * @return string|null */ public function get_framerate() { if ($this->framerate !== null) { return $this->framerate; } else { return null; } } /** * Get the preferred handler * * @return string|null One of 'flash', 'fmedia', 'quicktime', 'wmedia', 'mp3' */ public function get_handler() { return $this->get_real_type(true); } /** * Get a single hash * * @link http://www.rssboard.org/media-rss#media-hash * @param int $key * @return string|null Hash as per `media:hash`, prefixed with "$algo:" */ public function get_hash($key = 0) { $hashes = $this->get_hashes(); if (isset($hashes[$key])) { return $hashes[$key]; } else { return null; } } /** * Get all credits * * @return array|null Array of strings, see {@see get_hash()} */ public function get_hashes() { if ($this->hashes !== null) { return $this->hashes; } else { return null; } } /** * Get the height * * @return string|null */ public function get_height() { if ($this->height !== null) { return $this->height; } else { return null; } } /** * Get the language * * @link http://tools.ietf.org/html/rfc3066 * @return string|null Language code as per RFC 3066 */ public function get_language() { if ($this->lang !== null) { return $this->lang; } else { return null; } } /** * Get a single keyword * * @param int $key * @return string|null */ public function get_keyword($key = 0) { $keywords = $this->get_keywords(); if (isset($keywords[$key])) { return $keywords[$key]; } else { return null; } } /** * Get all keywords * * @return array|null Array of strings */ public function get_keywords() { if ($this->keywords !== null) { return $this->keywords; } else { return null; } } /** * Get length * * @return float Length in bytes */ public function get_length() { if ($this->length !== null) { return $this->length; } else { return null; } } /** * Get the URL * * @return string|null */ public function get_link() { if ($this->link !== null) { return urldecode($this->link); } else { return null; } } /** * Get the medium * * @link http://www.rssboard.org/media-rss#media-content * @return string|null Should be one of 'image', 'audio', 'video', 'document', 'executable' */ public function get_medium() { if ($this->medium !== null) { return $this->medium; } else { return null; } } /** * Get the player URL * * Typically the same as {@see get_permalink()} * @return string|null Player URL */ public function get_player() { if ($this->player !== null) { return $this->player; } else { return null; } } /** * Get a single rating * * @param int $key * @return SimplePie_Rating|null */ public function get_rating($key = 0) { $ratings = $this->get_ratings(); if (isset($ratings[$key])) { return $ratings[$key]; } else { return null; } } /** * Get all ratings * * @return array|null Array of {@see SimplePie_Rating} objects */ public function get_ratings() { if ($this->ratings !== null) { return $this->ratings; } else { return null; } } /** * Get a single restriction * * @param int $key * @return SimplePie_Restriction|null */ public function get_restriction($key = 0) { $restrictions = $this->get_restrictions(); if (isset($restrictions[$key])) { return $restrictions[$key]; } else { return null; } } /** * Get all restrictions * * @return array|null Array of {@see SimplePie_Restriction} objects */ public function get_restrictions() { if ($this->restrictions !== null) { return $this->restrictions; } else { return null; } } /** * Get the sampling rate (in kHz) * * @return string|null */ public function get_sampling_rate() { if ($this->samplingrate !== null) { return $this->samplingrate; } else { return null; } } /** * Get the file size (in MiB) * * @return float|null File size in mebibytes (1048 bytes) */ public function get_size() { $length = $this->get_length(); if ($length !== null) { return round($length/1048576, 2); } else { return null; } } /** * Get a single thumbnail * * @param int $key * @return string|null Thumbnail URL */ public function get_thumbnail($key = 0) { $thumbnails = $this->get_thumbnails(); if (isset($thumbnails[$key])) { return $thumbnails[$key]; } else { return null; } } /** * Get all thumbnails * * @return array|null Array of thumbnail URLs */ public function get_thumbnails() { if ($this->thumbnails !== null) { return $this->thumbnails; } else { return null; } } /** * Get the title * * @return string|null */ public function get_title() { if ($this->title !== null) { return $this->title; } else { return null; } } /** * Get mimetype of the enclosure * * @see get_real_type() * @return string|null MIME type */ public function get_type() { if ($this->type !== null) { return $this->type; } else { return null; } } /** * Get the width * * @return string|null */ public function get_width() { if ($this->width !== null) { return $this->width; } else { return null; } } /** * Embed the enclosure using `` * * @deprecated Use the second parameter to {@see embed} instead * * @param array|string $options See first paramter to {@see embed} * @return string HTML string to output */ public function native_embed($options='') { return $this->embed($options, true); } /** * Embed the enclosure using Javascript * * `$options` is an array or comma-separated key:value string, with the * following properties: * * - `alt` (string): Alternate content for when an end-user does not have * the appropriate handler installed or when a file type is * unsupported. Can be any text or HTML. Defaults to blank. * - `altclass` (string): If a file type is unsupported, the end-user will * see the alt text (above) linked directly to the content. That link * will have this value as its class name. Defaults to blank. * - `audio` (string): This is an image that should be used as a * placeholder for audio files before they're loaded (QuickTime-only). * Can be any relative or absolute URL. Defaults to blank. * - `bgcolor` (string): The background color for the media, if not * already transparent. Defaults to `#ffffff`. * - `height` (integer): The height of the embedded media. Accepts any * numeric pixel value (such as `360`) or `auto`. Defaults to `auto`, * and it is recommended that you use this default. * - `loop` (boolean): Do you want the media to loop when its done? * Defaults to `false`. * - `mediaplayer` (string): The location of the included * `mediaplayer.swf` file. This allows for the playback of Flash Video * (`.flv`) files, and is the default handler for non-Odeo MP3's. * Defaults to blank. * - `video` (string): This is an image that should be used as a * placeholder for video files before they're loaded (QuickTime-only). * Can be any relative or absolute URL. Defaults to blank. * - `width` (integer): The width of the embedded media. Accepts any * numeric pixel value (such as `480`) or `auto`. Defaults to `auto`, * and it is recommended that you use this default. * - `widescreen` (boolean): Is the enclosure widescreen or standard? * This applies only to video enclosures, and will automatically resize * the content appropriately. Defaults to `false`, implying 4:3 mode. * * Note: Non-widescreen (4:3) mode with `width` and `height` set to `auto` * will default to 480x360 video resolution. Widescreen (16:9) mode with * `width` and `height` set to `auto` will default to 480x270 video resolution. * * @todo If the dimensions for media:content are defined, use them when width/height are set to 'auto'. * @param array|string $options Comma-separated key:value list, or array * @param bool $native Use `` * @return string HTML string to output */ public function embed($options = '', $native = false) { // Set up defaults $audio = ''; $video = ''; $alt = ''; $altclass = ''; $loop = 'false'; $width = 'auto'; $height = 'auto'; $bgcolor = '#ffffff'; $mediaplayer = ''; $widescreen = false; $handler = $this->get_handler(); $type = $this->get_real_type(); // Process options and reassign values as necessary if (is_array($options)) { extract($options); } else { $options = explode(',', $options); foreach($options as $option) { $opt = explode(':', $option, 2); if (isset($opt[0], $opt[1])) { $opt[0] = trim($opt[0]); $opt[1] = trim($opt[1]); switch ($opt[0]) { case 'audio': $audio = $opt[1]; break; case 'video': $video = $opt[1]; break; case 'alt': $alt = $opt[1]; break; case 'altclass': $altclass = $opt[1]; break; case 'loop': $loop = $opt[1]; break; case 'width': $width = $opt[1]; break; case 'height': $height = $opt[1]; break; case 'bgcolor': $bgcolor = $opt[1]; break; case 'mediaplayer': $mediaplayer = $opt[1]; break; case 'widescreen': $widescreen = $opt[1]; break; } } } } $mime = explode('/', $type, 2); $mime = $mime[0]; // Process values for 'auto' if ($width === 'auto') { if ($mime === 'video') { if ($height === 'auto') { $width = 480; } elseif ($widescreen) { $width = round((intval($height)/9)*16); } else { $width = round((intval($height)/3)*4); } } else { $width = '100%'; } } if ($height === 'auto') { if ($mime === 'audio') { $height = 0; } elseif ($mime === 'video') { if ($width === 'auto') { if ($widescreen) { $height = 270; } else { $height = 360; } } elseif ($widescreen) { $height = round((intval($width)/16)*9); } else { $height = round((intval($width)/4)*3); } } else { $height = 376; } } elseif ($mime === 'audio') { $height = 0; } // Set proper placeholder value if ($mime === 'audio') { $placeholder = $audio; } elseif ($mime === 'video') { $placeholder = $video; } $embed = ''; // Flash if ($handler === 'flash') { if ($native) { $embed .= "get_link() . "\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\">"; } else { $embed .= ""; } } // Flash Media Player file types. // Preferred handler for MP3 file types. elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== '')) { $height += 20; if ($native) { $embed .= "get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\">"; } else { $embed .= ""; } } // QuickTime 7 file types. Need to test with QuickTime 6. // Only handle MP3's if the Flash Media Player is not present. elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === '')) { $height += 16; if ($native) { if ($placeholder !== '') { $embed .= "get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\">"; } else { $embed .= "get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\">"; } } else { $embed .= ""; } } // Windows Media elseif ($handler === 'wmedia') { $height += 45; if ($native) { $embed .= "get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\">"; } else { $embed .= ""; } } // Everything else else $embed .= '' . $alt . ''; return $embed; } /** * Get the real media type * * Often, feeds lie to us, necessitating a bit of deeper inspection. This * converts types to their canonical representations based on the file * extension * * @see get_type() * @param bool $find_handler Internal use only, use {@see get_handler()} instead * @return string MIME type */ public function get_real_type($find_handler = false) { // Mime-types by handler. $types_flash = array('application/x-shockwave-flash', 'application/futuresplash'); // Flash $types_fmedia = array('video/flv', 'video/x-flv','flv-application/octet-stream'); // Flash Media Player $types_quicktime = array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video'); // QuickTime $types_wmedia = array('application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'); // Windows Media $types_mp3 = array('audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg'); // MP3 if ($this->get_type() !== null) { $type = strtolower($this->type); } else { $type = null; } // If we encounter an unsupported mime-type, check the file extension and guess intelligently. if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3))) { switch (strtolower($this->get_extension())) { // Audio mime-types case 'aac': case 'adts': $type = 'audio/acc'; break; case 'aif': case 'aifc': case 'aiff': case 'cdda': $type = 'audio/aiff'; break; case 'bwf': $type = 'audio/wav'; break; case 'kar': case 'mid': case 'midi': case 'smf': $type = 'audio/midi'; break; case 'm4a': $type = 'audio/x-m4a'; break; case 'mp3': case 'swa': $type = 'audio/mp3'; break; case 'wav': $type = 'audio/wav'; break; case 'wax': $type = 'audio/x-ms-wax'; break; case 'wma': $type = 'audio/x-ms-wma'; break; // Video mime-types case '3gp': case '3gpp': $type = 'video/3gpp'; break; case '3g2': case '3gp2': $type = 'video/3gpp2'; break; case 'asf': $type = 'video/x-ms-asf'; break; case 'flv': $type = 'video/x-flv'; break; case 'm1a': case 'm1s': case 'm1v': case 'm15': case 'm75': case 'mp2': case 'mpa': case 'mpeg': case 'mpg': case 'mpm': case 'mpv': $type = 'video/mpeg'; break; case 'm4v': $type = 'video/x-m4v'; break; case 'mov': case 'qt': $type = 'video/quicktime'; break; case 'mp4': case 'mpg4': $type = 'video/mp4'; break; case 'sdv': $type = 'video/sd-video'; break; case 'wm': $type = 'video/x-ms-wm'; break; case 'wmv': $type = 'video/x-ms-wmv'; break; case 'wvx': $type = 'video/x-ms-wvx'; break; // Flash mime-types case 'spl': $type = 'application/futuresplash'; break; case 'swf': $type = 'application/x-shockwave-flash'; break; } } if ($find_handler) { if (in_array($type, $types_flash)) { return 'flash'; } elseif (in_array($type, $types_fmedia)) { return 'fmedia'; } elseif (in_array($type, $types_quicktime)) { return 'quicktime'; } elseif (in_array($type, $types_wmedia)) { return 'wmedia'; } elseif (in_array($type, $types_mp3)) { return 'mp3'; } else { return null; } } else { return $type; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Exception.php ================================================ encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); } $this->url = $url; $this->useragent = $useragent; if (preg_match('/^http(s)?:\/\//i', $url)) { if ($useragent === null) { $useragent = ini_get('user_agent'); $this->useragent = $useragent; } if (!is_array($headers)) { $headers = array(); } if (!$force_fsockopen && function_exists('curl_exec')) { $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL; $fp = curl_init(); $headers2 = array(); foreach ($headers as $key => $value) { $headers2[] = "$key: $value"; } if (version_compare(SimplePie_Misc::get_curl_version(), '7.10.5', '>=')) { curl_setopt($fp, CURLOPT_ENCODING, ''); } curl_setopt($fp, CURLOPT_URL, $url); curl_setopt($fp, CURLOPT_HEADER, 1); curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1); curl_setopt($fp, CURLOPT_TIMEOUT, $timeout); curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($fp, CURLOPT_REFERER, $url); curl_setopt($fp, CURLOPT_USERAGENT, $useragent); curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>=')) { curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects); } $this->headers = curl_exec($fp); if (curl_errno($fp) === 23 || curl_errno($fp) === 61) { curl_setopt($fp, CURLOPT_ENCODING, 'none'); $this->headers = curl_exec($fp); } if (curl_errno($fp)) { $this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); $this->success = false; } else { $info = curl_getinfo($fp); curl_close($fp); $this->headers = explode("\r\n\r\n", $this->headers, $info['redirect_count'] + 1); $this->headers = array_pop($this->headers); $parser = new SimplePie_HTTP_Parser($this->headers); if ($parser->parse()) { $this->headers = $parser->headers; $this->body = $parser->body; $this->status_code = $parser->status_code; if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) { $this->redirects++; $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); } } } } else { $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN; $url_parts = parse_url($url); $socket_host = $url_parts['host']; if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { $socket_host = "ssl://$url_parts[host]"; $url_parts['port'] = 443; } if (!isset($url_parts['port'])) { $url_parts['port'] = 80; } $fp = @fsockopen($socket_host, $url_parts['port'], $errno, $errstr, $timeout); if (!$fp) { $this->error = 'fsockopen error: ' . $errstr; $this->success = false; } else { stream_set_timeout($fp, $timeout); if (isset($url_parts['path'])) { if (isset($url_parts['query'])) { $get = "$url_parts[path]?$url_parts[query]"; } else { $get = $url_parts['path']; } } else { $get = '/'; } $out = "GET $get HTTP/1.1\r\n"; $out .= "Host: $url_parts[host]\r\n"; $out .= "User-Agent: $useragent\r\n"; if (extension_loaded('zlib')) { $out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n"; } if (isset($url_parts['user']) && isset($url_parts['pass'])) { $out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n"; } foreach ($headers as $key => $value) { $out .= "$key: $value\r\n"; } $out .= "Connection: Close\r\n\r\n"; fwrite($fp, $out); $info = stream_get_meta_data($fp); $this->headers = ''; while (!$info['eof'] && !$info['timed_out']) { $this->headers .= fread($fp, 1160); $info = stream_get_meta_data($fp); } if (!$info['timed_out']) { $parser = new SimplePie_HTTP_Parser($this->headers); if ($parser->parse()) { $this->headers = $parser->headers; $this->body = $parser->body; $this->status_code = $parser->status_code; if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) { $this->redirects++; $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); } if (isset($this->headers['content-encoding'])) { // Hey, we act dumb elsewhere, so let's do that here too switch (strtolower(trim($this->headers['content-encoding'], "\x09\x0A\x0D\x20"))) { case 'gzip': case 'x-gzip': $decoder = new SimplePie_gzdecode($this->body); if (!$decoder->parse()) { $this->error = 'Unable to decode HTTP "gzip" stream'; $this->success = false; } else { $this->body = $decoder->data; } break; case 'deflate': if (($decompressed = gzinflate($this->body)) !== false) { $this->body = $decompressed; } else if (($decompressed = gzuncompress($this->body)) !== false) { $this->body = $decompressed; } else if (function_exists('gzdecode') && ($decompressed = gzdecode($this->body)) !== false) { $this->body = $decompressed; } else { $this->error = 'Unable to decode HTTP "deflate" stream'; $this->success = false; } break; default: $this->error = 'Unknown content coding'; $this->success = false; } } } } else { $this->error = 'fsocket timed out'; $this->success = false; } fclose($fp); } } } else { $this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS; if (!$this->body = file_get_contents($url)) { $this->error = 'file_get_contents could not read the file'; $this->success = false; } } } } ================================================ FILE: libraries/simplepie/library/SimplePie/HTTP/Parser.php ================================================ data = $data; $this->data_length = strlen($this->data); } /** * Parse the input data * * @return bool true on success, false on failure */ public function parse() { while ($this->state && $this->state !== 'emit' && $this->has_data()) { $state = $this->state; $this->$state(); } $this->data = ''; if ($this->state === 'emit' || $this->state === 'body') { return true; } else { $this->http_version = ''; $this->status_code = ''; $this->reason = ''; $this->headers = array(); $this->body = ''; return false; } } /** * Check whether there is data beyond the pointer * * @return bool true if there is further data, false if not */ protected function has_data() { return (bool) ($this->position < $this->data_length); } /** * See if the next character is LWS * * @return bool true if the next character is LWS, false if not */ protected function is_linear_whitespace() { return (bool) ($this->data[$this->position] === "\x09" || $this->data[$this->position] === "\x20" || ($this->data[$this->position] === "\x0A" && isset($this->data[$this->position + 1]) && ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20"))); } /** * Parse the HTTP version */ protected function http_version() { if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/') { $len = strspn($this->data, '0123456789.', 5); $this->http_version = substr($this->data, 5, $len); $this->position += 5 + $len; if (substr_count($this->http_version, '.') <= 1) { $this->http_version = (float) $this->http_version; $this->position += strspn($this->data, "\x09\x20", $this->position); $this->state = 'status'; } else { $this->state = false; } } else { $this->state = false; } } /** * Parse the status code */ protected function status() { if ($len = strspn($this->data, '0123456789', $this->position)) { $this->status_code = (int) substr($this->data, $this->position, $len); $this->position += $len; $this->state = 'reason'; } else { $this->state = false; } } /** * Parse the reason phrase */ protected function reason() { $len = strcspn($this->data, "\x0A", $this->position); $this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20"); $this->position += $len + 1; $this->state = 'new_line'; } /** * Deal with a new line, shifting data around as needed */ protected function new_line() { $this->value = trim($this->value, "\x0D\x20"); if ($this->name !== '' && $this->value !== '') { $this->name = strtolower($this->name); // We should only use the last Content-Type header. c.f. issue #1 if (isset($this->headers[$this->name]) && $this->name !== 'content-type') { $this->headers[$this->name] .= ', ' . $this->value; } else { $this->headers[$this->name] = $this->value; } } $this->name = ''; $this->value = ''; if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A") { $this->position += 2; $this->state = 'body'; } elseif ($this->data[$this->position] === "\x0A") { $this->position++; $this->state = 'body'; } else { $this->state = 'name'; } } /** * Parse a header name */ protected function name() { $len = strcspn($this->data, "\x0A:", $this->position); if (isset($this->data[$this->position + $len])) { if ($this->data[$this->position + $len] === "\x0A") { $this->position += $len; $this->state = 'new_line'; } else { $this->name = substr($this->data, $this->position, $len); $this->position += $len + 1; $this->state = 'value'; } } else { $this->state = false; } } /** * Parse LWS, replacing consecutive LWS characters with a single space */ protected function linear_whitespace() { do { if (substr($this->data, $this->position, 2) === "\x0D\x0A") { $this->position += 2; } elseif ($this->data[$this->position] === "\x0A") { $this->position++; } $this->position += strspn($this->data, "\x09\x20", $this->position); } while ($this->has_data() && $this->is_linear_whitespace()); $this->value .= "\x20"; } /** * See what state to move to while within non-quoted header values */ protected function value() { if ($this->is_linear_whitespace()) { $this->linear_whitespace(); } else { switch ($this->data[$this->position]) { case '"': // Workaround for ETags: we have to include the quotes as // part of the tag. if (strtolower($this->name) === 'etag') { $this->value .= '"'; $this->position++; $this->state = 'value_char'; break; } $this->position++; $this->state = 'quote'; break; case "\x0A": $this->position++; $this->state = 'new_line'; break; default: $this->state = 'value_char'; break; } } } /** * Parse a header value while outside quotes */ protected function value_char() { $len = strcspn($this->data, "\x09\x20\x0A\"", $this->position); $this->value .= substr($this->data, $this->position, $len); $this->position += $len; $this->state = 'value'; } /** * See what state to move to while within quoted header values */ protected function quote() { if ($this->is_linear_whitespace()) { $this->linear_whitespace(); } else { switch ($this->data[$this->position]) { case '"': $this->position++; $this->state = 'value'; break; case "\x0A": $this->position++; $this->state = 'new_line'; break; case '\\': $this->position++; $this->state = 'quote_escaped'; break; default: $this->state = 'quote_char'; break; } } } /** * Parse a header value while within quotes */ protected function quote_char() { $len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position); $this->value .= substr($this->data, $this->position, $len); $this->position += $len; $this->state = 'value'; } /** * Parse an escaped character within quotes */ protected function quote_escaped() { $this->value .= $this->data[$this->position]; $this->position++; $this->state = 'quote'; } /** * Parse the body */ protected function body() { $this->body = substr($this->data, $this->position); if (!empty($this->headers['transfer-encoding'])) { unset($this->headers['transfer-encoding']); $this->state = 'chunked'; } else { $this->state = 'emit'; } } /** * Parsed a "Transfer-Encoding: chunked" body */ protected function chunked() { if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($this->body))) { $this->state = 'emit'; return; } $decoded = ''; $encoded = $this->body; while (true) { $is_chunked = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches ); if (!$is_chunked) { // Looks like it's not chunked after all $this->state = 'emit'; return; } $length = hexdec(trim($matches[1])); if ($length === 0) { // Ignore trailer headers $this->state = 'emit'; $this->body = $decoded; return; } $chunk_length = strlen($matches[0]); $decoded .= $part = substr($encoded, $chunk_length, $length); $encoded = substr($encoded, $chunk_length + $length + 2); if (trim($encoded) === '0' || empty($encoded)) { $this->state = 'emit'; $this->body = $decoded; return; } } } } ================================================ FILE: libraries/simplepie/library/SimplePie/IRI.php ================================================ array( 'port' => 674 ), 'dict' => array( 'port' => 2628 ), 'file' => array( 'ihost' => 'localhost' ), 'http' => array( 'port' => 80, 'ipath' => '/' ), 'https' => array( 'port' => 443, 'ipath' => '/' ), ); /** * Return the entire IRI when you try and read the object as a string * * @return string */ public function __toString() { return $this->get_iri(); } /** * Overload __set() to provide access via properties * * @param string $name Property name * @param mixed $value Property value */ public function __set($name, $value) { if (method_exists($this, 'set_' . $name)) { call_user_func(array($this, 'set_' . $name), $value); } elseif ( $name === 'iauthority' || $name === 'iuserinfo' || $name === 'ihost' || $name === 'ipath' || $name === 'iquery' || $name === 'ifragment' ) { call_user_func(array($this, 'set_' . substr($name, 1)), $value); } } /** * Overload __get() to provide access via properties * * @param string $name Property name * @return mixed */ public function __get($name) { // isset() returns false for null, we don't want to do that // Also why we use array_key_exists below instead of isset() $props = get_object_vars($this); if ( $name === 'iri' || $name === 'uri' || $name === 'iauthority' || $name === 'authority' ) { $return = $this->{"get_$name"}(); } elseif (array_key_exists($name, $props)) { $return = $this->$name; } // host -> ihost elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } // ischeme -> scheme elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } else { trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); $return = null; } if ($return === null && isset($this->normalization[$this->scheme][$name])) { return $this->normalization[$this->scheme][$name]; } else { return $return; } } /** * Overload __isset() to provide access via properties * * @param string $name Property name * @return bool */ public function __isset($name) { if (method_exists($this, 'get_' . $name) || isset($this->$name)) { return true; } else { return false; } } /** * Overload __unset() to provide access via properties * * @param string $name Property name */ public function __unset($name) { if (method_exists($this, 'set_' . $name)) { call_user_func(array($this, 'set_' . $name), ''); } } /** * Create a new IRI object, from a specified string * * @param string $iri */ public function __construct($iri = null) { $this->set_iri($iri); } /** * Create a new IRI object by resolving a relative IRI * * Returns false if $base is not absolute, otherwise an IRI. * * @param IRI|string $base (Absolute) Base IRI * @param IRI|string $relative Relative IRI * @return IRI|false */ public static function absolutize($base, $relative) { if (!($relative instanceof SimplePie_IRI)) { $relative = new SimplePie_IRI($relative); } if (!$relative->is_valid()) { return false; } elseif ($relative->scheme !== null) { return clone $relative; } else { if (!($base instanceof SimplePie_IRI)) { $base = new SimplePie_IRI($base); } if ($base->scheme !== null && $base->is_valid()) { if ($relative->get_iri() !== '') { if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) { $target = clone $relative; $target->scheme = $base->scheme; } else { $target = new SimplePie_IRI; $target->scheme = $base->scheme; $target->iuserinfo = $base->iuserinfo; $target->ihost = $base->ihost; $target->port = $base->port; if ($relative->ipath !== '') { if ($relative->ipath[0] === '/') { $target->ipath = $relative->ipath; } elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') { $target->ipath = '/' . $relative->ipath; } elseif (($last_segment = strrpos($base->ipath, '/')) !== false) { $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; } else { $target->ipath = $relative->ipath; } $target->ipath = $target->remove_dot_segments($target->ipath); $target->iquery = $relative->iquery; } else { $target->ipath = $base->ipath; if ($relative->iquery !== null) { $target->iquery = $relative->iquery; } elseif ($base->iquery !== null) { $target->iquery = $base->iquery; } } $target->ifragment = $relative->ifragment; } } else { $target = clone $base; $target->ifragment = null; } $target->scheme_normalization(); return $target; } else { return false; } } } /** * Parse an IRI into scheme/authority/path/query/fragment segments * * @param string $iri * @return array */ protected function parse_iri($iri) { $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); if (preg_match('/^((?P[^:\/?#]+):)?(\/\/(?P[^\/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?$/', $iri, $match)) { if ($match[1] === '') { $match['scheme'] = null; } if (!isset($match[3]) || $match[3] === '') { $match['authority'] = null; } if (!isset($match[5])) { $match['path'] = ''; } if (!isset($match[6]) || $match[6] === '') { $match['query'] = null; } if (!isset($match[8]) || $match[8] === '') { $match['fragment'] = null; } return $match; } else { // This can occur when a paragraph is accidentally parsed as a URI return false; } } /** * Remove dot segments from a path * * @param string $input * @return string */ protected function remove_dot_segments($input) { $output = ''; while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') { // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, if (strpos($input, '../') === 0) { $input = substr($input, 3); } elseif (strpos($input, './') === 0) { $input = substr($input, 2); } // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, elseif (strpos($input, '/./') === 0) { $input = substr($input, 2); } elseif ($input === '/.') { $input = '/'; } // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, elseif (strpos($input, '/../') === 0) { $input = substr($input, 3); $output = substr_replace($output, '', strrpos($output, '/')); } elseif ($input === '/..') { $input = '/'; $output = substr_replace($output, '', strrpos($output, '/')); } // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, elseif ($input === '.' || $input === '..') { $input = ''; } // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer elseif (($pos = strpos($input, '/', 1)) !== false) { $output .= substr($input, 0, $pos); $input = substr_replace($input, '', 0, $pos); } else { $output .= $input; $input = ''; } } return $output . $input; } /** * Replace invalid character with percent encoding * * @param string $string Input string * @param string $extra_chars Valid characters not in iunreserved or * iprivate (this is ASCII-only) * @param bool $iprivate Allow iprivate * @return string */ protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) { // Normalize as many pct-encoded sections as possible $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $string); // Replace invalid percent characters $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); // Add unreserved and % to $extra_chars (the latter is safe because all // pct-encoded sections are now valid). $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; // Now replace any bytes that aren't allowed with their pct-encoded versions $position = 0; $strlen = strlen($string); while (($position += strspn($string, $extra_chars, $position)) < $strlen) { $value = ord($string[$position]); // Start position $start = $position; // By default we are valid $valid = true; // No one byte sequences are valid due to the while. // Two byte sequence: if (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $length = 1; $remaining = 0; } if ($remaining) { if ($position + $length <= $strlen) { for ($position++; $remaining; $position++) { $value = ord($string[$position]); // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $character |= ($value & 0x3F) << (--$remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte: else { $valid = false; $position--; break; } } } else { $position = $strlen - 1; $valid = false; } } // Percent encode anything invalid or not in ucschar if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of ucschar codepoints // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF || ( // Everything else not in ucschar $character > 0xD7FF && $character < 0xF900 || $character < 0xA0 || $character > 0xEFFFD ) && ( // Everything not in iprivate, if it applies !$iprivate || $character < 0xE000 || $character > 0x10FFFD ) ) { // If we were a character, pretend we weren't, but rather an error. if ($valid) $position--; for ($j = $start; $j <= $position; $j++) { $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); $j += 2; $position += 2; $strlen += 2; } } } return $string; } /** * Callback function for preg_replace_callback. * * Removes sequences of percent encoded bytes that represent UTF-8 * encoded characters in iunreserved * * @param array $match PCRE match * @return string Replacement */ protected function remove_iunreserved_percent_encoded($match) { // As we just have valid percent encoded sequences we can just explode // and ignore the first member of the returned array (an empty string). $bytes = explode('%', $match[0]); // Initialize the new string (this is what will be returned) and that // there are no bytes remaining in the current sequence (unsurprising // at the first byte!). $string = ''; $remaining = 0; // Loop over each and every byte, and set $value to its value for ($i = 1, $len = count($bytes); $i < $len; $i++) { $value = hexdec($bytes[$i]); // If we're the first byte of sequence: if (!$remaining) { // Start position $start = $i; // By default we are valid $valid = true; // One byte sequence: if ($value <= 0x7F) { $character = $value; $length = 1; } // Two byte sequence: elseif (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $remaining = 0; } } // Continuation byte: else { // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $remaining--; $character |= ($value & 0x3F) << ($remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: else { $valid = false; $remaining = 0; $i--; } } // If we've reached the end of the current byte sequence, append it to Unicode::$data if (!$remaining) { // Percent encode anything invalid or not in iunreserved if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of iunreserved codepoints || $character < 0x2D || $character > 0xEFFFD // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF // Everything else not in iunreserved (this is all BMP) || $character === 0x2F || $character > 0x39 && $character < 0x41 || $character > 0x5A && $character < 0x61 || $character > 0x7A && $character < 0x7E || $character > 0x7E && $character < 0xA0 || $character > 0xD7FF && $character < 0xF900 ) { for ($j = $start; $j <= $i; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } else { for ($j = $start; $j <= $i; $j++) { $string .= chr(hexdec($bytes[$j])); } } } } // If we have any bytes left over they are invalid (i.e., we are // mid-way through a multi-byte sequence) if ($remaining) { for ($j = $start; $j < $len; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } return $string; } protected function scheme_normalization() { if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { $this->iuserinfo = null; } if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { $this->ihost = null; } if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { $this->port = null; } if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { $this->ipath = ''; } if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { $this->iquery = null; } if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { $this->ifragment = null; } } /** * Check if the object represents a valid IRI. This needs to be done on each * call as some things change depending on another part of the IRI. * * @return bool */ public function is_valid() { $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; if ($this->ipath !== '' && ( $isauthority && ( $this->ipath[0] !== '/' || substr($this->ipath, 0, 2) === '//' ) || ( $this->scheme === null && !$isauthority && strpos($this->ipath, ':') !== false && (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) ) ) ) { return false; } return true; } /** * Set the entire IRI. Returns true on success, false on failure (if there * are any invalid characters). * * @param string $iri * @return bool */ public function set_iri($iri) { static $cache; if (!$cache) { $cache = array(); } if ($iri === null) { return true; } elseif (isset($cache[$iri])) { list($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return) = $cache[$iri]; return $return; } else { $parsed = $this->parse_iri((string) $iri); if (!$parsed) { return false; } $return = $this->set_scheme($parsed['scheme']) && $this->set_authority($parsed['authority']) && $this->set_path($parsed['path']) && $this->set_query($parsed['query']) && $this->set_fragment($parsed['fragment']); $cache[$iri] = array($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return); return $return; } } /** * Set the scheme. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $scheme * @return bool */ public function set_scheme($scheme) { if ($scheme === null) { $this->scheme = null; } elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { $this->scheme = null; return false; } else { $this->scheme = strtolower($scheme); } return true; } /** * Set the authority. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $authority * @return bool */ public function set_authority($authority) { static $cache; if (!$cache) $cache = array(); if ($authority === null) { $this->iuserinfo = null; $this->ihost = null; $this->port = null; return true; } elseif (isset($cache[$authority])) { list($this->iuserinfo, $this->ihost, $this->port, $return) = $cache[$authority]; return $return; } else { $remaining = $authority; if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { $iuserinfo = substr($remaining, 0, $iuserinfo_end); $remaining = substr($remaining, $iuserinfo_end + 1); } else { $iuserinfo = null; } if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) { if (($port = substr($remaining, $port_start + 1)) === false) { $port = null; } $remaining = substr($remaining, 0, $port_start); } else { $port = null; } $return = $this->set_userinfo($iuserinfo) && $this->set_host($remaining) && $this->set_port($port); $cache[$authority] = array($this->iuserinfo, $this->ihost, $this->port, $return); return $return; } } /** * Set the iuserinfo. * * @param string $iuserinfo * @return bool */ public function set_userinfo($iuserinfo) { if ($iuserinfo === null) { $this->iuserinfo = null; } else { $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); $this->scheme_normalization(); } return true; } /** * Set the ihost. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $ihost * @return bool */ public function set_host($ihost) { if ($ihost === null) { $this->ihost = null; return true; } elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { if (SimplePie_Net_IPv6::check_ipv6(substr($ihost, 1, -1))) { $this->ihost = '[' . SimplePie_Net_IPv6::compress(substr($ihost, 1, -1)) . ']'; } else { $this->ihost = null; return false; } } else { $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); // Lowercase, but ignore pct-encoded sections (as they should // remain uppercase). This must be done after the previous step // as that can add unescaped characters. $position = 0; $strlen = strlen($ihost); while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { if ($ihost[$position] === '%') { $position += 3; } else { $ihost[$position] = strtolower($ihost[$position]); $position++; } } $this->ihost = $ihost; } $this->scheme_normalization(); return true; } /** * Set the port. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $port * @return bool */ public function set_port($port) { if ($port === null) { $this->port = null; return true; } elseif (strspn($port, '0123456789') === strlen($port)) { $this->port = (int) $port; $this->scheme_normalization(); return true; } else { $this->port = null; return false; } } /** * Set the ipath. * * @param string $ipath * @return bool */ public function set_path($ipath) { static $cache; if (!$cache) { $cache = array(); } $ipath = (string) $ipath; if (isset($cache[$ipath])) { $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; } else { $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); $removed = $this->remove_dot_segments($valid); $cache[$ipath] = array($valid, $removed); $this->ipath = ($this->scheme !== null) ? $removed : $valid; } $this->scheme_normalization(); return true; } /** * Set the iquery. * * @param string $iquery * @return bool */ public function set_query($iquery) { if ($iquery === null) { $this->iquery = null; } else { $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); $this->scheme_normalization(); } return true; } /** * Set the ifragment. * * @param string $ifragment * @return bool */ public function set_fragment($ifragment) { if ($ifragment === null) { $this->ifragment = null; } else { $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); $this->scheme_normalization(); } return true; } /** * Convert an IRI to a URI (or parts thereof) * * @return string */ public function to_uri($string) { static $non_ascii; if (!$non_ascii) { $non_ascii = implode('', range("\x80", "\xFF")); } $position = 0; $strlen = strlen($string); while (($position += strcspn($string, $non_ascii, $position)) < $strlen) { $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1); $position += 3; $strlen += 2; } return $string; } /** * Get the complete IRI * * @return string */ public function get_iri() { if (!$this->is_valid()) { return false; } $iri = ''; if ($this->scheme !== null) { $iri .= $this->scheme . ':'; } if (($iauthority = $this->get_iauthority()) !== null) { $iri .= '//' . $iauthority; } if ($this->ipath !== '') { $iri .= $this->ipath; } elseif (!empty($this->normalization[$this->scheme]['ipath']) && $iauthority !== null && $iauthority !== '') { $iri .= $this->normalization[$this->scheme]['ipath']; } if ($this->iquery !== null) { $iri .= '?' . $this->iquery; } if ($this->ifragment !== null) { $iri .= '#' . $this->ifragment; } return $iri; } /** * Get the complete URI * * @return string */ public function get_uri() { return $this->to_uri($this->get_iri()); } /** * Get the complete iauthority * * @return string */ protected function get_iauthority() { if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null) { $iauthority = ''; if ($this->iuserinfo !== null) { $iauthority .= $this->iuserinfo . '@'; } if ($this->ihost !== null) { $iauthority .= $this->ihost; } if ($this->port !== null) { $iauthority .= ':' . $this->port; } return $iauthority; } else { return null; } } /** * Get the complete authority * * @return string */ protected function get_authority() { $iauthority = $this->get_iauthority(); if (is_string($iauthority)) return $this->to_uri($iauthority); else return $iauthority; } } ================================================ FILE: libraries/simplepie/library/SimplePie/Item.php ================================================ feed = $feed; $this->data = $data; } /** * Set the registry handler * * This is usually used by {@see SimplePie_Registry::create} * * @since 1.3 * @param SimplePie_Registry $registry */ public function set_registry(SimplePie_Registry $registry) { $this->registry = $registry; } /** * Get a string representation of the item * * @return string */ public function __toString() { return md5(serialize($this->data)); } /** * Remove items that link back to this before destroying this object */ public function __destruct() { if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode')) { unset($this->feed); } } /** * Get data for an item-level element * * This method allows you to get access to ANY element/attribute that is a * sub-element of the item/entry tag. * * See {@see SimplePie::get_feed_tags()} for a description of the return value * * @since 1.0 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces * @param string $namespace The URL of the XML namespace of the elements you're trying to access * @param string $tag Tag name * @return array */ public function get_item_tags($namespace, $tag) { if (isset($this->data['child'][$namespace][$tag])) { return $this->data['child'][$namespace][$tag]; } else { return null; } } /** * Get the base URL value from the parent feed * * Uses `` * * @param array $element * @return string */ public function get_base($element = array()) { return $this->feed->get_base($element); } /** * Sanitize feed data * * @access private * @see SimplePie::sanitize() * @param string $data Data to sanitize * @param int $type One of the SIMPLEPIE_CONSTRUCT_* constants * @param string $base Base URL to resolve URLs against * @return string Sanitized data */ public function sanitize($data, $type, $base = '') { return $this->feed->sanitize($data, $type, $base); } /** * Get the parent feed * * Note: this may not work as you think for multifeeds! * * @link http://simplepie.org/faq/typical_multifeed_gotchas#missing_data_from_feed * @since 1.0 * @return SimplePie */ public function get_feed() { return $this->feed; } /** * Get the unique identifier for the item * * This is usually used when writing code to check for new items in a feed. * * Uses ``, ``, `` or the `about` attribute * for RDF. If none of these are supplied (or `$hash` is true), creates an * MD5 hash based on the permalink and title. If either of those are not * supplied, creates a hash based on the full feed data. * * @since Beta 2 * @param boolean $hash Should we force using a hash instead of the supplied ID? * @return string */ public function get_id($hash = false) { if (!$hash) { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'identifier')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'identifier')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (isset($this->data['attribs'][SIMPLEPIE_NAMESPACE_RDF]['about'])) { return $this->sanitize($this->data['attribs'][SIMPLEPIE_NAMESPACE_RDF]['about'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (($return = $this->get_permalink()) !== null) { return $return; } elseif (($return = $this->get_title()) !== null) { return $return; } } if ($this->get_permalink() !== null || $this->get_title() !== null) { return md5($this->get_permalink() . $this->get_title()); } else { return md5(serialize($this->data)); } } /** * Get the title of the item * * Uses ``, `` or `<dc:title>` * * @since Beta 2 (previously called `get_item_title` since 0.8) * @return string|null */ public function get_title() { if (!isset($this->data['title'])) { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $this->data['title'] = null; } } return $this->data['title']; } /** * Get the content for the item * * Prefers summaries over full content , but will return full content if a * summary does not exist. * * To prefer full content instead, use {@see get_content} * * Uses `<atom:summary>`, `<description>`, `<dc:description>` or * `<itunes:subtitle>` * * @since 0.8 * @param boolean $description_only Should we avoid falling back to the content? * @return string|null */ public function get_description($description_only = false) { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'summary')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'summary')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML); } elseif (!$description_only) { return $this->get_content(true); } else { return null; } } /** * Get the content for the item * * Prefers full content over summaries, but will return a summary if full * content does not exist. * * To prefer summaries instead, use {@see get_description} * * Uses `<atom:content>` or `<content:encoded>` (RSS 1.0 Content Module) * * @since 1.0 * @param boolean $content_only Should we avoid falling back to the description? * @return string|null */ public function get_content($content_only = false) { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'content')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_content_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'content')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); } elseif (!$content_only) { return $this->get_description(true); } else { return null; } } /** * Get a category for the item * * @since Beta 3 (previously called `get_categories()` since Beta 2) * @param int $key The category that you want to return. Remember that arrays begin with 0, not 1 * @return SimplePie_Category|null */ public function get_category($key = 0) { $categories = $this->get_categories(); if (isset($categories[$key])) { return $categories[$key]; } else { return null; } } /** * Get all categories for the item * * Uses `<atom:category>`, `<category>` or `<dc:subject>` * * @since Beta 3 * @return array|null List of {@see SimplePie_Category} objects */ public function get_categories() { $categories = array(); foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['attribs']['']['term'])) { $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) { // This is really the label, but keep this as the term also for BC. // Label will also work on retrieving because that falls back to term. $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); if (isset($category['attribs']['']['domain'])) { $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $scheme = null; } $categories[] = $this->registry->create('Category', array($term, $scheme, null)); } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) { $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) { $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } if (!empty($categories)) { return array_unique($categories); } else { return null; } } /** * Get an author for the item * * @since Beta 2 * @param int $key The author that you want to return. Remember that arrays begin with 0, not 1 * @return SimplePie_Author|null */ public function get_author($key = 0) { $authors = $this->get_authors(); if (isset($authors[$key])) { return $authors[$key]; } else { return null; } } /** * Get a contributor for the item * * @since 1.1 * @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1 * @return SimplePie_Author|null */ public function get_contributor($key = 0) { $contributors = $this->get_contributors(); if (isset($contributors[$key])) { return $contributors[$key]; } else { return null; } } /** * Get all contributors for the item * * Uses `<atom:contributor>` * * @since 1.1 * @return array|null List of {@see SimplePie_Author} objects */ public function get_contributors() { $contributors = array(); foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) { $name = null; $uri = null; $email = null; if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $contributors[] = $this->registry->create('Author', array($name, $uri, $email)); } } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) { $name = null; $url = null; $email = null; if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $contributors[] = $this->registry->create('Author', array($name, $url, $email)); } } if (!empty($contributors)) { return array_unique($contributors); } else { return null; } } /** * Get all authors for the item * * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>` * * @since Beta 2 * @return array|null List of {@see SimplePie_Author} objects */ public function get_authors() { $authors = array(); foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) { $name = null; $uri = null; $email = null; if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); } if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $authors[] = $this->registry->create('Author', array($name, $uri, $email)); } } if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) { $name = null; $url = null; $email = null; if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); } if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $authors[] = $this->registry->create('Author', array($name, $url, $email)); } } if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'author')) { $authors[] = $this->registry->create('Author', array(null, null, $this->sanitize($author[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT))); } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } if (!empty($authors)) { return array_unique($authors); } elseif (($source = $this->get_source()) && ($authors = $source->get_authors())) { return $authors; } elseif ($authors = $this->feed->get_authors()) { return $authors; } else { return null; } } /** * Get the copyright info for the item * * Uses `<atom:rights>` or `<dc:rights>` * * @since 1.1 * @return string */ public function get_copyright() { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { return null; } } /** * Get the posting date/time for the item * * Uses `<atom:published>`, `<atom:updated>`, `<atom:issued>`, * `<atom:modified>`, `<pubDate>` or `<dc:date>` * * Note: obeys PHP's timezone setting. To get a UTC date/time, use * {@see get_gmdate} * * @since Beta 2 (previously called `get_item_date` since 0.8) * * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data) * @return int|string|null */ public function get_date($date_format = 'j F Y, g:i a') { if (!isset($this->data['date'])) { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date')) { $this->data['date']['raw'] = $return[0]['data']; } if (!empty($this->data['date']['raw'])) { $parser = $this->registry->call('Parse_Date', 'get'); $this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']); } else { $this->data['date'] = null; } } if ($this->data['date']) { $date_format = (string) $date_format; switch ($date_format) { case '': return $this->sanitize($this->data['date']['raw'], SIMPLEPIE_CONSTRUCT_TEXT); case 'U': return $this->data['date']['parsed']; default: return date($date_format, $this->data['date']['parsed']); } } else { return null; } } /** * Get the update date/time for the item * * Uses `<atom:updated>` * * Note: obeys PHP's timezone setting. To get a UTC date/time, use * {@see get_gmdate} * * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data) * @return int|string|null */ public function get_updated_date($date_format = 'j F Y, g:i a') { if (!isset($this->data['updated'])) { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated')) { $this->data['updated']['raw'] = $return[0]['data']; } if (!empty($this->data['updated']['raw'])) { $parser = $this->registry->call('Parse_Date', 'get'); $this->data['updated']['parsed'] = $parser->parse($this->data['date']['raw']); } else { $this->data['updated'] = null; } } if ($this->data['updated']) { $date_format = (string) $date_format; switch ($date_format) { case '': return $this->sanitize($this->data['updated']['raw'], SIMPLEPIE_CONSTRUCT_TEXT); case 'U': return $this->data['updated']['parsed']; default: return date($date_format, $this->data['updated']['parsed']); } } else { return null; } } /** * Get the localized posting date/time for the item * * Returns the date formatted in the localized language. To display in * languages other than the server's default, you need to change the locale * with {@link http://php.net/setlocale setlocale()}. The available * localizations depend on which ones are installed on your web server. * * @since 1.0 * * @param string $date_format Supports any PHP date format from {@see http://php.net/strftime} (empty for the raw data) * @return int|string|null */ public function get_local_date($date_format = '%c') { if (!$date_format) { return $this->sanitize($this->get_date(''), SIMPLEPIE_CONSTRUCT_TEXT); } elseif (($date = $this->get_date('U')) !== null && $date !== false) { return strftime($date_format, $date); } else { return null; } } /** * Get the posting date/time for the item (UTC time) * * @see get_date * @param string $date_format Supports any PHP date format from {@see http://php.net/date} * @return int|string|null */ public function get_gmdate($date_format = 'j F Y, g:i a') { $date = $this->get_date('U'); if ($date === null) { return null; } return gmdate($date_format, $date); } /** * Get the update date/time for the item (UTC time) * * @see get_updated_date * @param string $date_format Supports any PHP date format from {@see http://php.net/date} * @return int|string|null */ public function get_updated_gmdate($date_format = 'j F Y, g:i a') { $date = $this->get_updated_date('U'); if ($date === null) { return null; } return gmdate($date_format, $date); } /** * Get the permalink for the item * * Returns the first link available with a relationship of "alternate". * Identical to {@see get_link()} with key 0 * * @see get_link * @since 0.8 * @return string|null Permalink URL */ public function get_permalink() { $link = $this->get_link(); $enclosure = $this->get_enclosure(0); if ($link !== null) { return $link; } elseif ($enclosure !== null) { return $enclosure->get_link(); } else { return null; } } /** * Get a single link for the item * * @since Beta 3 * @param int $key The link that you want to return. Remember that arrays begin with 0, not 1 * @param string $rel The relationship of the link to return * @return string|null Link URL */ public function get_link($key = 0, $rel = 'alternate') { $links = $this->get_links($rel); if ($links[$key] !== null) { return $links[$key]; } else { return null; } } /** * Get all links for the item * * Uses `<atom:link>`, `<link>` or `<guid>` * * @since Beta 2 * @param string $rel The relationship of links to return * @return array|null Links found for the item (strings) */ public function get_links($rel = 'alternate') { if (!isset($this->data['links'])) { $this->data['links'] = array(); foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); } } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); } } if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) { if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true') { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } } $keys = array_keys($this->data['links']); foreach ($keys as $key) { if ($this->registry->call('Misc', 'is_isegment_nz_nc', array($key))) { if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) { $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; } else { $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; } } elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) { $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; } $this->data['links'][$key] = array_unique($this->data['links'][$key]); } } if (isset($this->data['links'][$rel])) { return $this->data['links'][$rel]; } else { return null; } } /** * Get an enclosure from the item * * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. * * @since Beta 2 * @todo Add ability to prefer one type of content over another (in a media group). * @param int $key The enclosure that you want to return. Remember that arrays begin with 0, not 1 * @return SimplePie_Enclosure|null */ public function get_enclosure($key = 0, $prefer = null) { $enclosures = $this->get_enclosures(); if (isset($enclosures[$key])) { return $enclosures[$key]; } else { return null; } } /** * Get all available enclosures (podcasts, etc.) * * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. * * At this point, we're pretty much assuming that all enclosures for an item * are the same content. Anything else is too complicated to * properly support. * * @since Beta 2 * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4). * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists). * @return array|null List of SimplePie_Enclosure items */ public function get_enclosures() { if (!isset($this->data['enclosures'])) { $this->data['enclosures'] = array(); // Elements $captions_parent = null; $categories_parent = null; $copyrights_parent = null; $credits_parent = null; $description_parent = null; $duration_parent = null; $hashes_parent = null; $keywords_parent = null; $player_parent = null; $ratings_parent = null; $restrictions_parent = null; $thumbnails_parent = null; $title_parent = null; // Let's do the channel and item-level ones first, and just re-use them if we need to. $parent = $this->get_feed(); // CAPTIONS if ($captions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text')) { foreach ($captions as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $captions_parent[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); } } elseif ($captions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text')) { foreach ($captions as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $captions_parent[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); } } if (is_array($captions_parent)) { $captions_parent = array_values(array_unique($captions_parent)); } // CATEGORIES foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label)); } foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label)); } foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'category') as $category) { $term = null; $scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; $label = null; if (isset($category['attribs']['']['text'])) { $label = $this->sanitize($category['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label)); if (isset($category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'])) { foreach ((array) $category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'] as $subcategory) { if (isset($subcategory['attribs']['']['text'])) { $label = $this->sanitize($subcategory['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label)); } } } if (is_array($categories_parent)) { $categories_parent = array_values(array_unique($categories_parent)); } // COPYRIGHT if ($copyright = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright')) { $copyright_url = null; $copyright_label = null; if (isset($copyright[0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($copyright[0]['data'])) { $copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $copyrights_parent = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); } elseif ($copyright = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright')) { $copyright_url = null; $copyright_label = null; if (isset($copyright[0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($copyright[0]['data'])) { $copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $copyrights_parent = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); } // CREDITS if ($credits = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit')) { foreach ($credits as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $credits_parent[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); } } elseif ($credits = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit')) { foreach ($credits as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $credits_parent[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); } } if (is_array($credits_parent)) { $credits_parent = array_values(array_unique($credits_parent)); } // DESCRIPTION if ($description_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description')) { if (isset($description_parent[0]['data'])) { $description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } } elseif ($description_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description')) { if (isset($description_parent[0]['data'])) { $description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } } // DURATION if ($duration_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'duration')) { $seconds = null; $minutes = null; $hours = null; if (isset($duration_parent[0]['data'])) { $temp = explode(':', $this->sanitize($duration_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); if (sizeof($temp) > 0) { $seconds = (int) array_pop($temp); } if (sizeof($temp) > 0) { $minutes = (int) array_pop($temp); $seconds += $minutes * 60; } if (sizeof($temp) > 0) { $hours = (int) array_pop($temp); $seconds += $hours * 3600; } unset($temp); $duration_parent = $seconds; } } // HASHES if ($hashes_iterator = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash')) { foreach ($hashes_iterator as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes_parent[] = $algo.':'.$value; } } elseif ($hashes_iterator = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash')) { foreach ($hashes_iterator as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes_parent[] = $algo.':'.$value; } } if (is_array($hashes_parent)) { $hashes_parent = array_values(array_unique($hashes_parent)); } // KEYWORDS if ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords')) { if (isset($keywords[0]['data'])) { $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords_parent[] = trim($word); } } unset($temp); } elseif ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords')) { if (isset($keywords[0]['data'])) { $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords_parent[] = trim($word); } } unset($temp); } elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords')) { if (isset($keywords[0]['data'])) { $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords_parent[] = trim($word); } } unset($temp); } elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords')) { if (isset($keywords[0]['data'])) { $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords_parent[] = trim($word); } } unset($temp); } if (is_array($keywords_parent)) { $keywords_parent = array_values(array_unique($keywords_parent)); } // PLAYER if ($player_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player')) { if (isset($player_parent[0]['attribs']['']['url'])) { $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } } elseif ($player_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player')) { if (isset($player_parent[0]['attribs']['']['url'])) { $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } } // RATINGS if ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating')) { foreach ($ratings as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); } } elseif ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit')) { foreach ($ratings as $rating) { $rating_scheme = 'urn:itunes'; $rating_value = null; if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); } } elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating')) { foreach ($ratings as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); } } elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit')) { foreach ($ratings as $rating) { $rating_scheme = 'urn:itunes'; $rating_value = null; if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); } } if (is_array($ratings_parent)) { $ratings_parent = array_values(array_unique($ratings_parent)); } // RESTRICTIONS if ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction')) { foreach ($restrictions as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); } } elseif ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block')) { foreach ($restrictions as $restriction) { $restriction_relationship = 'allow'; $restriction_type = null; $restriction_value = 'itunes'; if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') { $restriction_relationship = 'deny'; } $restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); } } elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction')) { foreach ($restrictions as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); } } elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block')) { foreach ($restrictions as $restriction) { $restriction_relationship = 'allow'; $restriction_type = null; $restriction_value = 'itunes'; if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') { $restriction_relationship = 'deny'; } $restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); } } if (is_array($restrictions_parent)) { $restrictions_parent = array_values(array_unique($restrictions_parent)); } else { $restrictions_parent = array(new SimplePie_Restriction('allow', null, 'default')); } // THUMBNAILS if ($thumbnails = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail')) { foreach ($thumbnails as $thumbnail) { if (isset($thumbnail['attribs']['']['url'])) { $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } } } elseif ($thumbnails = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail')) { foreach ($thumbnails as $thumbnail) { if (isset($thumbnail['attribs']['']['url'])) { $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } } } // TITLES if ($title_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title')) { if (isset($title_parent[0]['data'])) { $title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } } elseif ($title_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title')) { if (isset($title_parent[0]['data'])) { $title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } } // Clear the memory unset($parent); // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; // Elements $captions = null; $categories = null; $copyrights = null; $credits = null; $description = null; $hashes = null; $keywords = null; $player = null; $ratings = null; $restrictions = null; $thumbnails = null; $title = null; // If we have media:group tags, loop through them. foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group') as $group) { if(isset($group['child']) && isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'])) { // If we have media:content tags, loop through them. foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content) { if (isset($content['attribs']['']['url'])) { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; // Elements $captions = null; $categories = null; $copyrights = null; $credits = null; $description = null; $hashes = null; $keywords = null; $player = null; $ratings = null; $restrictions = null; $thumbnails = null; $title = null; // Start checking the attributes of media:content if (isset($content['attribs']['']['bitrate'])) { $bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['channels'])) { $channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['duration'])) { $duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $duration = $duration_parent; } if (isset($content['attribs']['']['expression'])) { $expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['framerate'])) { $framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['height'])) { $height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['lang'])) { $lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['fileSize'])) { $length = ceil($content['attribs']['']['fileSize']); } if (isset($content['attribs']['']['medium'])) { $medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['samplingrate'])) { $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['type'])) { $type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['width'])) { $width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT); } $url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); // Checking the other optional media: elements. Priority: media:content, media:group, item, channel // CAPTIONS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); } if (is_array($captions)) { $captions = array_values(array_unique($captions)); } } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) { foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); } if (is_array($captions)) { $captions = array_values(array_unique($captions)); } } else { $captions = $captions_parent; } // CATEGORIES if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) { foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); } } if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) { foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); } } if (is_array($categories) && is_array($categories_parent)) { $categories = array_values(array_unique(array_merge($categories, $categories_parent))); } elseif (is_array($categories)) { $categories = array_values(array_unique($categories)); } elseif (is_array($categories_parent)) { $categories = array_values(array_unique($categories_parent)); } // COPYRIGHTS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) { $copyright_url = null; $copyright_label = null; if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { $copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) { $copyright_url = null; $copyright_label = null; if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { $copyright_label = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); } else { $copyrights = $copyrights_parent; } // CREDITS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); } if (is_array($credits)) { $credits = array_values(array_unique($credits)); } } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) { foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); } if (is_array($credits)) { $credits = array_values(array_unique($credits)); } } else { $credits = $credits_parent; } // DESCRIPTION if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) { $description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) { $description = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $description = $description_parent; } // HASHES if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes[] = $algo.':'.$value; } if (is_array($hashes)) { $hashes = array_values(array_unique($hashes)); } } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) { foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes[] = $algo.':'.$value; } if (is_array($hashes)) { $hashes = array_values(array_unique($hashes)); } } else { $hashes = $hashes_parent; } // KEYWORDS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) { if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { $temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords[] = trim($word); } unset($temp); } if (is_array($keywords)) { $keywords = array_values(array_unique($keywords)); } } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) { if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { $temp = explode(',', $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords[] = trim($word); } unset($temp); } if (is_array($keywords)) { $keywords = array_values(array_unique($keywords)); } } else { $keywords = $keywords_parent; } // PLAYER if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) { $player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) { $player = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } else { $player = $player_parent; } // RATINGS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); } if (is_array($ratings)) { $ratings = array_values(array_unique($ratings)); } } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) { foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); } if (is_array($ratings)) { $ratings = array_values(array_unique($ratings)); } } else { $ratings = $ratings_parent; } // RESTRICTIONS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); } if (is_array($restrictions)) { $restrictions = array_values(array_unique($restrictions)); } } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) { foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); } if (is_array($restrictions)) { $restrictions = array_values(array_unique($restrictions)); } } else { $restrictions = $restrictions_parent; } // THUMBNAILS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } if (is_array($thumbnails)) { $thumbnails = array_values(array_unique($thumbnails)); } } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) { foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } if (is_array($thumbnails)) { $thumbnails = array_values(array_unique($thumbnails)); } } else { $thumbnails = $thumbnails_parent; } // TITLES if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) { $title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) { $title = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $title = $title_parent; } $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width)); } } } } // If we have standalone media:content tags, loop through them. if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'])) { foreach ((array) $this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content) { if (isset($content['attribs']['']['url']) || isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; // Elements $captions = null; $categories = null; $copyrights = null; $credits = null; $description = null; $hashes = null; $keywords = null; $player = null; $ratings = null; $restrictions = null; $thumbnails = null; $title = null; // Start checking the attributes of media:content if (isset($content['attribs']['']['bitrate'])) { $bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['channels'])) { $channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['duration'])) { $duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $duration = $duration_parent; } if (isset($content['attribs']['']['expression'])) { $expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['framerate'])) { $framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['height'])) { $height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['lang'])) { $lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['fileSize'])) { $length = ceil($content['attribs']['']['fileSize']); } if (isset($content['attribs']['']['medium'])) { $medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['samplingrate'])) { $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['type'])) { $type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['width'])) { $width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['attribs']['']['url'])) { $url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } // Checking the other optional media: elements. Priority: media:content, media:group, item, channel // CAPTIONS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); } if (is_array($captions)) { $captions = array_values(array_unique($captions)); } } else { $captions = $captions_parent; } // CATEGORIES if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) { foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); } } if (is_array($categories) && is_array($categories_parent)) { $categories = array_values(array_unique(array_merge($categories, $categories_parent))); } elseif (is_array($categories)) { $categories = array_values(array_unique($categories)); } elseif (is_array($categories_parent)) { $categories = array_values(array_unique($categories_parent)); } else { $categories = null; } // COPYRIGHTS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) { $copyright_url = null; $copyright_label = null; if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { $copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); } else { $copyrights = $copyrights_parent; } // CREDITS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); } if (is_array($credits)) { $credits = array_values(array_unique($credits)); } } else { $credits = $credits_parent; } // DESCRIPTION if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) { $description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $description = $description_parent; } // HASHES if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes[] = $algo.':'.$value; } if (is_array($hashes)) { $hashes = array_values(array_unique($hashes)); } } else { $hashes = $hashes_parent; } // KEYWORDS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) { if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { $temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords[] = trim($word); } unset($temp); } if (is_array($keywords)) { $keywords = array_values(array_unique($keywords)); } } else { $keywords = $keywords_parent; } // PLAYER if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) { $player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } else { $player = $player_parent; } // RATINGS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); } if (is_array($ratings)) { $ratings = array_values(array_unique($ratings)); } } else { $ratings = $ratings_parent; } // RESTRICTIONS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); } $restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); } if (is_array($restrictions)) { $restrictions = array_values(array_unique($restrictions)); } } else { $restrictions = $restrictions_parent; } // THUMBNAILS if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) { foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); } if (is_array($thumbnails)) { $thumbnails = array_values(array_unique($thumbnails)); } } else { $thumbnails = $thumbnails_parent; } // TITLES if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) { $title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $title = $title_parent; } $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width)); } } } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link) { if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; $url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); if (isset($link['attribs']['']['type'])) { $type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($link['attribs']['']['length'])) { $length = ceil($link['attribs']['']['length']); } // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width)); } } foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link) { if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; $url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); if (isset($link['attribs']['']['type'])) { $type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($link['attribs']['']['length'])) { $length = ceil($link['attribs']['']['length']); } // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width)); } } if ($enclosure = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure')) { if (isset($enclosure[0]['attribs']['']['url'])) { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; $url = $this->sanitize($enclosure[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure[0])); if (isset($enclosure[0]['attribs']['']['type'])) { $type = $this->sanitize($enclosure[0]['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($enclosure[0]['attribs']['']['length'])) { $length = ceil($enclosure[0]['attribs']['']['length']); } // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width)); } } if (sizeof($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $restrictions_parent || $samplingrate || $thumbnails_parent || $title_parent || $width)) { // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width)); } $this->data['enclosures'] = array_values(array_unique($this->data['enclosures'])); } if (!empty($this->data['enclosures'])) { return $this->data['enclosures']; } else { return null; } } /** * Get the latitude coordinates for the item * * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications * * Uses `<geo:lat>` or `<georss:point>` * * @since 1.0 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo * @link http://www.georss.org/ GeoRSS * @return string|null */ public function get_latitude() { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) { return (float) $return[0]['data']; } elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[1]; } else { return null; } } /** * Get the longitude coordinates for the item * * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications * * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>` * * @since 1.0 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo * @link http://www.georss.org/ GeoRSS * @return string|null */ public function get_longitude() { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) { return (float) $return[0]['data']; } elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) { return (float) $return[0]['data']; } elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[2]; } else { return null; } } /** * Get the `<atom:source>` for the item * * @since 1.1 * @return SimplePie_Source|null */ public function get_source() { if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'source')) { return $this->registry->create('Source', array($this, $return[0])); } else { return null; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Locator.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Used for feed auto-discovery * * * This class can be overloaded with {@see SimplePie::set_locator_class()} * * @package SimplePie */ class SimplePie_Locator { var $useragent; var $timeout; var $file; var $local = array(); var $elsewhere = array(); var $cached_entities = array(); var $http_base; var $base; var $base_location = 0; var $checked_feeds = 0; var $max_checked_feeds = 10; protected $registry; public function __construct(SimplePie_File $file, $timeout = 10, $useragent = null, $max_checked_feeds = 10) { $this->file = $file; $this->useragent = $useragent; $this->timeout = $timeout; $this->max_checked_feeds = $max_checked_feeds; if (class_exists('DOMDocument')) { $this->dom = new DOMDocument(); set_error_handler(array('SimplePie_Misc', 'silence_errors')); $this->dom->loadHTML($this->file->body); restore_error_handler(); } else { $this->dom = null; } } public function set_registry(SimplePie_Registry $registry) { $this->registry = $registry; } public function find($type = SIMPLEPIE_LOCATOR_ALL, &$working) { if ($this->is_feed($this->file)) { return $this->file; } if ($this->file->method & SIMPLEPIE_FILE_SOURCE_REMOTE) { $sniffer = $this->registry->create('Content_Type_Sniffer', array($this->file)); if ($sniffer->get_type() !== 'text/html') { return null; } } if ($type & ~SIMPLEPIE_LOCATOR_NONE) { $this->get_base(); } if ($type & SIMPLEPIE_LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery()) { return $working[0]; } if ($type & (SIMPLEPIE_LOCATOR_LOCAL_EXTENSION | SIMPLEPIE_LOCATOR_LOCAL_BODY | SIMPLEPIE_LOCATOR_REMOTE_EXTENSION | SIMPLEPIE_LOCATOR_REMOTE_BODY) && $this->get_links()) { if ($type & SIMPLEPIE_LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local)) { return $working; } if ($type & SIMPLEPIE_LOCATOR_LOCAL_BODY && $working = $this->body($this->local)) { return $working; } if ($type & SIMPLEPIE_LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere)) { return $working; } if ($type & SIMPLEPIE_LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere)) { return $working; } } return null; } public function is_feed($file) { if ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE) { $sniffer = $this->registry->create('Content_Type_Sniffer', array($file)); $sniffed = $sniffer->get_type(); if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml'))) { return true; } else { return false; } } elseif ($file->method & SIMPLEPIE_FILE_SOURCE_LOCAL) { return true; } else { return false; } } public function get_base() { if ($this->dom === null) { throw new SimplePie_Exception('DOMDocument not found, unable to use locator'); } $this->http_base = $this->file->url; $this->base = $this->http_base; $elements = $this->dom->getElementsByTagName('base'); foreach ($elements as $element) { if ($element->hasAttribute('href')) { $base = $this->registry->call('Misc', 'absolutize_url', array(trim($element->getAttribute('href')), $this->http_base)); if ($base === false) { continue; } $this->base = $base; $this->base_location = method_exists($element, 'getLineNo') ? $element->getLineNo() : 0; break; } } } public function autodiscovery() { $done = array(); $feeds = array(); $feeds = array_merge($feeds, $this->search_elements_by_tag('link', $done, $feeds)); $feeds = array_merge($feeds, $this->search_elements_by_tag('a', $done, $feeds)); $feeds = array_merge($feeds, $this->search_elements_by_tag('area', $done, $feeds)); if (!empty($feeds)) { return array_values($feeds); } else { return null; } } protected function search_elements_by_tag($name, &$done, $feeds) { if ($this->dom === null) { throw new SimplePie_Exception('DOMDocument not found, unable to use locator'); } $links = $this->dom->getElementsByTagName($name); foreach ($links as $link) { if ($this->checked_feeds === $this->max_checked_feeds) { break; } if ($link->hasAttribute('href') && $link->hasAttribute('rel')) { $rel = array_unique($this->registry->call('Misc', 'space_seperated_tokens', array(strtolower($link->getAttribute('rel'))))); $line = method_exists($link, 'getLineNo') ? $link->getLineNo() : 1; if ($this->base_location < $line) { $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base)); } else { $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->http_base)); } if ($href === false) { continue; } if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !in_array('stylesheet', $rel) && $link->hasAttribute('type') && in_array(strtolower($this->registry->call('Misc', 'parse_mime', array($link->getAttribute('type')))), array('application/rss+xml', 'application/atom+xml'))) && !isset($feeds[$href])) { $this->checked_feeds++; $headers = array( 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', ); $feed = $this->registry->create('File', array($href, $this->timeout, 5, $headers, $this->useragent)); if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) { $feeds[$href] = $feed; } } $done[] = $href; } } return $feeds; } public function get_links() { if ($this->dom === null) { throw new SimplePie_Exception('DOMDocument not found, unable to use locator'); } $links = $this->dom->getElementsByTagName('a'); foreach ($links as $link) { if ($link->hasAttribute('href')) { $href = trim($link->getAttribute('href')); $parsed = $this->registry->call('Misc', 'parse_url', array($href)); if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme'])) { if ($this->base_location < $link->getLineNo()) { $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base)); } else { $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->http_base)); } if ($href === false) { continue; } $current = $this->registry->call('Misc', 'parse_url', array($this->file->url)); if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority']) { $this->local[] = $href; } else { $this->elsewhere[] = $href; } } } } $this->local = array_unique($this->local); $this->elsewhere = array_unique($this->elsewhere); if (!empty($this->local) || !empty($this->elsewhere)) { return true; } return null; } public function extension(&$array) { foreach ($array as $key => $value) { if ($this->checked_feeds === $this->max_checked_feeds) { break; } if (in_array(strtolower(strrchr($value, '.')), array('.rss', '.rdf', '.atom', '.xml'))) { $this->checked_feeds++; $headers = array( 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', ); $feed = $this->registry->create('File', array($value, $this->timeout, 5, $headers, $this->useragent)); if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) { return $feed; } else { unset($array[$key]); } } } return null; } public function body(&$array) { foreach ($array as $key => $value) { if ($this->checked_feeds === $this->max_checked_feeds) { break; } if (preg_match('/(rss|rdf|atom|xml)/i', $value)) { $this->checked_feeds++; $headers = array( 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', ); $feed = $this->registry->create('File', array($value, $this->timeout, 5, null, $this->useragent)); if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) { return $feed; } else { unset($array[$key]); } } } return null; } } ================================================ FILE: libraries/simplepie/library/SimplePie/Misc.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Miscellanous utilities * * @package SimplePie */ class SimplePie_Misc { public static function time_hms($seconds) { $time = ''; $hours = floor($seconds / 3600); $remainder = $seconds % 3600; if ($hours > 0) { $time .= $hours.':'; } $minutes = floor($remainder / 60); $seconds = $remainder % 60; if ($minutes < 10 && $hours > 0) { $minutes = '0' . $minutes; } if ($seconds < 10) { $seconds = '0' . $seconds; } $time .= $minutes.':'; $time .= $seconds; return $time; } public static function absolutize_url($relative, $base) { $iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative); if ($iri === false) { return false; } return $iri->get_uri(); } /** * Get a HTML/XML element from a HTML string * * @deprecated Use DOMDocument instead (parsing HTML with regex is bad!) * @param string $realname Element name (including namespace prefix if applicable) * @param string $string HTML document * @return array */ public static function get_element($realname, $string) { $return = array(); $name = preg_quote($realname, '/'); if (preg_match_all("/<($name)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++) { $return[$i]['tag'] = $realname; $return[$i]['full'] = $matches[$i][0][0]; $return[$i]['offset'] = $matches[$i][0][1]; if (strlen($matches[$i][3][0]) <= 2) { $return[$i]['self_closing'] = true; } else { $return[$i]['self_closing'] = false; $return[$i]['content'] = $matches[$i][4][0]; } $return[$i]['attribs'] = array(); if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER)) { for ($j = 0, $total_attribs = count($attribs); $j < $total_attribs; $j++) { if (count($attribs[$j]) === 2) { $attribs[$j][2] = $attribs[$j][1]; } $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); } } } } return $return; } public static function element_implode($element) { $full = "<$element[tag]"; foreach ($element['attribs'] as $key => $value) { $key = strtolower($key); $full .= " $key=\"" . htmlspecialchars($value['data']) . '"'; } if ($element['self_closing']) { $full .= ' />'; } else { $full .= ">$element[content]</$element[tag]>"; } return $full; } public static function error($message, $level, $file, $line) { if ((ini_get('error_reporting') & $level) > 0) { switch ($level) { case E_USER_ERROR: $note = 'PHP Error'; break; case E_USER_WARNING: $note = 'PHP Warning'; break; case E_USER_NOTICE: $note = 'PHP Notice'; break; default: $note = 'Unknown Error'; break; } $log_error = true; if (!function_exists('error_log')) { $log_error = false; } $log_file = @ini_get('error_log'); if (!empty($log_file) && ('syslog' !== $log_file) && !@is_writable($log_file)) { $log_error = false; } if ($log_error) { @error_log("$note: $message in $file on line $line", 0); } } return $message; } public static function fix_protocol($url, $http = 1) { $url = SimplePie_Misc::normalize_url($url); $parsed = SimplePie_Misc::parse_url($url); if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') { return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http); } if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url)) { return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http); } if ($http === 2 && $parsed['scheme'] !== '') { return "feed:$url"; } elseif ($http === 3 && strtolower($parsed['scheme']) === 'http') { return substr_replace($url, 'podcast', 0, 4); } elseif ($http === 4 && strtolower($parsed['scheme']) === 'http') { return substr_replace($url, 'itpc', 0, 4); } else { return $url; } } public static function parse_url($url) { $iri = new SimplePie_IRI($url); return array( 'scheme' => (string) $iri->scheme, 'authority' => (string) $iri->authority, 'path' => (string) $iri->path, 'query' => (string) $iri->query, 'fragment' => (string) $iri->fragment ); } public static function compress_parse_url($scheme = '', $authority = '', $path = '', $query = '', $fragment = '') { $iri = new SimplePie_IRI(''); $iri->scheme = $scheme; $iri->authority = $authority; $iri->path = $path; $iri->query = $query; $iri->fragment = $fragment; return $iri->get_uri(); } public static function normalize_url($url) { $iri = new SimplePie_IRI($url); return $iri->get_uri(); } public static function percent_encoding_normalization($match) { $integer = hexdec($match[1]); if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E) { return chr($integer); } else { return strtoupper($match[0]); } } /** * Converts a Windows-1252 encoded string to a UTF-8 encoded string * * @static * @param string $string Windows-1252 encoded string * @return string UTF-8 encoded string */ public static function windows_1252_to_utf8($string) { static $convert_table = array("\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\xE2\x80\x99", "\x93" => "\xE2\x80\x9C", "\x94" => "\xE2\x80\x9D", "\x95" => "\xE2\x80\xA2", "\x96" => "\xE2\x80\x93", "\x97" => "\xE2\x80\x94", "\x98" => "\xCB\x9C", "\x99" => "\xE2\x84\xA2", "\x9A" => "\xC5\xA1", "\x9B" => "\xE2\x80\xBA", "\x9C" => "\xC5\x93", "\x9D" => "\xEF\xBF\xBD", "\x9E" => "\xC5\xBE", "\x9F" => "\xC5\xB8", "\xA0" => "\xC2\xA0", "\xA1" => "\xC2\xA1", "\xA2" => "\xC2\xA2", "\xA3" => "\xC2\xA3", "\xA4" => "\xC2\xA4", "\xA5" => "\xC2\xA5", "\xA6" => "\xC2\xA6", "\xA7" => "\xC2\xA7", "\xA8" => "\xC2\xA8", "\xA9" => "\xC2\xA9", "\xAA" => "\xC2\xAA", "\xAB" => "\xC2\xAB", "\xAC" => "\xC2\xAC", "\xAD" => "\xC2\xAD", "\xAE" => "\xC2\xAE", "\xAF" => "\xC2\xAF", "\xB0" => "\xC2\xB0", "\xB1" => "\xC2\xB1", "\xB2" => "\xC2\xB2", "\xB3" => "\xC2\xB3", "\xB4" => "\xC2\xB4", "\xB5" => "\xC2\xB5", "\xB6" => "\xC2\xB6", "\xB7" => "\xC2\xB7", "\xB8" => "\xC2\xB8", "\xB9" => "\xC2\xB9", "\xBA" => "\xC2\xBA", "\xBB" => "\xC2\xBB", "\xBC" => "\xC2\xBC", "\xBD" => "\xC2\xBD", "\xBE" => "\xC2\xBE", "\xBF" => "\xC2\xBF", "\xC0" => "\xC3\x80", "\xC1" => "\xC3\x81", "\xC2" => "\xC3\x82", "\xC3" => "\xC3\x83", "\xC4" => "\xC3\x84", "\xC5" => "\xC3\x85", "\xC6" => "\xC3\x86", "\xC7" => "\xC3\x87", "\xC8" => "\xC3\x88", "\xC9" => "\xC3\x89", "\xCA" => "\xC3\x8A", "\xCB" => "\xC3\x8B", "\xCC" => "\xC3\x8C", "\xCD" => "\xC3\x8D", "\xCE" => "\xC3\x8E", "\xCF" => "\xC3\x8F", "\xD0" => "\xC3\x90", "\xD1" => "\xC3\x91", "\xD2" => "\xC3\x92", "\xD3" => "\xC3\x93", "\xD4" => "\xC3\x94", "\xD5" => "\xC3\x95", "\xD6" => "\xC3\x96", "\xD7" => "\xC3\x97", "\xD8" => "\xC3\x98", "\xD9" => "\xC3\x99", "\xDA" => "\xC3\x9A", "\xDB" => "\xC3\x9B", "\xDC" => "\xC3\x9C", "\xDD" => "\xC3\x9D", "\xDE" => "\xC3\x9E", "\xDF" => "\xC3\x9F", "\xE0" => "\xC3\xA0", "\xE1" => "\xC3\xA1", "\xE2" => "\xC3\xA2", "\xE3" => "\xC3\xA3", "\xE4" => "\xC3\xA4", "\xE5" => "\xC3\xA5", "\xE6" => "\xC3\xA6", "\xE7" => "\xC3\xA7", "\xE8" => "\xC3\xA8", "\xE9" => "\xC3\xA9", "\xEA" => "\xC3\xAA", "\xEB" => "\xC3\xAB", "\xEC" => "\xC3\xAC", "\xED" => "\xC3\xAD", "\xEE" => "\xC3\xAE", "\xEF" => "\xC3\xAF", "\xF0" => "\xC3\xB0", "\xF1" => "\xC3\xB1", "\xF2" => "\xC3\xB2", "\xF3" => "\xC3\xB3", "\xF4" => "\xC3\xB4", "\xF5" => "\xC3\xB5", "\xF6" => "\xC3\xB6", "\xF7" => "\xC3\xB7", "\xF8" => "\xC3\xB8", "\xF9" => "\xC3\xB9", "\xFA" => "\xC3\xBA", "\xFB" => "\xC3\xBB", "\xFC" => "\xC3\xBC", "\xFD" => "\xC3\xBD", "\xFE" => "\xC3\xBE", "\xFF" => "\xC3\xBF"); return strtr($string, $convert_table); } /** * Change a string from one encoding to another * * @param string $data Raw data in $input encoding * @param string $input Encoding of $data * @param string $output Encoding you want * @return string|boolean False if we can't convert it */ public static function change_encoding($data, $input, $output) { $input = SimplePie_Misc::encoding($input); $output = SimplePie_Misc::encoding($output); // We fail to fail on non US-ASCII bytes if ($input === 'US-ASCII') { static $non_ascii_octects = ''; if (!$non_ascii_octects) { for ($i = 0x80; $i <= 0xFF; $i++) { $non_ascii_octects .= chr($i); } } $data = substr($data, 0, strcspn($data, $non_ascii_octects)); } // This is first, as behaviour of this is completely predictable if ($input === 'windows-1252' && $output === 'UTF-8') { return SimplePie_Misc::windows_1252_to_utf8($data); } // This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported). elseif (function_exists('mb_convert_encoding') && ($return = SimplePie_Misc::change_encoding_mbstring($data, $input, $output))) { return $return; } // This is last, as behaviour of this varies with OS userland and PHP version elseif (function_exists('iconv') && ($return = SimplePie_Misc::change_encoding_iconv($data, $input, $output))) { return $return; } // If we can't do anything, just fail else { return false; } } protected static function change_encoding_mbstring($data, $input, $output) { if ($input === 'windows-949') { $input = 'EUC-KR'; } if ($output === 'windows-949') { $output = 'EUC-KR'; } if ($input === 'Windows-31J') { $input = 'SJIS'; } if ($output === 'Windows-31J') { $output = 'SJIS'; } // Check that the encoding is supported if (@mb_convert_encoding("\x80", 'UTF-16BE', $input) === "\x00\x80") { return false; } if (!in_array($input, mb_list_encodings())) { return false; } // Let's do some conversion if ($return = @mb_convert_encoding($data, $output, $input)) { return $return; } return false; } protected static function change_encoding_iconv($data, $input, $output) { return @iconv($input, $output, $data); } /** * Normalize an encoding name * * This is automatically generated by create.php * * To generate it, run `php create.php` on the command line, and copy the * output to replace this function. * * @param string $charset Character set to standardise * @return string Standardised name */ public static function encoding($charset) { // Normalization from UTS #22 switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset))) { case 'adobestandardencoding': case 'csadobestandardencoding': return 'Adobe-Standard-Encoding'; case 'adobesymbolencoding': case 'cshppsmath': return 'Adobe-Symbol-Encoding'; case 'ami1251': case 'amiga1251': return 'Amiga-1251'; case 'ansix31101983': case 'csat5001983': case 'csiso99naplps': case 'isoir99': case 'naplps': return 'ANSI_X3.110-1983'; case 'arabic7': case 'asmo449': case 'csiso89asmo449': case 'iso9036': case 'isoir89': return 'ASMO_449'; case 'big5': case 'csbig5': return 'Big5'; case 'big5hkscs': return 'Big5-HKSCS'; case 'bocu1': case 'csbocu1': return 'BOCU-1'; case 'brf': case 'csbrf': return 'BRF'; case 'bs4730': case 'csiso4unitedkingdom': case 'gb': case 'iso646gb': case 'isoir4': case 'uk': return 'BS_4730'; case 'bsviewdata': case 'csiso47bsviewdata': case 'isoir47': return 'BS_viewdata'; case 'cesu8': case 'cscesu8': return 'CESU-8'; case 'ca': case 'csa71': case 'csaz243419851': case 'csiso121canadian1': case 'iso646ca': case 'isoir121': return 'CSA_Z243.4-1985-1'; case 'csa72': case 'csaz243419852': case 'csiso122canadian2': case 'iso646ca2': case 'isoir122': return 'CSA_Z243.4-1985-2'; case 'csaz24341985gr': case 'csiso123csaz24341985gr': case 'isoir123': return 'CSA_Z243.4-1985-gr'; case 'csiso139csn369103': case 'csn369103': case 'isoir139': return 'CSN_369103'; case 'csdecmcs': case 'dec': case 'decmcs': return 'DEC-MCS'; case 'csiso21german': case 'de': case 'din66003': case 'iso646de': case 'isoir21': return 'DIN_66003'; case 'csdkus': case 'dkus': return 'dk-us'; case 'csiso646danish': case 'dk': case 'ds2089': case 'iso646dk': return 'DS_2089'; case 'csibmebcdicatde': case 'ebcdicatde': return 'EBCDIC-AT-DE'; case 'csebcdicatdea': case 'ebcdicatdea': return 'EBCDIC-AT-DE-A'; case 'csebcdiccafr': case 'ebcdiccafr': return 'EBCDIC-CA-FR'; case 'csebcdicdkno': case 'ebcdicdkno': return 'EBCDIC-DK-NO'; case 'csebcdicdknoa': case 'ebcdicdknoa': return 'EBCDIC-DK-NO-A'; case 'csebcdices': case 'ebcdices': return 'EBCDIC-ES'; case 'csebcdicesa': case 'ebcdicesa': return 'EBCDIC-ES-A'; case 'csebcdicess': case 'ebcdicess': return 'EBCDIC-ES-S'; case 'csebcdicfise': case 'ebcdicfise': return 'EBCDIC-FI-SE'; case 'csebcdicfisea': case 'ebcdicfisea': return 'EBCDIC-FI-SE-A'; case 'csebcdicfr': case 'ebcdicfr': return 'EBCDIC-FR'; case 'csebcdicit': case 'ebcdicit': return 'EBCDIC-IT'; case 'csebcdicpt': case 'ebcdicpt': return 'EBCDIC-PT'; case 'csebcdicuk': case 'ebcdicuk': return 'EBCDIC-UK'; case 'csebcdicus': case 'ebcdicus': return 'EBCDIC-US'; case 'csiso111ecmacyrillic': case 'ecmacyrillic': case 'isoir111': case 'koi8e': return 'ECMA-cyrillic'; case 'csiso17spanish': case 'es': case 'iso646es': case 'isoir17': return 'ES'; case 'csiso85spanish2': case 'es2': case 'iso646es2': case 'isoir85': return 'ES2'; case 'cseucpkdfmtjapanese': case 'eucjp': case 'extendedunixcodepackedformatforjapanese': return 'EUC-JP'; case 'cseucfixwidjapanese': case 'extendedunixcodefixedwidthforjapanese': return 'Extended_UNIX_Code_Fixed_Width_for_Japanese'; case 'gb18030': return 'GB18030'; case 'chinese': case 'cp936': case 'csgb2312': case 'csiso58gb231280': case 'gb2312': case 'gb231280': case 'gbk': case 'isoir58': case 'ms936': case 'windows936': return 'GBK'; case 'cn': case 'csiso57gb1988': case 'gb198880': case 'iso646cn': case 'isoir57': return 'GB_1988-80'; case 'csiso153gost1976874': case 'gost1976874': case 'isoir153': case 'stsev35888': return 'GOST_19768-74'; case 'csiso150': case 'csiso150greekccitt': case 'greekccitt': case 'isoir150': return 'greek-ccitt'; case 'csiso88greek7': case 'greek7': case 'isoir88': return 'greek7'; case 'csiso18greek7old': case 'greek7old': case 'isoir18': return 'greek7-old'; case 'cshpdesktop': case 'hpdesktop': return 'HP-DeskTop'; case 'cshplegal': case 'hplegal': return 'HP-Legal'; case 'cshpmath8': case 'hpmath8': return 'HP-Math8'; case 'cshppifont': case 'hppifont': return 'HP-Pi-font'; case 'cshproman8': case 'hproman8': case 'r8': case 'roman8': return 'hp-roman8'; case 'hzgb2312': return 'HZ-GB-2312'; case 'csibmsymbols': case 'ibmsymbols': return 'IBM-Symbols'; case 'csibmthai': case 'ibmthai': return 'IBM-Thai'; case 'cp37': case 'csibm37': case 'ebcdiccpca': case 'ebcdiccpnl': case 'ebcdiccpus': case 'ebcdiccpwt': case 'ibm37': return 'IBM037'; case 'cp38': case 'csibm38': case 'ebcdicint': case 'ibm38': return 'IBM038'; case 'cp273': case 'csibm273': case 'ibm273': return 'IBM273'; case 'cp274': case 'csibm274': case 'ebcdicbe': case 'ibm274': return 'IBM274'; case 'cp275': case 'csibm275': case 'ebcdicbr': case 'ibm275': return 'IBM275'; case 'csibm277': case 'ebcdiccpdk': case 'ebcdiccpno': case 'ibm277': return 'IBM277'; case 'cp278': case 'csibm278': case 'ebcdiccpfi': case 'ebcdiccpse': case 'ibm278': return 'IBM278'; case 'cp280': case 'csibm280': case 'ebcdiccpit': case 'ibm280': return 'IBM280'; case 'cp281': case 'csibm281': case 'ebcdicjpe': case 'ibm281': return 'IBM281'; case 'cp284': case 'csibm284': case 'ebcdiccpes': case 'ibm284': return 'IBM284'; case 'cp285': case 'csibm285': case 'ebcdiccpgb': case 'ibm285': return 'IBM285'; case 'cp290': case 'csibm290': case 'ebcdicjpkana': case 'ibm290': return 'IBM290'; case 'cp297': case 'csibm297': case 'ebcdiccpfr': case 'ibm297': return 'IBM297'; case 'cp420': case 'csibm420': case 'ebcdiccpar1': case 'ibm420': return 'IBM420'; case 'cp423': case 'csibm423': case 'ebcdiccpgr': case 'ibm423': return 'IBM423'; case 'cp424': case 'csibm424': case 'ebcdiccphe': case 'ibm424': return 'IBM424'; case '437': case 'cp437': case 'cspc8codepage437': case 'ibm437': return 'IBM437'; case 'cp500': case 'csibm500': case 'ebcdiccpbe': case 'ebcdiccpch': case 'ibm500': return 'IBM500'; case 'cp775': case 'cspc775baltic': case 'ibm775': return 'IBM775'; case '850': case 'cp850': case 'cspc850multilingual': case 'ibm850': return 'IBM850'; case '851': case 'cp851': case 'csibm851': case 'ibm851': return 'IBM851'; case '852': case 'cp852': case 'cspcp852': case 'ibm852': return 'IBM852'; case '855': case 'cp855': case 'csibm855': case 'ibm855': return 'IBM855'; case '857': case 'cp857': case 'csibm857': case 'ibm857': return 'IBM857'; case 'ccsid858': case 'cp858': case 'ibm858': case 'pcmultilingual850euro': return 'IBM00858'; case '860': case 'cp860': case 'csibm860': case 'ibm860': return 'IBM860'; case '861': case 'cp861': case 'cpis': case 'csibm861': case 'ibm861': return 'IBM861'; case '862': case 'cp862': case 'cspc862latinhebrew': case 'ibm862': return 'IBM862'; case '863': case 'cp863': case 'csibm863': case 'ibm863': return 'IBM863'; case 'cp864': case 'csibm864': case 'ibm864': return 'IBM864'; case '865': case 'cp865': case 'csibm865': case 'ibm865': return 'IBM865'; case '866': case 'cp866': case 'csibm866': case 'ibm866': return 'IBM866'; case 'cp868': case 'cpar': case 'csibm868': case 'ibm868': return 'IBM868'; case '869': case 'cp869': case 'cpgr': case 'csibm869': case 'ibm869': return 'IBM869'; case 'cp870': case 'csibm870': case 'ebcdiccproece': case 'ebcdiccpyu': case 'ibm870': return 'IBM870'; case 'cp871': case 'csibm871': case 'ebcdiccpis': case 'ibm871': return 'IBM871'; case 'cp880': case 'csibm880': case 'ebcdiccyrillic': case 'ibm880': return 'IBM880'; case 'cp891': case 'csibm891': case 'ibm891': return 'IBM891'; case 'cp903': case 'csibm903': case 'ibm903': return 'IBM903'; case '904': case 'cp904': case 'csibbm904': case 'ibm904': return 'IBM904'; case 'cp905': case 'csibm905': case 'ebcdiccptr': case 'ibm905': return 'IBM905'; case 'cp918': case 'csibm918': case 'ebcdiccpar2': case 'ibm918': return 'IBM918'; case 'ccsid924': case 'cp924': case 'ebcdiclatin9euro': case 'ibm924': return 'IBM00924'; case 'cp1026': case 'csibm1026': case 'ibm1026': return 'IBM1026'; case 'ibm1047': return 'IBM1047'; case 'ccsid1140': case 'cp1140': case 'ebcdicus37euro': case 'ibm1140': return 'IBM01140'; case 'ccsid1141': case 'cp1141': case 'ebcdicde273euro': case 'ibm1141': return 'IBM01141'; case 'ccsid1142': case 'cp1142': case 'ebcdicdk277euro': case 'ebcdicno277euro': case 'ibm1142': return 'IBM01142'; case 'ccsid1143': case 'cp1143': case 'ebcdicfi278euro': case 'ebcdicse278euro': case 'ibm1143': return 'IBM01143'; case 'ccsid1144': case 'cp1144': case 'ebcdicit280euro': case 'ibm1144': return 'IBM01144'; case 'ccsid1145': case 'cp1145': case 'ebcdices284euro': case 'ibm1145': return 'IBM01145'; case 'ccsid1146': case 'cp1146': case 'ebcdicgb285euro': case 'ibm1146': return 'IBM01146'; case 'ccsid1147': case 'cp1147': case 'ebcdicfr297euro': case 'ibm1147': return 'IBM01147'; case 'ccsid1148': case 'cp1148': case 'ebcdicinternational500euro': case 'ibm1148': return 'IBM01148'; case 'ccsid1149': case 'cp1149': case 'ebcdicis871euro': case 'ibm1149': return 'IBM01149'; case 'csiso143iecp271': case 'iecp271': case 'isoir143': return 'IEC_P27-1'; case 'csiso49inis': case 'inis': case 'isoir49': return 'INIS'; case 'csiso50inis8': case 'inis8': case 'isoir50': return 'INIS-8'; case 'csiso51iniscyrillic': case 'iniscyrillic': case 'isoir51': return 'INIS-cyrillic'; case 'csinvariant': case 'invariant': return 'INVARIANT'; case 'iso2022cn': return 'ISO-2022-CN'; case 'iso2022cnext': return 'ISO-2022-CN-EXT'; case 'csiso2022jp': case 'iso2022jp': return 'ISO-2022-JP'; case 'csiso2022jp2': case 'iso2022jp2': return 'ISO-2022-JP-2'; case 'csiso2022kr': case 'iso2022kr': return 'ISO-2022-KR'; case 'cswindows30latin1': case 'iso88591windows30latin1': return 'ISO-8859-1-Windows-3.0-Latin-1'; case 'cswindows31latin1': case 'iso88591windows31latin1': return 'ISO-8859-1-Windows-3.1-Latin-1'; case 'csisolatin2': case 'iso88592': case 'iso885921987': case 'isoir101': case 'l2': case 'latin2': return 'ISO-8859-2'; case 'cswindows31latin2': case 'iso88592windowslatin2': return 'ISO-8859-2-Windows-Latin-2'; case 'csisolatin3': case 'iso88593': case 'iso885931988': case 'isoir109': case 'l3': case 'latin3': return 'ISO-8859-3'; case 'csisolatin4': case 'iso88594': case 'iso885941988': case 'isoir110': case 'l4': case 'latin4': return 'ISO-8859-4'; case 'csisolatincyrillic': case 'cyrillic': case 'iso88595': case 'iso885951988': case 'isoir144': return 'ISO-8859-5'; case 'arabic': case 'asmo708': case 'csisolatinarabic': case 'ecma114': case 'iso88596': case 'iso885961987': case 'isoir127': return 'ISO-8859-6'; case 'csiso88596e': case 'iso88596e': return 'ISO-8859-6-E'; case 'csiso88596i': case 'iso88596i': return 'ISO-8859-6-I'; case 'csisolatingreek': case 'ecma118': case 'elot928': case 'greek': case 'greek8': case 'iso88597': case 'iso885971987': case 'isoir126': return 'ISO-8859-7'; case 'csisolatinhebrew': case 'hebrew': case 'iso88598': case 'iso885981988': case 'isoir138': return 'ISO-8859-8'; case 'csiso88598e': case 'iso88598e': return 'ISO-8859-8-E'; case 'csiso88598i': case 'iso88598i': return 'ISO-8859-8-I'; case 'cswindows31latin5': case 'iso88599windowslatin5': return 'ISO-8859-9-Windows-Latin-5'; case 'csisolatin6': case 'iso885910': case 'iso8859101992': case 'isoir157': case 'l6': case 'latin6': return 'ISO-8859-10'; case 'iso885913': return 'ISO-8859-13'; case 'iso885914': case 'iso8859141998': case 'isoceltic': case 'isoir199': case 'l8': case 'latin8': return 'ISO-8859-14'; case 'iso885915': case 'latin9': return 'ISO-8859-15'; case 'iso885916': case 'iso8859162001': case 'isoir226': case 'l10': case 'latin10': return 'ISO-8859-16'; case 'iso10646j1': return 'ISO-10646-J-1'; case 'csunicode': case 'iso10646ucs2': return 'ISO-10646-UCS-2'; case 'csucs4': case 'iso10646ucs4': return 'ISO-10646-UCS-4'; case 'csunicodeascii': case 'iso10646ucsbasic': return 'ISO-10646-UCS-Basic'; case 'csunicodelatin1': case 'iso10646': case 'iso10646unicodelatin1': return 'ISO-10646-Unicode-Latin1'; case 'csiso10646utf1': case 'iso10646utf1': return 'ISO-10646-UTF-1'; case 'csiso115481': case 'iso115481': case 'isotr115481': return 'ISO-11548-1'; case 'csiso90': case 'isoir90': return 'iso-ir-90'; case 'csunicodeibm1261': case 'isounicodeibm1261': return 'ISO-Unicode-IBM-1261'; case 'csunicodeibm1264': case 'isounicodeibm1264': return 'ISO-Unicode-IBM-1264'; case 'csunicodeibm1265': case 'isounicodeibm1265': return 'ISO-Unicode-IBM-1265'; case 'csunicodeibm1268': case 'isounicodeibm1268': return 'ISO-Unicode-IBM-1268'; case 'csunicodeibm1276': case 'isounicodeibm1276': return 'ISO-Unicode-IBM-1276'; case 'csiso646basic1983': case 'iso646basic1983': case 'ref': return 'ISO_646.basic:1983'; case 'csiso2intlrefversion': case 'irv': case 'iso646irv1983': case 'isoir2': return 'ISO_646.irv:1983'; case 'csiso2033': case 'e13b': case 'iso20331983': case 'isoir98': return 'ISO_2033-1983'; case 'csiso5427cyrillic': case 'iso5427': case 'isoir37': return 'ISO_5427'; case 'iso5427cyrillic1981': case 'iso54271981': case 'isoir54': return 'ISO_5427:1981'; case 'csiso5428greek': case 'iso54281980': case 'isoir55': return 'ISO_5428:1980'; case 'csiso6937add': case 'iso6937225': case 'isoir152': return 'ISO_6937-2-25'; case 'csisotextcomm': case 'iso69372add': case 'isoir142': return 'ISO_6937-2-add'; case 'csiso8859supp': case 'iso8859supp': case 'isoir154': case 'latin125': return 'ISO_8859-supp'; case 'csiso10367box': case 'iso10367box': case 'isoir155': return 'ISO_10367-box'; case 'csiso15italian': case 'iso646it': case 'isoir15': case 'it': return 'IT'; case 'csiso13jisc6220jp': case 'isoir13': case 'jisc62201969': case 'jisc62201969jp': case 'katakana': case 'x2017': return 'JIS_C6220-1969-jp'; case 'csiso14jisc6220ro': case 'iso646jp': case 'isoir14': case 'jisc62201969ro': case 'jp': return 'JIS_C6220-1969-ro'; case 'csiso42jisc62261978': case 'isoir42': case 'jisc62261978': return 'JIS_C6226-1978'; case 'csiso87jisx208': case 'isoir87': case 'jisc62261983': case 'jisx2081983': case 'x208': return 'JIS_C6226-1983'; case 'csiso91jisc62291984a': case 'isoir91': case 'jisc62291984a': case 'jpocra': return 'JIS_C6229-1984-a'; case 'csiso92jisc62991984b': case 'iso646jpocrb': case 'isoir92': case 'jisc62291984b': case 'jpocrb': return 'JIS_C6229-1984-b'; case 'csiso93jis62291984badd': case 'isoir93': case 'jisc62291984badd': case 'jpocrbadd': return 'JIS_C6229-1984-b-add'; case 'csiso94jis62291984hand': case 'isoir94': case 'jisc62291984hand': case 'jpocrhand': return 'JIS_C6229-1984-hand'; case 'csiso95jis62291984handadd': case 'isoir95': case 'jisc62291984handadd': case 'jpocrhandadd': return 'JIS_C6229-1984-hand-add'; case 'csiso96jisc62291984kana': case 'isoir96': case 'jisc62291984kana': return 'JIS_C6229-1984-kana'; case 'csjisencoding': case 'jisencoding': return 'JIS_Encoding'; case 'cshalfwidthkatakana': case 'jisx201': case 'x201': return 'JIS_X0201'; case 'csiso159jisx2121990': case 'isoir159': case 'jisx2121990': case 'x212': return 'JIS_X0212-1990'; case 'csiso141jusib1002': case 'iso646yu': case 'isoir141': case 'js': case 'jusib1002': case 'yu': return 'JUS_I.B1.002'; case 'csiso147macedonian': case 'isoir147': case 'jusib1003mac': case 'macedonian': return 'JUS_I.B1.003-mac'; case 'csiso146serbian': case 'isoir146': case 'jusib1003serb': case 'serbian': return 'JUS_I.B1.003-serb'; case 'koi7switched': return 'KOI7-switched'; case 'cskoi8r': case 'koi8r': return 'KOI8-R'; case 'koi8u': return 'KOI8-U'; case 'csksc5636': case 'iso646kr': case 'ksc5636': return 'KSC5636'; case 'cskz1048': case 'kz1048': case 'rk1048': case 'strk10482002': return 'KZ-1048'; case 'csiso19latingreek': case 'isoir19': case 'latingreek': return 'latin-greek'; case 'csiso27latingreek1': case 'isoir27': case 'latingreek1': return 'Latin-greek-1'; case 'csiso158lap': case 'isoir158': case 'lap': case 'latinlap': return 'latin-lap'; case 'csmacintosh': case 'mac': case 'macintosh': return 'macintosh'; case 'csmicrosoftpublishing': case 'microsoftpublishing': return 'Microsoft-Publishing'; case 'csmnem': case 'mnem': return 'MNEM'; case 'csmnemonic': case 'mnemonic': return 'MNEMONIC'; case 'csiso86hungarian': case 'hu': case 'iso646hu': case 'isoir86': case 'msz77953': return 'MSZ_7795.3'; case 'csnatsdano': case 'isoir91': case 'natsdano': return 'NATS-DANO'; case 'csnatsdanoadd': case 'isoir92': case 'natsdanoadd': return 'NATS-DANO-ADD'; case 'csnatssefi': case 'isoir81': case 'natssefi': return 'NATS-SEFI'; case 'csnatssefiadd': case 'isoir82': case 'natssefiadd': return 'NATS-SEFI-ADD'; case 'csiso151cuba': case 'cuba': case 'iso646cu': case 'isoir151': case 'ncnc1081': return 'NC_NC00-10:81'; case 'csiso69french': case 'fr': case 'iso646fr': case 'isoir69': case 'nfz62010': return 'NF_Z_62-010'; case 'csiso25french': case 'iso646fr1': case 'isoir25': case 'nfz620101973': return 'NF_Z_62-010_(1973)'; case 'csiso60danishnorwegian': case 'csiso60norwegian1': case 'iso646no': case 'isoir60': case 'no': case 'ns45511': return 'NS_4551-1'; case 'csiso61norwegian2': case 'iso646no2': case 'isoir61': case 'no2': case 'ns45512': return 'NS_4551-2'; case 'osdebcdicdf3irv': return 'OSD_EBCDIC_DF03_IRV'; case 'osdebcdicdf41': return 'OSD_EBCDIC_DF04_1'; case 'osdebcdicdf415': return 'OSD_EBCDIC_DF04_15'; case 'cspc8danishnorwegian': case 'pc8danishnorwegian': return 'PC8-Danish-Norwegian'; case 'cspc8turkish': case 'pc8turkish': return 'PC8-Turkish'; case 'csiso16portuguese': case 'iso646pt': case 'isoir16': case 'pt': return 'PT'; case 'csiso84portuguese2': case 'iso646pt2': case 'isoir84': case 'pt2': return 'PT2'; case 'cp154': case 'csptcp154': case 'cyrillicasian': case 'pt154': case 'ptcp154': return 'PTCP154'; case 'scsu': return 'SCSU'; case 'csiso10swedish': case 'fi': case 'iso646fi': case 'iso646se': case 'isoir10': case 'se': case 'sen850200b': return 'SEN_850200_B'; case 'csiso11swedishfornames': case 'iso646se2': case 'isoir11': case 'se2': case 'sen850200c': return 'SEN_850200_C'; case 'csiso102t617bit': case 'isoir102': case 't617bit': return 'T.61-7bit'; case 'csiso103t618bit': case 'isoir103': case 't61': case 't618bit': return 'T.61-8bit'; case 'csiso128t101g2': case 'isoir128': case 't101g2': return 'T.101-G2'; case 'cstscii': case 'tscii': return 'TSCII'; case 'csunicode11': case 'unicode11': return 'UNICODE-1-1'; case 'csunicode11utf7': case 'unicode11utf7': return 'UNICODE-1-1-UTF-7'; case 'csunknown8bit': case 'unknown8bit': return 'UNKNOWN-8BIT'; case 'ansix341968': case 'ansix341986': case 'ascii': case 'cp367': case 'csascii': case 'ibm367': case 'iso646irv1991': case 'iso646us': case 'isoir6': case 'us': case 'usascii': return 'US-ASCII'; case 'csusdk': case 'usdk': return 'us-dk'; case 'utf7': return 'UTF-7'; case 'utf8': return 'UTF-8'; case 'utf16': return 'UTF-16'; case 'utf16be': return 'UTF-16BE'; case 'utf16le': return 'UTF-16LE'; case 'utf32': return 'UTF-32'; case 'utf32be': return 'UTF-32BE'; case 'utf32le': return 'UTF-32LE'; case 'csventurainternational': case 'venturainternational': return 'Ventura-International'; case 'csventuramath': case 'venturamath': return 'Ventura-Math'; case 'csventuraus': case 'venturaus': return 'Ventura-US'; case 'csiso70videotexsupp1': case 'isoir70': case 'videotexsuppl': return 'videotex-suppl'; case 'csviqr': case 'viqr': return 'VIQR'; case 'csviscii': case 'viscii': return 'VISCII'; case 'csshiftjis': case 'cswindows31j': case 'mskanji': case 'shiftjis': case 'windows31j': return 'Windows-31J'; case 'iso885911': case 'tis620': return 'windows-874'; case 'cseuckr': case 'csksc56011987': case 'euckr': case 'isoir149': case 'korean': case 'ksc5601': case 'ksc56011987': case 'ksc56011989': case 'windows949': return 'windows-949'; case 'windows1250': return 'windows-1250'; case 'windows1251': return 'windows-1251'; case 'cp819': case 'csisolatin1': case 'ibm819': case 'iso88591': case 'iso885911987': case 'isoir100': case 'l1': case 'latin1': case 'windows1252': return 'windows-1252'; case 'windows1253': return 'windows-1253'; case 'csisolatin5': case 'iso88599': case 'iso885991989': case 'isoir148': case 'l5': case 'latin5': case 'windows1254': return 'windows-1254'; case 'windows1255': return 'windows-1255'; case 'windows1256': return 'windows-1256'; case 'windows1257': return 'windows-1257'; case 'windows1258': return 'windows-1258'; default: return $charset; } } public static function get_curl_version() { if (is_array($curl = curl_version())) { $curl = $curl['version']; } elseif (substr($curl, 0, 5) === 'curl/') { $curl = substr($curl, 5, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 5)); } elseif (substr($curl, 0, 8) === 'libcurl/') { $curl = substr($curl, 8, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 8)); } else { $curl = 0; } return $curl; } /** * Strip HTML comments * * @param string $data Data to strip comments from * @return string Comment stripped string */ public static function strip_comments($data) { $output = ''; while (($start = strpos($data, '<!--')) !== false) { $output .= substr($data, 0, $start); if (($end = strpos($data, '-->', $start)) !== false) { $data = substr_replace($data, '', 0, $end + 3); } else { $data = ''; } } return $output . $data; } public static function parse_date($dt) { $parser = SimplePie_Parse_Date::get(); return $parser->parse($dt); } /** * Decode HTML entities * * @deprecated Use DOMDocument instead * @param string $data Input data * @return string Output data */ public static function entities_decode($data) { $decoder = new SimplePie_Decode_HTML_Entities($data); return $decoder->parse(); } /** * Remove RFC822 comments * * @param string $data Data to strip comments from * @return string Comment stripped string */ public static function uncomment_rfc822($string) { $string = (string) $string; $position = 0; $length = strlen($string); $depth = 0; $output = ''; while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) { $output .= substr($string, $position, $pos - $position); $position = $pos + 1; if ($string[$pos - 1] !== '\\') { $depth++; while ($depth && $position < $length) { $position += strcspn($string, '()', $position); if ($string[$position - 1] === '\\') { $position++; continue; } elseif (isset($string[$position])) { switch ($string[$position]) { case '(': $depth++; break; case ')': $depth--; break; } $position++; } else { break; } } } else { $output .= '('; } } $output .= substr($string, $position); return $output; } public static function parse_mime($mime) { if (($pos = strpos($mime, ';')) === false) { return trim($mime); } else { return trim(substr($mime, 0, $pos)); } } public static function atom_03_construct_type($attribs) { if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode']) === 'base64')) { $mode = SIMPLEPIE_CONSTRUCT_BASE64; } else { $mode = SIMPLEPIE_CONSTRUCT_NONE; } if (isset($attribs['']['type'])) { switch (strtolower(trim($attribs['']['type']))) { case 'text': case 'text/plain': return SIMPLEPIE_CONSTRUCT_TEXT | $mode; case 'html': case 'text/html': return SIMPLEPIE_CONSTRUCT_HTML | $mode; case 'xhtml': case 'application/xhtml+xml': return SIMPLEPIE_CONSTRUCT_XHTML | $mode; default: return SIMPLEPIE_CONSTRUCT_NONE | $mode; } } else { return SIMPLEPIE_CONSTRUCT_TEXT | $mode; } } public static function atom_10_construct_type($attribs) { if (isset($attribs['']['type'])) { switch (strtolower(trim($attribs['']['type']))) { case 'text': return SIMPLEPIE_CONSTRUCT_TEXT; case 'html': return SIMPLEPIE_CONSTRUCT_HTML; case 'xhtml': return SIMPLEPIE_CONSTRUCT_XHTML; default: return SIMPLEPIE_CONSTRUCT_NONE; } } return SIMPLEPIE_CONSTRUCT_TEXT; } public static function atom_10_content_construct_type($attribs) { if (isset($attribs['']['type'])) { $type = strtolower(trim($attribs['']['type'])); switch ($type) { case 'text': return SIMPLEPIE_CONSTRUCT_TEXT; case 'html': return SIMPLEPIE_CONSTRUCT_HTML; case 'xhtml': return SIMPLEPIE_CONSTRUCT_XHTML; } if (in_array(substr($type, -4), array('+xml', '/xml')) || substr($type, 0, 5) === 'text/') { return SIMPLEPIE_CONSTRUCT_NONE; } else { return SIMPLEPIE_CONSTRUCT_BASE64; } } else { return SIMPLEPIE_CONSTRUCT_TEXT; } } public static function is_isegment_nz_nc($string) { return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string); } public static function space_seperated_tokens($string) { $space_characters = "\x20\x09\x0A\x0B\x0C\x0D"; $string_length = strlen($string); $position = strspn($string, $space_characters); $tokens = array(); while ($position < $string_length) { $len = strcspn($string, $space_characters, $position); $tokens[] = substr($string, $position, $len); $position += $len; $position += strspn($string, $space_characters, $position); } return $tokens; } /** * Converts a unicode codepoint to a UTF-8 character * * @static * @param int $codepoint Unicode codepoint * @return string UTF-8 character */ public static function codepoint_to_utf8($codepoint) { $codepoint = (int) $codepoint; if ($codepoint < 0) { return false; } else if ($codepoint <= 0x7f) { return chr($codepoint); } else if ($codepoint <= 0x7ff) { return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f)); } else if ($codepoint <= 0xffff) { return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); } else if ($codepoint <= 0x10ffff) { return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); } else { // U+FFFD REPLACEMENT CHARACTER return "\xEF\xBF\xBD"; } } /** * Similar to parse_str() * * Returns an associative array of name/value pairs, where the value is an * array of values that have used the same name * * @static * @param string $str The input string. * @return array */ public static function parse_str($str) { $return = array(); $str = explode('&', $str); foreach ($str as $section) { if (strpos($section, '=') !== false) { list($name, $value) = explode('=', $section, 2); $return[urldecode($name)][] = urldecode($value); } else { $return[urldecode($section)][] = null; } } return $return; } /** * Detect XML encoding, as per XML 1.0 Appendix F.1 * * @todo Add support for EBCDIC * @param string $data XML data * @param SimplePie_Registry $registry Class registry * @return array Possible encodings */ public static function xml_encoding($data, $registry) { // UTF-32 Big Endian BOM if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") { $encoding[] = 'UTF-32BE'; } // UTF-32 Little Endian BOM elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") { $encoding[] = 'UTF-32LE'; } // UTF-16 Big Endian BOM elseif (substr($data, 0, 2) === "\xFE\xFF") { $encoding[] = 'UTF-16BE'; } // UTF-16 Little Endian BOM elseif (substr($data, 0, 2) === "\xFF\xFE") { $encoding[] = 'UTF-16LE'; } // UTF-8 BOM elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") { $encoding[] = 'UTF-8'; } // UTF-32 Big Endian Without BOM elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C") { if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E")) { $parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8'))); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-32BE'; } // UTF-32 Little Endian Without BOM elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00") { if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00")) { $parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8'))); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-32LE'; } // UTF-16 Big Endian Without BOM elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C") { if ($pos = strpos($data, "\x00\x3F\x00\x3E")) { $parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8'))); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-16BE'; } // UTF-16 Little Endian Without BOM elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00") { if ($pos = strpos($data, "\x3F\x00\x3E\x00")) { $parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8'))); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-16LE'; } // US-ASCII (or superset) elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C") { if ($pos = strpos($data, "\x3F\x3E")) { $parser = $registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5))); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-8'; } // Fallback to UTF-8 else { $encoding[] = 'UTF-8'; } return $encoding; } public static function output_javascript() { if (function_exists('ob_gzhandler')) { ob_start('ob_gzhandler'); } header('Content-type: text/javascript; charset: UTF-8'); header('Cache-Control: must-revalidate'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days ?> function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) { if (placeholder != '') { document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" href="'+link+'" src="'+placeholder+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="false" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>'); } else { document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" src="'+link+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="true" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>'); } } function embed_flash(bgcolor, width, height, link, loop, type) { document.writeln('<embed src="'+link+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="'+type+'" quality="high" width="'+width+'" height="'+height+'" bgcolor="'+bgcolor+'" loop="'+loop+'"></embed>'); } function embed_flv(width, height, link, placeholder, loop, player) { document.writeln('<embed src="'+player+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="'+width+'" height="'+height+'" wmode="transparent" flashvars="file='+link+'&autostart=false&repeat='+loop+'&showdigits=true&showfsbutton=false"></embed>'); } function embed_wmedia(width, height, link) { document.writeln('<embed type="application/x-mplayer2" src="'+link+'" autosize="1" width="'+width+'" height="'+height+'" showcontrols="1" showstatusbar="0" showdisplay="0" autostart="0"></embed>'); } <?php } /** * Get the SimplePie build timestamp * * Uses the git index if it exists, otherwise uses the modification time * of the newest file. */ public static function get_build() { $root = dirname(dirname(__FILE__)); if (file_exists($root . '/.git/index')) { return filemtime($root . '/.git/index'); } elseif (file_exists($root . '/SimplePie')) { $time = 0; foreach (glob($root . '/SimplePie/*.php') as $file) { if (($mtime = filemtime($file)) > $time) { $time = $mtime; } } return $time; } elseif (file_exists(dirname(__FILE__) . '/Core.php')) { return filemtime(dirname(__FILE__) . '/Core.php'); } else { return filemtime(__FILE__); } } /** * Format debugging information */ public static function debug(&$sp) { $info = 'SimplePie ' . SIMPLEPIE_VERSION . ' Build ' . SIMPLEPIE_BUILD . "\n"; $info .= 'PHP ' . PHP_VERSION . "\n"; if ($sp->error() !== null) { $info .= 'Error occurred: ' . $sp->error() . "\n"; } else { $info .= "No error found.\n"; } $info .= "Extensions:\n"; $extensions = array('pcre', 'curl', 'zlib', 'mbstring', 'iconv', 'xmlreader', 'xml'); foreach ($extensions as $ext) { if (extension_loaded($ext)) { $info .= " $ext loaded\n"; switch ($ext) { case 'pcre': $info .= ' Version ' . PCRE_VERSION . "\n"; break; case 'curl': $version = curl_version(); $info .= ' Version ' . $version['version'] . "\n"; break; case 'mbstring': $info .= ' Overloading: ' . mb_get_info('func_overload') . "\n"; break; case 'iconv': $info .= ' Version ' . ICONV_VERSION . "\n"; break; case 'xml': $info .= ' Version ' . LIBXML_DOTTED_VERSION . "\n"; break; } } else { $info .= " $ext not loaded\n"; } } return $info; } public static function silence_errors($num, $str) { // No-op } } ================================================ FILE: libraries/simplepie/library/SimplePie/Net/IPv6.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Class to validate and to work with IPv6 addresses. * * @package SimplePie * @subpackage HTTP * @copyright 2003-2005 The PHP Group * @license http://www.opensource.org/licenses/bsd-license.php * @link http://pear.php.net/package/Net_IPv6 * @author Alexander Merz <alexander.merz@web.de> * @author elfrink at introweb dot nl * @author Josh Peck <jmp at joshpeck dot org> * @author Geoffrey Sneddon <geoffers@gmail.com> */ class SimplePie_Net_IPv6 { /** * Uncompresses an IPv6 address * * RFC 4291 allows you to compress concecutive zero pieces in an address to * '::'. This method expects a valid IPv6 address and expands the '::' to * the required number of zero pieces. * * Example: FF01::101 -> FF01:0:0:0:0:0:0:101 * ::1 -> 0:0:0:0:0:0:0:1 * * @author Alexander Merz <alexander.merz@web.de> * @author elfrink at introweb dot nl * @author Josh Peck <jmp at joshpeck dot org> * @copyright 2003-2005 The PHP Group * @license http://www.opensource.org/licenses/bsd-license.php * @param string $ip An IPv6 address * @return string The uncompressed IPv6 address */ public static function uncompress($ip) { $c1 = -1; $c2 = -1; if (substr_count($ip, '::') === 1) { list($ip1, $ip2) = explode('::', $ip); if ($ip1 === '') { $c1 = -1; } else { $c1 = substr_count($ip1, ':'); } if ($ip2 === '') { $c2 = -1; } else { $c2 = substr_count($ip2, ':'); } if (strpos($ip2, '.') !== false) { $c2++; } // :: if ($c1 === -1 && $c2 === -1) { $ip = '0:0:0:0:0:0:0:0'; } // ::xxx else if ($c1 === -1) { $fill = str_repeat('0:', 7 - $c2); $ip = str_replace('::', $fill, $ip); } // xxx:: else if ($c2 === -1) { $fill = str_repeat(':0', 7 - $c1); $ip = str_replace('::', $fill, $ip); } // xxx::xxx else { $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); $ip = str_replace('::', $fill, $ip); } } return $ip; } /** * Compresses an IPv6 address * * RFC 4291 allows you to compress concecutive zero pieces in an address to * '::'. This method expects a valid IPv6 address and compresses consecutive * zero pieces to '::'. * * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 * 0:0:0:0:0:0:0:1 -> ::1 * * @see uncompress() * @param string $ip An IPv6 address * @return string The compressed IPv6 address */ public static function compress($ip) { // Prepare the IP to be compressed $ip = self::uncompress($ip); $ip_parts = self::split_v6_v4($ip); // Replace all leading zeros $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); // Find bunches of zeros if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) { $max = 0; $pos = null; foreach ($matches[0] as $match) { if (strlen($match[0]) > $max) { $max = strlen($match[0]); $pos = $match[1]; } } $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); } if ($ip_parts[1] !== '') { return implode(':', $ip_parts); } else { return $ip_parts[0]; } } /** * Splits an IPv6 address into the IPv6 and IPv4 representation parts * * RFC 4291 allows you to represent the last two parts of an IPv6 address * using the standard IPv4 representation * * Example: 0:0:0:0:0:0:13.1.68.3 * 0:0:0:0:0:FFFF:129.144.52.38 * * @param string $ip An IPv6 address * @return array [0] contains the IPv6 represented part, and [1] the IPv4 represented part */ private static function split_v6_v4($ip) { if (strpos($ip, '.') !== false) { $pos = strrpos($ip, ':'); $ipv6_part = substr($ip, 0, $pos); $ipv4_part = substr($ip, $pos + 1); return array($ipv6_part, $ipv4_part); } else { return array($ip, ''); } } /** * Checks an IPv6 address * * Checks if the given IP is a valid IPv6 address * * @param string $ip An IPv6 address * @return bool true if $ip is a valid IPv6 address */ public static function check_ipv6($ip) { $ip = self::uncompress($ip); list($ipv6, $ipv4) = self::split_v6_v4($ip); $ipv6 = explode(':', $ipv6); $ipv4 = explode('.', $ipv4); if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) { foreach ($ipv6 as $ipv6_part) { // The section can't be empty if ($ipv6_part === '') return false; // Nor can it be over four characters if (strlen($ipv6_part) > 4) return false; // Remove leading zeros (this is safe because of the above) $ipv6_part = ltrim($ipv6_part, '0'); if ($ipv6_part === '') $ipv6_part = '0'; // Check the value is valid $value = hexdec($ipv6_part); if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) return false; } if (count($ipv4) === 4) { foreach ($ipv4 as $ipv4_part) { $value = (int) $ipv4_part; if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) return false; } } return true; } else { return false; } } /** * Checks if the given IP is a valid IPv6 address * * @codeCoverageIgnore * @deprecated Use {@see SimplePie_Net_IPv6::check_ipv6()} instead * @see check_ipv6 * @param string $ip An IPv6 address * @return bool true if $ip is a valid IPv6 address */ public static function checkIPv6($ip) { return self::check_ipv6($ip); } } ================================================ FILE: libraries/simplepie/library/SimplePie/Parse/Date.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Date Parser * * @package SimplePie * @subpackage Parsing */ class SimplePie_Parse_Date { /** * Input data * * @access protected * @var string */ var $date; /** * List of days, calendar day name => ordinal day number in the week * * @access protected * @var array */ var $day = array( // English 'mon' => 1, 'monday' => 1, 'tue' => 2, 'tuesday' => 2, 'wed' => 3, 'wednesday' => 3, 'thu' => 4, 'thursday' => 4, 'fri' => 5, 'friday' => 5, 'sat' => 6, 'saturday' => 6, 'sun' => 7, 'sunday' => 7, // Dutch 'maandag' => 1, 'dinsdag' => 2, 'woensdag' => 3, 'donderdag' => 4, 'vrijdag' => 5, 'zaterdag' => 6, 'zondag' => 7, // French 'lundi' => 1, 'mardi' => 2, 'mercredi' => 3, 'jeudi' => 4, 'vendredi' => 5, 'samedi' => 6, 'dimanche' => 7, // German 'montag' => 1, 'dienstag' => 2, 'mittwoch' => 3, 'donnerstag' => 4, 'freitag' => 5, 'samstag' => 6, 'sonnabend' => 6, 'sonntag' => 7, // Italian 'lunedì' => 1, 'martedì' => 2, 'mercoledì' => 3, 'giovedì' => 4, 'venerdì' => 5, 'sabato' => 6, 'domenica' => 7, // Spanish 'lunes' => 1, 'martes' => 2, 'miércoles' => 3, 'jueves' => 4, 'viernes' => 5, 'sábado' => 6, 'domingo' => 7, // Finnish 'maanantai' => 1, 'tiistai' => 2, 'keskiviikko' => 3, 'torstai' => 4, 'perjantai' => 5, 'lauantai' => 6, 'sunnuntai' => 7, // Hungarian 'hétfő' => 1, 'kedd' => 2, 'szerda' => 3, 'csütörtok' => 4, 'péntek' => 5, 'szombat' => 6, 'vasárnap' => 7, // Greek 'Δευ' => 1, 'Τρι' => 2, 'Τετ' => 3, 'Πεμ' => 4, 'Παρ' => 5, 'Σαβ' => 6, 'Κυρ' => 7, ); /** * List of months, calendar month name => calendar month number * * @access protected * @var array */ var $month = array( // English 'jan' => 1, 'january' => 1, 'feb' => 2, 'february' => 2, 'mar' => 3, 'march' => 3, 'apr' => 4, 'april' => 4, 'may' => 5, // No long form of May 'jun' => 6, 'june' => 6, 'jul' => 7, 'july' => 7, 'aug' => 8, 'august' => 8, 'sep' => 9, 'september' => 8, 'oct' => 10, 'october' => 10, 'nov' => 11, 'november' => 11, 'dec' => 12, 'december' => 12, // Dutch 'januari' => 1, 'februari' => 2, 'maart' => 3, 'april' => 4, 'mei' => 5, 'juni' => 6, 'juli' => 7, 'augustus' => 8, 'september' => 9, 'oktober' => 10, 'november' => 11, 'december' => 12, // French 'janvier' => 1, 'février' => 2, 'mars' => 3, 'avril' => 4, 'mai' => 5, 'juin' => 6, 'juillet' => 7, 'août' => 8, 'septembre' => 9, 'octobre' => 10, 'novembre' => 11, 'décembre' => 12, // German 'januar' => 1, 'februar' => 2, 'märz' => 3, 'april' => 4, 'mai' => 5, 'juni' => 6, 'juli' => 7, 'august' => 8, 'september' => 9, 'oktober' => 10, 'november' => 11, 'dezember' => 12, // Italian 'gennaio' => 1, 'febbraio' => 2, 'marzo' => 3, 'aprile' => 4, 'maggio' => 5, 'giugno' => 6, 'luglio' => 7, 'agosto' => 8, 'settembre' => 9, 'ottobre' => 10, 'novembre' => 11, 'dicembre' => 12, // Spanish 'enero' => 1, 'febrero' => 2, 'marzo' => 3, 'abril' => 4, 'mayo' => 5, 'junio' => 6, 'julio' => 7, 'agosto' => 8, 'septiembre' => 9, 'setiembre' => 9, 'octubre' => 10, 'noviembre' => 11, 'diciembre' => 12, // Finnish 'tammikuu' => 1, 'helmikuu' => 2, 'maaliskuu' => 3, 'huhtikuu' => 4, 'toukokuu' => 5, 'kesäkuu' => 6, 'heinäkuu' => 7, 'elokuu' => 8, 'suuskuu' => 9, 'lokakuu' => 10, 'marras' => 11, 'joulukuu' => 12, // Hungarian 'január' => 1, 'február' => 2, 'március' => 3, 'április' => 4, 'május' => 5, 'június' => 6, 'július' => 7, 'augusztus' => 8, 'szeptember' => 9, 'október' => 10, 'november' => 11, 'december' => 12, // Greek 'Ιαν' => 1, 'Φεβ' => 2, 'Μάώ' => 3, 'Μαώ' => 3, 'Απρ' => 4, 'Μάι' => 5, 'Μαϊ' => 5, 'Μαι' => 5, 'Ιούν' => 6, 'Ιον' => 6, 'Ιούλ' => 7, 'Ιολ' => 7, 'Αύγ' => 8, 'Αυγ' => 8, 'Σεπ' => 9, 'Οκτ' => 10, 'Νοέ' => 11, 'Δεκ' => 12, ); /** * List of timezones, abbreviation => offset from UTC * * @access protected * @var array */ var $timezone = array( 'ACDT' => 37800, 'ACIT' => 28800, 'ACST' => 34200, 'ACT' => -18000, 'ACWDT' => 35100, 'ACWST' => 31500, 'AEDT' => 39600, 'AEST' => 36000, 'AFT' => 16200, 'AKDT' => -28800, 'AKST' => -32400, 'AMDT' => 18000, 'AMT' => -14400, 'ANAST' => 46800, 'ANAT' => 43200, 'ART' => -10800, 'AZOST' => -3600, 'AZST' => 18000, 'AZT' => 14400, 'BIOT' => 21600, 'BIT' => -43200, 'BOT' => -14400, 'BRST' => -7200, 'BRT' => -10800, 'BST' => 3600, 'BTT' => 21600, 'CAST' => 18000, 'CAT' => 7200, 'CCT' => 23400, 'CDT' => -18000, 'CEDT' => 7200, 'CET' => 3600, 'CGST' => -7200, 'CGT' => -10800, 'CHADT' => 49500, 'CHAST' => 45900, 'CIST' => -28800, 'CKT' => -36000, 'CLDT' => -10800, 'CLST' => -14400, 'COT' => -18000, 'CST' => -21600, 'CVT' => -3600, 'CXT' => 25200, 'DAVT' => 25200, 'DTAT' => 36000, 'EADT' => -18000, 'EAST' => -21600, 'EAT' => 10800, 'ECT' => -18000, 'EDT' => -14400, 'EEST' => 10800, 'EET' => 7200, 'EGT' => -3600, 'EKST' => 21600, 'EST' => -18000, 'FJT' => 43200, 'FKDT' => -10800, 'FKST' => -14400, 'FNT' => -7200, 'GALT' => -21600, 'GEDT' => 14400, 'GEST' => 10800, 'GFT' => -10800, 'GILT' => 43200, 'GIT' => -32400, 'GST' => 14400, 'GST' => -7200, 'GYT' => -14400, 'HAA' => -10800, 'HAC' => -18000, 'HADT' => -32400, 'HAE' => -14400, 'HAP' => -25200, 'HAR' => -21600, 'HAST' => -36000, 'HAT' => -9000, 'HAY' => -28800, 'HKST' => 28800, 'HMT' => 18000, 'HNA' => -14400, 'HNC' => -21600, 'HNE' => -18000, 'HNP' => -28800, 'HNR' => -25200, 'HNT' => -12600, 'HNY' => -32400, 'IRDT' => 16200, 'IRKST' => 32400, 'IRKT' => 28800, 'IRST' => 12600, 'JFDT' => -10800, 'JFST' => -14400, 'JST' => 32400, 'KGST' => 21600, 'KGT' => 18000, 'KOST' => 39600, 'KOVST' => 28800, 'KOVT' => 25200, 'KRAST' => 28800, 'KRAT' => 25200, 'KST' => 32400, 'LHDT' => 39600, 'LHST' => 37800, 'LINT' => 50400, 'LKT' => 21600, 'MAGST' => 43200, 'MAGT' => 39600, 'MAWT' => 21600, 'MDT' => -21600, 'MESZ' => 7200, 'MEZ' => 3600, 'MHT' => 43200, 'MIT' => -34200, 'MNST' => 32400, 'MSDT' => 14400, 'MSST' => 10800, 'MST' => -25200, 'MUT' => 14400, 'MVT' => 18000, 'MYT' => 28800, 'NCT' => 39600, 'NDT' => -9000, 'NFT' => 41400, 'NMIT' => 36000, 'NOVST' => 25200, 'NOVT' => 21600, 'NPT' => 20700, 'NRT' => 43200, 'NST' => -12600, 'NUT' => -39600, 'NZDT' => 46800, 'NZST' => 43200, 'OMSST' => 25200, 'OMST' => 21600, 'PDT' => -25200, 'PET' => -18000, 'PETST' => 46800, 'PETT' => 43200, 'PGT' => 36000, 'PHOT' => 46800, 'PHT' => 28800, 'PKT' => 18000, 'PMDT' => -7200, 'PMST' => -10800, 'PONT' => 39600, 'PST' => -28800, 'PWT' => 32400, 'PYST' => -10800, 'PYT' => -14400, 'RET' => 14400, 'ROTT' => -10800, 'SAMST' => 18000, 'SAMT' => 14400, 'SAST' => 7200, 'SBT' => 39600, 'SCDT' => 46800, 'SCST' => 43200, 'SCT' => 14400, 'SEST' => 3600, 'SGT' => 28800, 'SIT' => 28800, 'SRT' => -10800, 'SST' => -39600, 'SYST' => 10800, 'SYT' => 7200, 'TFT' => 18000, 'THAT' => -36000, 'TJT' => 18000, 'TKT' => -36000, 'TMT' => 18000, 'TOT' => 46800, 'TPT' => 32400, 'TRUT' => 36000, 'TVT' => 43200, 'TWT' => 28800, 'UYST' => -7200, 'UYT' => -10800, 'UZT' => 18000, 'VET' => -14400, 'VLAST' => 39600, 'VLAT' => 36000, 'VOST' => 21600, 'VUT' => 39600, 'WAST' => 7200, 'WAT' => 3600, 'WDT' => 32400, 'WEST' => 3600, 'WFT' => 43200, 'WIB' => 25200, 'WIT' => 32400, 'WITA' => 28800, 'WKST' => 18000, 'WST' => 28800, 'YAKST' => 36000, 'YAKT' => 32400, 'YAPT' => 36000, 'YEKST' => 21600, 'YEKT' => 18000, ); /** * Cached PCRE for SimplePie_Parse_Date::$day * * @access protected * @var string */ var $day_pcre; /** * Cached PCRE for SimplePie_Parse_Date::$month * * @access protected * @var string */ var $month_pcre; /** * Array of user-added callback methods * * @access private * @var array */ var $built_in = array(); /** * Array of user-added callback methods * * @access private * @var array */ var $user = array(); /** * Create new SimplePie_Parse_Date object, and set self::day_pcre, * self::month_pcre, and self::built_in * * @access private */ public function __construct() { $this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')'; $this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')'; static $cache; if (!isset($cache[get_class($this)])) { $all_methods = get_class_methods($this); foreach ($all_methods as $method) { if (strtolower(substr($method, 0, 5)) === 'date_') { $cache[get_class($this)][] = $method; } } } foreach ($cache[get_class($this)] as $method) { $this->built_in[] = $method; } } /** * Get the object * * @access public */ public static function get() { static $object; if (!$object) { $object = new SimplePie_Parse_Date; } return $object; } /** * Parse a date * * @final * @access public * @param string $date Date to parse * @return int Timestamp corresponding to date string, or false on failure */ public function parse($date) { foreach ($this->user as $method) { if (($returned = call_user_func($method, $date)) !== false) { return $returned; } } foreach ($this->built_in as $method) { if (($returned = call_user_func(array($this, $method), $date)) !== false) { return $returned; } } return false; } /** * Add a callback method to parse a date * * @final * @access public * @param callback $callback */ public function add_callback($callback) { if (is_callable($callback)) { $this->user[] = $callback; } else { trigger_error('User-supplied function must be a valid callback', E_USER_WARNING); } } /** * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as * well as allowing any of upper or lower case "T", horizontal tabs, or * spaces to be used as the time seperator (including more than one)) * * @access protected * @return int Timestamp */ public function date_w3cdtf($date) { static $pcre; if (!$pcre) { $year = '([0-9]{4})'; $month = $day = $hour = $minute = $second = '([0-9]{2})'; $decimal = '([0-9]*)'; $zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))'; $pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/'; } if (preg_match($pcre, $date, $match)) { /* Capturing subpatterns: 1: Year 2: Month 3: Day 4: Hour 5: Minute 6: Second 7: Decimal fraction of a second 8: Zulu 9: Timezone ± 10: Timezone hours 11: Timezone minutes */ // Fill in empty matches for ($i = count($match); $i <= 3; $i++) { $match[$i] = '1'; } for ($i = count($match); $i <= 7; $i++) { $match[$i] = '0'; } // Numeric timezone if (isset($match[9]) && $match[9] !== '') { $timezone = $match[10] * 3600; $timezone += $match[11] * 60; if ($match[9] === '-') { $timezone = 0 - $timezone; } } else { $timezone = 0; } // Convert the number of seconds to an integer, taking decimals into account $second = round($match[6] + $match[7] / pow(10, strlen($match[7]))); return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone; } else { return false; } } /** * Remove RFC822 comments * * @access protected * @param string $data Data to strip comments from * @return string Comment stripped string */ public function remove_rfc2822_comments($string) { $string = (string) $string; $position = 0; $length = strlen($string); $depth = 0; $output = ''; while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) { $output .= substr($string, $position, $pos - $position); $position = $pos + 1; if ($string[$pos - 1] !== '\\') { $depth++; while ($depth && $position < $length) { $position += strcspn($string, '()', $position); if ($string[$position - 1] === '\\') { $position++; continue; } elseif (isset($string[$position])) { switch ($string[$position]) { case '(': $depth++; break; case ')': $depth--; break; } $position++; } else { break; } } } else { $output .= '('; } } $output .= substr($string, $position); return $output; } /** * Parse RFC2822's date format * * @access protected * @return int Timestamp */ public function date_rfc2822($date) { static $pcre; if (!$pcre) { $wsp = '[\x09\x20]'; $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)'; $optional_fws = $fws . '?'; $day_name = $this->day_pcre; $month = $this->month_pcre; $day = '([0-9]{1,2})'; $hour = $minute = $second = '([0-9]{2})'; $year = '([0-9]{2,4})'; $num_zone = '([+\-])([0-9]{2})([0-9]{2})'; $character_zone = '([A-Z]{1,5})'; $zone = '(?:' . $num_zone . '|' . $character_zone . ')'; $pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i'; } if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match)) { /* Capturing subpatterns: 1: Day name 2: Day 3: Month 4: Year 5: Hour 6: Minute 7: Second 8: Timezone ± 9: Timezone hours 10: Timezone minutes 11: Alphabetic timezone */ // Find the month number $month = $this->month[strtolower($match[3])]; // Numeric timezone if ($match[8] !== '') { $timezone = $match[9] * 3600; $timezone += $match[10] * 60; if ($match[8] === '-') { $timezone = 0 - $timezone; } } // Character timezone elseif (isset($this->timezone[strtoupper($match[11])])) { $timezone = $this->timezone[strtoupper($match[11])]; } // Assume everything else to be -0000 else { $timezone = 0; } // Deal with 2/3 digit years if ($match[4] < 50) { $match[4] += 2000; } elseif ($match[4] < 1000) { $match[4] += 1900; } // Second is optional, if it is empty set it to zero if ($match[7] !== '') { $second = $match[7]; } else { $second = 0; } return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone; } else { return false; } } /** * Parse RFC850's date format * * @access protected * @return int Timestamp */ public function date_rfc850($date) { static $pcre; if (!$pcre) { $space = '[\x09\x20]+'; $day_name = $this->day_pcre; $month = $this->month_pcre; $day = '([0-9]{1,2})'; $year = $hour = $minute = $second = '([0-9]{2})'; $zone = '([A-Z]{1,5})'; $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i'; } if (preg_match($pcre, $date, $match)) { /* Capturing subpatterns: 1: Day name 2: Day 3: Month 4: Year 5: Hour 6: Minute 7: Second 8: Timezone */ // Month $month = $this->month[strtolower($match[3])]; // Character timezone if (isset($this->timezone[strtoupper($match[8])])) { $timezone = $this->timezone[strtoupper($match[8])]; } // Assume everything else to be -0000 else { $timezone = 0; } // Deal with 2 digit year if ($match[4] < 50) { $match[4] += 2000; } else { $match[4] += 1900; } return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone; } else { return false; } } /** * Parse C99's asctime()'s date format * * @access protected * @return int Timestamp */ public function date_asctime($date) { static $pcre; if (!$pcre) { $space = '[\x09\x20]+'; $wday_name = $this->day_pcre; $mon_name = $this->month_pcre; $day = '([0-9]{1,2})'; $hour = $sec = $min = '([0-9]{2})'; $year = '([0-9]{4})'; $terminator = '\x0A?\x00?'; $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i'; } if (preg_match($pcre, $date, $match)) { /* Capturing subpatterns: 1: Day name 2: Month 3: Day 4: Hour 5: Minute 6: Second 7: Year */ $month = $this->month[strtolower($match[2])]; return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]); } else { return false; } } /** * Parse dates using strtotime() * * @access protected * @return int Timestamp */ public function date_strtotime($date) { $strtotime = strtotime($date); if ($strtotime === -1 || $strtotime === false) { return false; } else { return $strtotime; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Parser.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Parses XML into something sane * * * This class can be overloaded with {@see SimplePie::set_parser_class()} * * @package SimplePie * @subpackage Parsing */ class SimplePie_Parser { var $error_code; var $error_string; var $current_line; var $current_column; var $current_byte; var $separator = ' '; var $namespace = array(''); var $element = array(''); var $xml_base = array(''); var $xml_base_explicit = array(false); var $xml_lang = array(''); var $data = array(); var $datas = array(array()); var $current_xhtml_construct = -1; var $encoding; protected $registry; public function set_registry(SimplePie_Registry $registry) { $this->registry = $registry; } public function parse(&$data, $encoding) { // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character if (strtoupper($encoding) === 'US-ASCII') { $this->encoding = 'UTF-8'; } else { $this->encoding = $encoding; } // Strip BOM: // UTF-32 Big Endian BOM if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") { $data = substr($data, 4); } // UTF-32 Little Endian BOM elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") { $data = substr($data, 4); } // UTF-16 Big Endian BOM elseif (substr($data, 0, 2) === "\xFE\xFF") { $data = substr($data, 2); } // UTF-16 Little Endian BOM elseif (substr($data, 0, 2) === "\xFF\xFE") { $data = substr($data, 2); } // UTF-8 BOM elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") { $data = substr($data, 3); } if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) { $declaration = $this->registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5))); if ($declaration->parse()) { $data = substr($data, $pos + 2); $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; } else { $this->error_string = 'SimplePie bug! Please report this!'; return false; } } $return = true; static $xml_is_sane = null; if ($xml_is_sane === null) { $parser_check = xml_parser_create(); xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); xml_parser_free($parser_check); $xml_is_sane = isset($values[0]['value']); } // Create the parser if ($xml_is_sane) { $xml = xml_parser_create_ns($this->encoding, $this->separator); xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1); xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0); xml_set_object($xml, $this); xml_set_character_data_handler($xml, 'cdata'); xml_set_element_handler($xml, 'tag_open', 'tag_close'); // Parse! if (!xml_parse($xml, $data, true)) { $this->error_code = xml_get_error_code($xml); $this->error_string = xml_error_string($this->error_code); $return = false; } $this->current_line = xml_get_current_line_number($xml); $this->current_column = xml_get_current_column_number($xml); $this->current_byte = xml_get_current_byte_index($xml); xml_parser_free($xml); return $return; } else { libxml_clear_errors(); $xml = new XMLReader(); $xml->xml($data); while (@$xml->read()) { switch ($xml->nodeType) { case constant('XMLReader::END_ELEMENT'): if ($xml->namespaceURI !== '') { $tagName = $xml->namespaceURI . $this->separator . $xml->localName; } else { $tagName = $xml->localName; } $this->tag_close(null, $tagName); break; case constant('XMLReader::ELEMENT'): $empty = $xml->isEmptyElement; if ($xml->namespaceURI !== '') { $tagName = $xml->namespaceURI . $this->separator . $xml->localName; } else { $tagName = $xml->localName; } $attributes = array(); while ($xml->moveToNextAttribute()) { if ($xml->namespaceURI !== '') { $attrName = $xml->namespaceURI . $this->separator . $xml->localName; } else { $attrName = $xml->localName; } $attributes[$attrName] = $xml->value; } $this->tag_open(null, $tagName, $attributes); if ($empty) { $this->tag_close(null, $tagName); } break; case constant('XMLReader::TEXT'): case constant('XMLReader::CDATA'): $this->cdata(null, $xml->value); break; } } if ($error = libxml_get_last_error()) { $this->error_code = $error->code; $this->error_string = $error->message; $this->current_line = $error->line; $this->current_column = $error->column; return false; } else { return true; } } } public function get_error_code() { return $this->error_code; } public function get_error_string() { return $this->error_string; } public function get_current_line() { return $this->current_line; } public function get_current_column() { return $this->current_column; } public function get_current_byte() { return $this->current_byte; } public function get_data() { return $this->data; } public function tag_open($parser, $tag, $attributes) { list($this->namespace[], $this->element[]) = $this->split_ns($tag); $attribs = array(); foreach ($attributes as $name => $value) { list($attrib_namespace, $attribute) = $this->split_ns($name); $attribs[$attrib_namespace][$attribute] = $value; } if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['base'])) { $base = $this->registry->call('Misc', 'absolutize_url', array($attribs[SIMPLEPIE_NAMESPACE_XML]['base'], end($this->xml_base))); if ($base !== false) { $this->xml_base[] = $base; $this->xml_base_explicit[] = true; } } else { $this->xml_base[] = end($this->xml_base); $this->xml_base_explicit[] = end($this->xml_base_explicit); } if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['lang'])) { $this->xml_lang[] = $attribs[SIMPLEPIE_NAMESPACE_XML]['lang']; } else { $this->xml_lang[] = end($this->xml_lang); } if ($this->current_xhtml_construct >= 0) { $this->current_xhtml_construct++; if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML) { $this->data['data'] .= '<' . end($this->element); if (isset($attribs[''])) { foreach ($attribs[''] as $name => $value) { $this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"'; } } $this->data['data'] .= '>'; } } else { $this->datas[] =& $this->data; $this->data =& $this->data['child'][end($this->namespace)][end($this->element)][]; $this->data = array('data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang)); if ((end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_03 && in_array(end($this->element), array('title', 'tagline', 'copyright', 'info', 'summary', 'content')) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml') || (end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_10 && in_array(end($this->element), array('rights', 'subtitle', 'summary', 'info', 'title', 'content')) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml') || (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_20 && in_array(end($this->element), array('title'))) || (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_090 && in_array(end($this->element), array('title'))) || (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_10 && in_array(end($this->element), array('title')))) { $this->current_xhtml_construct = 0; } } } public function cdata($parser, $cdata) { if ($this->current_xhtml_construct >= 0) { $this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding); } else { $this->data['data'] .= $cdata; } } public function tag_close($parser, $tag) { if ($this->current_xhtml_construct >= 0) { $this->current_xhtml_construct--; if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML && !in_array(end($this->element), array('area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'))) { $this->data['data'] .= '</' . end($this->element) . '>'; } } if ($this->current_xhtml_construct === -1) { $this->data =& $this->datas[count($this->datas) - 1]; array_pop($this->datas); } array_pop($this->element); array_pop($this->namespace); array_pop($this->xml_base); array_pop($this->xml_base_explicit); array_pop($this->xml_lang); } public function split_ns($string) { static $cache = array(); if (!isset($cache[$string])) { if ($pos = strpos($string, $this->separator)) { static $separator_length; if (!$separator_length) { $separator_length = strlen($this->separator); } $namespace = substr($string, 0, $pos); $local_name = substr($string, $pos + $separator_length); if (strtolower($namespace) === SIMPLEPIE_NAMESPACE_ITUNES) { $namespace = SIMPLEPIE_NAMESPACE_ITUNES; } // Normalize the Media RSS namespaces if ($namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG || $namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG2 || $namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG3 || $namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG4 || $namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG5 ) { $namespace = SIMPLEPIE_NAMESPACE_MEDIARSS; } $cache[$string] = array($namespace, $local_name); } else { $cache[$string] = array('', $string); } } return $cache[$string]; } } ================================================ FILE: libraries/simplepie/library/SimplePie/Rating.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Handles `<media:rating>` or `<itunes:explicit>` tags as defined in Media RSS and iTunes RSS respectively * * Used by {@see SimplePie_Enclosure::get_rating()} and {@see SimplePie_Enclosure::get_ratings()} * * This class can be overloaded with {@see SimplePie::set_rating_class()} * * @package SimplePie * @subpackage API */ class SimplePie_Rating { /** * Rating scheme * * @var string * @see get_scheme() */ var $scheme; /** * Rating value * * @var string * @see get_value() */ var $value; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct($scheme = null, $value = null) { $this->scheme = $scheme; $this->value = $value; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the organizational scheme for the rating * * @return string|null */ public function get_scheme() { if ($this->scheme !== null) { return $this->scheme; } else { return null; } } /** * Get the value of the rating * * @return string|null */ public function get_value() { if ($this->value !== null) { return $this->value; } else { return null; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Registry.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Handles creating objects and calling methods * * Access this via {@see SimplePie::get_registry()} * * @package SimplePie */ class SimplePie_Registry { /** * Default class mapping * * Overriding classes *must* subclass these. * * @var array */ protected $default = array( 'Cache' => 'SimplePie_Cache', 'Locator' => 'SimplePie_Locator', 'Parser' => 'SimplePie_Parser', 'File' => 'SimplePie_File', 'Sanitize' => 'SimplePie_Sanitize', 'Item' => 'SimplePie_Item', 'Author' => 'SimplePie_Author', 'Category' => 'SimplePie_Category', 'Enclosure' => 'SimplePie_Enclosure', 'Caption' => 'SimplePie_Caption', 'Copyright' => 'SimplePie_Copyright', 'Credit' => 'SimplePie_Credit', 'Rating' => 'SimplePie_Rating', 'Restriction' => 'SimplePie_Restriction', 'Content_Type_Sniffer' => 'SimplePie_Content_Type_Sniffer', 'Source' => 'SimplePie_Source', 'Misc' => 'SimplePie_Misc', 'XML_Declaration_Parser' => 'SimplePie_XML_Declaration_Parser', 'Parse_Date' => 'SimplePie_Parse_Date', ); /** * Class mapping * * @see register() * @var array */ protected $classes = array(); /** * Legacy classes * * @see register() * @var array */ protected $legacy = array(); /** * Constructor * * No-op */ public function __construct() { } /** * Register a class * * @param string $type See {@see $default} for names * @param string $class Class name, must subclass the corresponding default * @param bool $legacy Whether to enable legacy support for this class * @return bool Successfulness */ public function register($type, $class, $legacy = false) { if (!is_subclass_of($class, $this->default[$type])) { return false; } $this->classes[$type] = $class; if ($legacy) { $this->legacy[] = $class; } return true; } /** * Get the class registered for a type * * Where possible, use {@see create()} or {@see call()} instead * * @param string $type * @return string|null */ public function get_class($type) { if (!empty($this->classes[$type])) { return $this->classes[$type]; } if (!empty($this->default[$type])) { return $this->default[$type]; } return null; } /** * Create a new instance of a given type * * @param string $type * @param array $parameters Parameters to pass to the constructor * @return object Instance of class */ public function &create($type, $parameters = array()) { $class = $this->get_class($type); if (in_array($class, $this->legacy)) { switch ($type) { case 'locator': // Legacy: file, timeout, useragent, file_class, max_checked_feeds, content_type_sniffer_class // Specified: file, timeout, useragent, max_checked_feeds $replacement = array($this->get_class('file'), $parameters[3], $this->get_class('content_type_sniffer')); array_splice($parameters, 3, 1, $replacement); break; } } if (!method_exists($class, '__construct')) { $instance = new $class; } else { $reflector = new ReflectionClass($class); $instance = $reflector->newInstanceArgs($parameters); } if (method_exists($instance, 'set_registry')) { $instance->set_registry($this); } return $instance; } /** * Call a static method for a type * * @param string $type * @param string $method * @param array $parameters * @return mixed */ public function &call($type, $method, $parameters = array()) { $class = $this->get_class($type); if (in_array($class, $this->legacy)) { switch ($type) { case 'Cache': // For backwards compatibility with old non-static // Cache::create() methods if ($method === 'get_handler') { $result = @call_user_func_array(array($class, 'create'), $parameters); return $result; } break; } } $result = call_user_func_array(array($class, $method), $parameters); return $result; } } ================================================ FILE: libraries/simplepie/library/SimplePie/Restriction.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Handles `<media:restriction>` as defined in Media RSS * * Used by {@see SimplePie_Enclosure::get_restriction()} and {@see SimplePie_Enclosure::get_restrictions()} * * This class can be overloaded with {@see SimplePie::set_restriction_class()} * * @package SimplePie * @subpackage API */ class SimplePie_Restriction { /** * Relationship ('allow'/'deny') * * @var string * @see get_relationship() */ var $relationship; /** * Type of restriction * * @var string * @see get_type() */ var $type; /** * Restricted values * * @var string * @see get_value() */ var $value; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct($relationship = null, $type = null, $value = null) { $this->relationship = $relationship; $this->type = $type; $this->value = $value; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the relationship * * @return string|null Either 'allow' or 'deny' */ public function get_relationship() { if ($this->relationship !== null) { return $this->relationship; } else { return null; } } /** * Get the type * * @return string|null */ public function get_type() { if ($this->type !== null) { return $this->type; } else { return null; } } /** * Get the list of restricted things * * @return string|null */ public function get_value() { if ($this->value !== null) { return $this->value; } else { return null; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Sanitize.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Used for data cleanup and post-processing * * * This class can be overloaded with {@see SimplePie::set_sanitize_class()} * * @package SimplePie * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags */ class SimplePie_Sanitize { // Private vars var $base; // Options var $remove_div = true; var $image_handler = ''; var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); var $encode_instead_of_strip = false; var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); var $strip_comments = false; var $output_encoding = 'UTF-8'; var $enable_cache = true; var $cache_location = './cache'; var $cache_name_function = 'md5'; var $timeout = 10; var $useragent = ''; var $force_fsockopen = false; var $replace_url_attributes = null; public function __construct() { // Set defaults $this->set_url_replacements(null); } public function remove_div($enable = true) { $this->remove_div = (bool) $enable; } public function set_image_handler($page = false) { if ($page) { $this->image_handler = (string) $page; } else { $this->image_handler = false; } } public function set_registry(SimplePie_Registry $registry) { $this->registry = $registry; } public function pass_cache_data($enable_cache = true, $cache_location = './cache', $cache_name_function = 'md5', $cache_class = 'SimplePie_Cache') { if (isset($enable_cache)) { $this->enable_cache = (bool) $enable_cache; } if ($cache_location) { $this->cache_location = (string) $cache_location; } if ($cache_name_function) { $this->cache_name_function = (string) $cache_name_function; } } public function pass_file_data($file_class = 'SimplePie_File', $timeout = 10, $useragent = '', $force_fsockopen = false) { if ($timeout) { $this->timeout = (string) $timeout; } if ($useragent) { $this->useragent = (string) $useragent; } if ($force_fsockopen) { $this->force_fsockopen = (string) $force_fsockopen; } } public function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style')) { if ($tags) { if (is_array($tags)) { $this->strip_htmltags = $tags; } else { $this->strip_htmltags = explode(',', $tags); } } else { $this->strip_htmltags = false; } } public function encode_instead_of_strip($encode = false) { $this->encode_instead_of_strip = (bool) $encode; } public function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc')) { if ($attribs) { if (is_array($attribs)) { $this->strip_attributes = $attribs; } else { $this->strip_attributes = explode(',', $attribs); } } else { $this->strip_attributes = false; } } public function strip_comments($strip = false) { $this->strip_comments = (bool) $strip; } public function set_output_encoding($encoding = 'UTF-8') { $this->output_encoding = (string) $encoding; } /** * Set element/attribute key/value pairs of HTML attributes * containing URLs that need to be resolved relative to the feed * * Defaults to |a|@href, |area|@href, |blockquote|@cite, |del|@cite, * |form|@action, |img|@longdesc, |img|@src, |input|@src, |ins|@cite, * |q|@cite * * @since 1.0 * @param array|null $element_attribute Element/attribute key/value pairs, null for default */ public function set_url_replacements($element_attribute = null) { if ($element_attribute === null) { $element_attribute = array( 'a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array( 'longdesc', 'src' ), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite' ); } $this->replace_url_attributes = (array) $element_attribute; } public function sanitize($data, $type, $base = '') { $data = trim($data); if ($data !== '' || $type & SIMPLEPIE_CONSTRUCT_IRI) { if ($type & SIMPLEPIE_CONSTRUCT_MAYBE_HTML) { if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>)/', $data)) { $type |= SIMPLEPIE_CONSTRUCT_HTML; } else { $type |= SIMPLEPIE_CONSTRUCT_TEXT; } } if ($type & SIMPLEPIE_CONSTRUCT_BASE64) { $data = base64_decode($data); } if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML)) { $document = new DOMDocument(); $document->encoding = 'UTF-8'; $data = $this->preprocess($data, $type); set_error_handler(array('SimplePie_Misc', 'silence_errors')); $document->loadHTML($data); restore_error_handler(); // Strip comments if ($this->strip_comments) { $xpath = new DOMXPath($document); $comments = $xpath->query('//comment()'); foreach ($comments as $comment) { $comment->parentNode->removeChild($comment); } } // Strip out HTML tags and attributes that might cause various security problems. // Based on recommendations by Mark Pilgrim at: // http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely if ($this->strip_htmltags) { foreach ($this->strip_htmltags as $tag) { $this->strip_tag($tag, $document, $type); } } if ($this->strip_attributes) { foreach ($this->strip_attributes as $attrib) { $this->strip_attr($attrib, $document); } } // Replace relative URLs $this->base = $base; foreach ($this->replace_url_attributes as $element => $attributes) { $this->replace_urls($document, $element, $attributes); } // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags. if (isset($this->image_handler) && ((string) $this->image_handler) !== '' && $this->enable_cache) { $images = $document->getElementsByTagName('img'); foreach ($images as $img) { if ($img->hasAttribute('src')) { $image_url = call_user_func($this->cache_name_function, $img->getAttribute('src')); $cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, $image_url, 'spi')); if ($cache->load()) { $img->setAttribute('src', $this->image_handler . $image_url); } else { $file = $this->registry->create('File', array($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen)); $headers = $file->headers; if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) { if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) { $img->setAttribute('src', $this->image_handler . $image_url); } else { trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); } } } } } } // Remove the DOCTYPE // Seems to cause segfaulting if we don't do this if ($document->firstChild instanceof DOMDocumentType) { $document->removeChild($document->firstChild); } // Move everything from the body to the root $real_body = $document->getElementsByTagName('body')->item(0)->childNodes->item(0); $document->replaceChild($real_body, $document->firstChild); // Finally, convert to a HTML string $data = trim($document->saveHTML()); if ($this->remove_div) { $data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '', $data); $data = preg_replace('/<\/div>$/', '', $data); } else { $data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '<div>', $data); } } if ($type & SIMPLEPIE_CONSTRUCT_IRI) { $absolute = $this->registry->call('Misc', 'absolutize_url', array($data, $base)); if ($absolute !== false) { $data = $absolute; } } if ($type & (SIMPLEPIE_CONSTRUCT_TEXT | SIMPLEPIE_CONSTRUCT_IRI)) { $data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8'); } if ($this->output_encoding !== 'UTF-8') { $data = $this->registry->call('Misc', 'change_encoding', array($data, 'UTF-8', $this->output_encoding)); } } return $data; } protected function preprocess($html, $type) { $ret = ''; if ($type & ~SIMPLEPIE_CONSTRUCT_XHTML) { // Atom XHTML constructs are wrapped with a div by default // Note: No protection if $html contains a stray </div>! $html = '<div>' . $html . '</div>'; $ret .= '<!DOCTYPE html>'; $content_type = 'text/html'; } else { $ret .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; $content_type = 'application/xhtml+xml'; } $ret .= '<html><head>'; $ret .= '<meta http-equiv="Content-Type" content="' . $content_type . '; charset=utf-8" />'; $ret .= '</head><body>' . $html . '</body></html>'; return $ret; } public function replace_urls($document, $tag, $attributes) { if (!is_array($attributes)) { $attributes = array($attributes); } if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) { $elements = $document->getElementsByTagName($tag); foreach ($elements as $element) { foreach ($attributes as $attribute) { if ($element->hasAttribute($attribute)) { $value = $this->registry->call('Misc', 'absolutize_url', array($element->getAttribute($attribute), $this->base)); if ($value !== false) { $element->setAttribute($attribute, $value); } } } } } } public function do_strip_htmltags($match) { if ($this->encode_instead_of_strip) { if (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style'))) { $match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8'); $match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8'); return "<$match[1]$match[2]>$match[3]</$match[1]>"; } else { return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8'); } } elseif (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style'))) { return $match[4]; } else { return ''; } } protected function strip_tag($tag, $document, $type) { $xpath = new DOMXPath($document); $elements = $xpath->query('body//' . $tag); if ($this->encode_instead_of_strip) { foreach ($elements as $element) { $fragment = $document->createDocumentFragment(); // For elements which aren't script or style, include the tag itself if (!in_array($tag, array('script', 'style'))) { $text = '<' . $tag; if ($element->hasAttributes()) { $attrs = array(); foreach ($element->attributes as $name => $attr) { $value = $attr->value; // In XHTML, empty values should never exist, so we repeat the value if (empty($value) && ($type & SIMPLEPIE_CONSTRUCT_XHTML)) { $value = $name; } // For HTML, empty is fine elseif (empty($value) && ($type & SIMPLEPIE_CONSTRUCT_HTML)) { $attrs[] = $name; continue; } // Standard attribute text $attrs[] = $name . '="' . $attr->value . '"'; } $text .= ' ' . implode(' ', $attrs); } $text .= '>'; $fragment->appendChild(new DOMText($text)); } $number = $element->childNodes->length; for ($i = $number; $i > 0; $i--) { $child = $element->childNodes->item(0); $fragment->appendChild($child); } if (!in_array($tag, array('script', 'style'))) { $fragment->appendChild(new DOMText('</' . $tag . '>')); } $element->parentNode->replaceChild($fragment, $element); } return; } elseif (in_array($tag, array('script', 'style'))) { foreach ($elements as $element) { $element->parentNode->removeChild($element); } return; } else { foreach ($elements as $element) { $fragment = $document->createDocumentFragment(); $number = $element->childNodes->length; for ($i = $number; $i > 0; $i--) { $child = $element->childNodes->item(0); $fragment->appendChild($child); } $element->parentNode->replaceChild($fragment, $element); } } } protected function strip_attr($attrib, $document) { $xpath = new DOMXPath($document); $elements = $xpath->query('//*[@' . $attrib . ']'); foreach ($elements as $element) { $element->removeAttribute($attrib); } } } ================================================ FILE: libraries/simplepie/library/SimplePie/Source.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Handles `<atom:source>` * * Used by {@see SimplePie_Item::get_source()} * * This class can be overloaded with {@see SimplePie::set_source_class()} * * @package SimplePie * @subpackage API */ class SimplePie_Source { var $item; var $data = array(); protected $registry; public function __construct($item, $data) { $this->item = $item; $this->data = $data; } public function set_registry(SimplePie_Registry $registry) { $this->registry = $registry; } public function __toString() { return md5(serialize($this->data)); } public function get_source_tags($namespace, $tag) { if (isset($this->data['child'][$namespace][$tag])) { return $this->data['child'][$namespace][$tag]; } else { return null; } } public function get_base($element = array()) { return $this->item->get_base($element); } public function sanitize($data, $type, $base = '') { return $this->item->sanitize($data, $type, $base); } public function get_item() { return $this->item; } public function get_title() { if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { return null; } } public function get_category($key = 0) { $categories = $this->get_categories(); if (isset($categories[$key])) { return $categories[$key]; } else { return null; } } public function get_categories() { $categories = array(); foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['attribs']['']['term'])) { $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); } foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) { // This is really the label, but keep this as the term also for BC. // Label will also work on retrieving because that falls back to term. $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); if (isset($category['attribs']['']['domain'])) { $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $scheme = null; } $categories[] = $this->registry->create('Category', array($term, $scheme, null)); } foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) { $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) { $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } if (!empty($categories)) { return array_unique($categories); } else { return null; } } public function get_author($key = 0) { $authors = $this->get_authors(); if (isset($authors[$key])) { return $authors[$key]; } else { return null; } } public function get_authors() { $authors = array(); foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) { $name = null; $uri = null; $email = null; if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); } if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $authors[] = $this->registry->create('Author', array($name, $uri, $email)); } } if ($author = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) { $name = null; $url = null; $email = null; if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); } if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $authors[] = $this->registry->create('Author', array($name, $url, $email)); } } foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } if (!empty($authors)) { return array_unique($authors); } else { return null; } } public function get_contributor($key = 0) { $contributors = $this->get_contributors(); if (isset($contributors[$key])) { return $contributors[$key]; } else { return null; } } public function get_contributors() { $contributors = array(); foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) { $name = null; $uri = null; $email = null; if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $contributors[] = $this->registry->create('Author', array($name, $uri, $email)); } } foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) { $name = null; $url = null; $email = null; if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $contributors[] = $this->registry->create('Author', array($name, $url, $email)); } } if (!empty($contributors)) { return array_unique($contributors); } else { return null; } } public function get_link($key = 0, $rel = 'alternate') { $links = $this->get_links($rel); if (isset($links[$key])) { return $links[$key]; } else { return null; } } /** * Added for parity between the parent-level and the item/entry-level. */ public function get_permalink() { return $this->get_link(0); } public function get_links($rel = 'alternate') { if (!isset($this->data['links'])) { $this->data['links'] = array(); if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link')) { foreach ($links as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); } } } if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link')) { foreach ($links as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); } } } if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } $keys = array_keys($this->data['links']); foreach ($keys as $key) { if ($this->registry->call('Misc', 'is_isegment_nz_nc', array($key))) { if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) { $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; } else { $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; } } elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) { $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; } $this->data['links'][$key] = array_unique($this->data['links'][$key]); } } if (isset($this->data['links'][$rel])) { return $this->data['links'][$rel]; } else { return null; } } public function get_description() { if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); } else { return null; } } public function get_copyright() { if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { return null; } } public function get_language() { if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (isset($this->data['xml_lang'])) { return $this->sanitize($this->data['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); } else { return null; } } public function get_latitude() { if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) { return (float) $return[0]['data']; } elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[1]; } else { return null; } } public function get_longitude() { if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) { return (float) $return[0]['data']; } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) { return (float) $return[0]['data']; } elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[2]; } else { return null; } } public function get_image_url() { if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image')) { return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } else { return null; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/XML/Declaration/Parser.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Parses the XML Declaration * * @package SimplePie * @subpackage Parsing */ class SimplePie_XML_Declaration_Parser { /** * XML Version * * @access public * @var string */ var $version = '1.0'; /** * Encoding * * @access public * @var string */ var $encoding = 'UTF-8'; /** * Standalone * * @access public * @var bool */ var $standalone = false; /** * Current state of the state machine * * @access private * @var string */ var $state = 'before_version_name'; /** * Input data * * @access private * @var string */ var $data = ''; /** * Input data length (to avoid calling strlen() everytime this is needed) * * @access private * @var int */ var $data_length = 0; /** * Current position of the pointer * * @var int * @access private */ var $position = 0; /** * Create an instance of the class with the input data * * @access public * @param string $data Input data */ public function __construct($data) { $this->data = $data; $this->data_length = strlen($this->data); } /** * Parse the input data * * @access public * @return bool true on success, false on failure */ public function parse() { while ($this->state && $this->state !== 'emit' && $this->has_data()) { $state = $this->state; $this->$state(); } $this->data = ''; if ($this->state === 'emit') { return true; } else { $this->version = ''; $this->encoding = ''; $this->standalone = ''; return false; } } /** * Check whether there is data beyond the pointer * * @access private * @return bool true if there is further data, false if not */ public function has_data() { return (bool) ($this->position < $this->data_length); } /** * Advance past any whitespace * * @return int Number of whitespace characters passed */ public function skip_whitespace() { $whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position); $this->position += $whitespace; return $whitespace; } /** * Read value */ public function get_value() { $quote = substr($this->data, $this->position, 1); if ($quote === '"' || $quote === "'") { $this->position++; $len = strcspn($this->data, $quote, $this->position); if ($this->has_data()) { $value = substr($this->data, $this->position, $len); $this->position += $len + 1; return $value; } } return false; } public function before_version_name() { if ($this->skip_whitespace()) { $this->state = 'version_name'; } else { $this->state = false; } } public function version_name() { if (substr($this->data, $this->position, 7) === 'version') { $this->position += 7; $this->skip_whitespace(); $this->state = 'version_equals'; } else { $this->state = false; } } public function version_equals() { if (substr($this->data, $this->position, 1) === '=') { $this->position++; $this->skip_whitespace(); $this->state = 'version_value'; } else { $this->state = false; } } public function version_value() { if ($this->version = $this->get_value()) { $this->skip_whitespace(); if ($this->has_data()) { $this->state = 'encoding_name'; } else { $this->state = 'emit'; } } else { $this->state = false; } } public function encoding_name() { if (substr($this->data, $this->position, 8) === 'encoding') { $this->position += 8; $this->skip_whitespace(); $this->state = 'encoding_equals'; } else { $this->state = 'standalone_name'; } } public function encoding_equals() { if (substr($this->data, $this->position, 1) === '=') { $this->position++; $this->skip_whitespace(); $this->state = 'encoding_value'; } else { $this->state = false; } } public function encoding_value() { if ($this->encoding = $this->get_value()) { $this->skip_whitespace(); if ($this->has_data()) { $this->state = 'standalone_name'; } else { $this->state = 'emit'; } } else { $this->state = false; } } public function standalone_name() { if (substr($this->data, $this->position, 10) === 'standalone') { $this->position += 10; $this->skip_whitespace(); $this->state = 'standalone_equals'; } else { $this->state = false; } } public function standalone_equals() { if (substr($this->data, $this->position, 1) === '=') { $this->position++; $this->skip_whitespace(); $this->state = 'standalone_value'; } else { $this->state = false; } } public function standalone_value() { if ($standalone = $this->get_value()) { switch ($standalone) { case 'yes': $this->standalone = true; break; case 'no': $this->standalone = false; break; default: $this->state = false; return; } $this->skip_whitespace(); if ($this->has_data()) { $this->state = false; } else { $this->state = 'emit'; } } else { $this->state = false; } } } ================================================ FILE: libraries/simplepie/library/SimplePie/gzdecode.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * Decode 'gzip' encoded HTTP data * * @package SimplePie * @subpackage HTTP * @link http://www.gzip.org/format.txt */ class SimplePie_gzdecode { /** * Compressed data * * @access private * @var string * @see gzdecode::$data */ var $compressed_data; /** * Size of compressed data * * @access private * @var int */ var $compressed_size; /** * Minimum size of a valid gzip string * * @access private * @var int */ var $min_compressed_size = 18; /** * Current position of pointer * * @access private * @var int */ var $position = 0; /** * Flags (FLG) * * @access private * @var int */ var $flags; /** * Uncompressed data * * @access public * @see gzdecode::$compressed_data * @var string */ var $data; /** * Modified time * * @access public * @var int */ var $MTIME; /** * Extra Flags * * @access public * @var int */ var $XFL; /** * Operating System * * @access public * @var int */ var $OS; /** * Subfield ID 1 * * @access public * @see gzdecode::$extra_field * @see gzdecode::$SI2 * @var string */ var $SI1; /** * Subfield ID 2 * * @access public * @see gzdecode::$extra_field * @see gzdecode::$SI1 * @var string */ var $SI2; /** * Extra field content * * @access public * @see gzdecode::$SI1 * @see gzdecode::$SI2 * @var string */ var $extra_field; /** * Original filename * * @access public * @var string */ var $filename; /** * Human readable comment * * @access public * @var string */ var $comment; /** * Don't allow anything to be set * * @param string $name * @param mixed $value */ public function __set($name, $value) { trigger_error("Cannot write property $name", E_USER_ERROR); } /** * Set the compressed string and related properties * * @param string $data */ public function __construct($data) { $this->compressed_data = $data; $this->compressed_size = strlen($data); } /** * Decode the GZIP stream * * @return bool Successfulness */ public function parse() { if ($this->compressed_size >= $this->min_compressed_size) { // Check ID1, ID2, and CM if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08") { return false; } // Get the FLG (FLaGs) $this->flags = ord($this->compressed_data[3]); // FLG bits above (1 << 4) are reserved if ($this->flags > 0x1F) { return false; } // Advance the pointer after the above $this->position += 4; // MTIME $mtime = substr($this->compressed_data, $this->position, 4); // Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness if (current(unpack('S', "\x00\x01")) === 1) { $mtime = strrev($mtime); } $this->MTIME = current(unpack('l', $mtime)); $this->position += 4; // Get the XFL (eXtra FLags) $this->XFL = ord($this->compressed_data[$this->position++]); // Get the OS (Operating System) $this->OS = ord($this->compressed_data[$this->position++]); // Parse the FEXTRA if ($this->flags & 4) { // Read subfield IDs $this->SI1 = $this->compressed_data[$this->position++]; $this->SI2 = $this->compressed_data[$this->position++]; // SI2 set to zero is reserved for future use if ($this->SI2 === "\x00") { return false; } // Get the length of the extra field $len = current(unpack('v', substr($this->compressed_data, $this->position, 2))); $this->position += 2; // Check the length of the string is still valid $this->min_compressed_size += $len + 4; if ($this->compressed_size >= $this->min_compressed_size) { // Set the extra field to the given data $this->extra_field = substr($this->compressed_data, $this->position, $len); $this->position += $len; } else { return false; } } // Parse the FNAME if ($this->flags & 8) { // Get the length of the filename $len = strcspn($this->compressed_data, "\x00", $this->position); // Check the length of the string is still valid $this->min_compressed_size += $len + 1; if ($this->compressed_size >= $this->min_compressed_size) { // Set the original filename to the given string $this->filename = substr($this->compressed_data, $this->position, $len); $this->position += $len + 1; } else { return false; } } // Parse the FCOMMENT if ($this->flags & 16) { // Get the length of the comment $len = strcspn($this->compressed_data, "\x00", $this->position); // Check the length of the string is still valid $this->min_compressed_size += $len + 1; if ($this->compressed_size >= $this->min_compressed_size) { // Set the original comment to the given string $this->comment = substr($this->compressed_data, $this->position, $len); $this->position += $len + 1; } else { return false; } } // Parse the FHCRC if ($this->flags & 2) { // Check the length of the string is still valid $this->min_compressed_size += $len + 2; if ($this->compressed_size >= $this->min_compressed_size) { // Read the CRC $crc = current(unpack('v', substr($this->compressed_data, $this->position, 2))); // Check the CRC matches if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc) { $this->position += 2; } else { return false; } } else { return false; } } // Decompress the actual data if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) { return false; } else { $this->position = $this->compressed_size - 8; } // Check CRC of data $crc = current(unpack('V', substr($this->compressed_data, $this->position, 4))); $this->position += 4; /*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc)) { return false; }*/ // Check ISIZE of data $isize = current(unpack('V', substr($this->compressed_data, $this->position, 4))); $this->position += 4; if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize)) { return false; } // Wow, against all odds, we've actually got a valid gzip string return true; } else { return false; } } } ================================================ FILE: libraries/simplepie/library/SimplePie.php ================================================ <?php /** * SimplePie * * A PHP-Based RSS and Atom Feed Framework. * Takes the hard work out of managing a complete RSS/Atom solution. * * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * * Neither the name of the SimplePie Team nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie * @version 1.3.1 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon * @author Ryan McCue * @link http://simplepie.org/ SimplePie * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ /** * SimplePie Name */ define('SIMPLEPIE_NAME', 'SimplePie'); /** * SimplePie Version */ define('SIMPLEPIE_VERSION', '1.3.1'); /** * SimplePie Build * @todo Hardcode for release (there's no need to have to call SimplePie_Misc::get_build() only every load of simplepie.inc) */ define('SIMPLEPIE_BUILD', gmdate('YmdHis', SimplePie_Misc::get_build())); /** * SimplePie Website URL */ define('SIMPLEPIE_URL', 'http://localhost'); /** * SimplePie Useragent * @see SimplePie::set_useragent() */ define('SIMPLEPIE_USERAGENT', SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION . ' (Feed Parser; ' . SIMPLEPIE_URL . '; Allow like Gecko) Build/' . SIMPLEPIE_BUILD); /** * SimplePie Linkback */ define('SIMPLEPIE_LINKBACK', '<a href="' . SIMPLEPIE_URL . '" title="' . SIMPLEPIE_NAME . ' ' . SIMPLEPIE_VERSION . '">' . SIMPLEPIE_NAME . '</a>'); /** * No Autodiscovery * @see SimplePie::set_autodiscovery_level() */ define('SIMPLEPIE_LOCATOR_NONE', 0); /** * Feed Link Element Autodiscovery * @see SimplePie::set_autodiscovery_level() */ define('SIMPLEPIE_LOCATOR_AUTODISCOVERY', 1); /** * Local Feed Extension Autodiscovery * @see SimplePie::set_autodiscovery_level() */ define('SIMPLEPIE_LOCATOR_LOCAL_EXTENSION', 2); /** * Local Feed Body Autodiscovery * @see SimplePie::set_autodiscovery_level() */ define('SIMPLEPIE_LOCATOR_LOCAL_BODY', 4); /** * Remote Feed Extension Autodiscovery * @see SimplePie::set_autodiscovery_level() */ define('SIMPLEPIE_LOCATOR_REMOTE_EXTENSION', 8); /** * Remote Feed Body Autodiscovery * @see SimplePie::set_autodiscovery_level() */ define('SIMPLEPIE_LOCATOR_REMOTE_BODY', 16); /** * All Feed Autodiscovery * @see SimplePie::set_autodiscovery_level() */ define('SIMPLEPIE_LOCATOR_ALL', 31); /** * No known feed type */ define('SIMPLEPIE_TYPE_NONE', 0); /** * RSS 0.90 */ define('SIMPLEPIE_TYPE_RSS_090', 1); /** * RSS 0.91 (Netscape) */ define('SIMPLEPIE_TYPE_RSS_091_NETSCAPE', 2); /** * RSS 0.91 (Userland) */ define('SIMPLEPIE_TYPE_RSS_091_USERLAND', 4); /** * RSS 0.91 (both Netscape and Userland) */ define('SIMPLEPIE_TYPE_RSS_091', 6); /** * RSS 0.92 */ define('SIMPLEPIE_TYPE_RSS_092', 8); /** * RSS 0.93 */ define('SIMPLEPIE_TYPE_RSS_093', 16); /** * RSS 0.94 */ define('SIMPLEPIE_TYPE_RSS_094', 32); /** * RSS 1.0 */ define('SIMPLEPIE_TYPE_RSS_10', 64); /** * RSS 2.0 */ define('SIMPLEPIE_TYPE_RSS_20', 128); /** * RDF-based RSS */ define('SIMPLEPIE_TYPE_RSS_RDF', 65); /** * Non-RDF-based RSS (truly intended as syndication format) */ define('SIMPLEPIE_TYPE_RSS_SYNDICATION', 190); /** * All RSS */ define('SIMPLEPIE_TYPE_RSS_ALL', 255); /** * Atom 0.3 */ define('SIMPLEPIE_TYPE_ATOM_03', 256); /** * Atom 1.0 */ define('SIMPLEPIE_TYPE_ATOM_10', 512); /** * All Atom */ define('SIMPLEPIE_TYPE_ATOM_ALL', 768); /** * All feed types */ define('SIMPLEPIE_TYPE_ALL', 1023); /** * No construct */ define('SIMPLEPIE_CONSTRUCT_NONE', 0); /** * Text construct */ define('SIMPLEPIE_CONSTRUCT_TEXT', 1); /** * HTML construct */ define('SIMPLEPIE_CONSTRUCT_HTML', 2); /** * XHTML construct */ define('SIMPLEPIE_CONSTRUCT_XHTML', 4); /** * base64-encoded construct */ define('SIMPLEPIE_CONSTRUCT_BASE64', 8); /** * IRI construct */ define('SIMPLEPIE_CONSTRUCT_IRI', 16); /** * A construct that might be HTML */ define('SIMPLEPIE_CONSTRUCT_MAYBE_HTML', 32); /** * All constructs */ define('SIMPLEPIE_CONSTRUCT_ALL', 63); /** * Don't change case */ define('SIMPLEPIE_SAME_CASE', 1); /** * Change to lowercase */ define('SIMPLEPIE_LOWERCASE', 2); /** * Change to uppercase */ define('SIMPLEPIE_UPPERCASE', 4); /** * PCRE for HTML attributes */ define('SIMPLEPIE_PCRE_HTML_ATTRIBUTE', '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*'); /** * PCRE for XML attributes */ define('SIMPLEPIE_PCRE_XML_ATTRIBUTE', '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*'); /** * XML Namespace */ define('SIMPLEPIE_NAMESPACE_XML', 'http://www.w3.org/XML/1998/namespace'); /** * Atom 1.0 Namespace */ define('SIMPLEPIE_NAMESPACE_ATOM_10', 'http://www.w3.org/2005/Atom'); /** * Atom 0.3 Namespace */ define('SIMPLEPIE_NAMESPACE_ATOM_03', 'http://purl.org/atom/ns#'); /** * RDF Namespace */ define('SIMPLEPIE_NAMESPACE_RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); /** * RSS 0.90 Namespace */ define('SIMPLEPIE_NAMESPACE_RSS_090', 'http://my.netscape.com/rdf/simple/0.9/'); /** * RSS 1.0 Namespace */ define('SIMPLEPIE_NAMESPACE_RSS_10', 'http://purl.org/rss/1.0/'); /** * RSS 1.0 Content Module Namespace */ define('SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT', 'http://purl.org/rss/1.0/modules/content/'); /** * RSS 2.0 Namespace * (Stupid, I know, but I'm certain it will confuse people less with support.) */ define('SIMPLEPIE_NAMESPACE_RSS_20', ''); /** * DC 1.0 Namespace */ define('SIMPLEPIE_NAMESPACE_DC_10', 'http://purl.org/dc/elements/1.0/'); /** * DC 1.1 Namespace */ define('SIMPLEPIE_NAMESPACE_DC_11', 'http://purl.org/dc/elements/1.1/'); /** * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace */ define('SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO', 'http://www.w3.org/2003/01/geo/wgs84_pos#'); /** * GeoRSS Namespace */ define('SIMPLEPIE_NAMESPACE_GEORSS', 'http://www.georss.org/georss'); /** * Media RSS Namespace */ define('SIMPLEPIE_NAMESPACE_MEDIARSS', 'http://search.yahoo.com/mrss/'); /** * Wrong Media RSS Namespace. Caused by a long-standing typo in the spec. */ define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG', 'http://search.yahoo.com/mrss'); /** * Wrong Media RSS Namespace #2. New namespace introduced in Media RSS 1.5. */ define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG2', 'http://video.search.yahoo.com/mrss'); /** * Wrong Media RSS Namespace #3. A possible typo of the Media RSS 1.5 namespace. */ define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG3', 'http://video.search.yahoo.com/mrss/'); /** * Wrong Media RSS Namespace #4. New spec location after the RSS Advisory Board takes it over, but not a valid namespace. */ define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG4', 'http://www.rssboard.org/media-rss'); /** * Wrong Media RSS Namespace #5. A possible typo of the RSS Advisory Board URL. */ define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG5', 'http://www.rssboard.org/media-rss/'); /** * iTunes RSS Namespace */ define('SIMPLEPIE_NAMESPACE_ITUNES', 'http://www.itunes.com/dtds/podcast-1.0.dtd'); /** * XHTML Namespace */ define('SIMPLEPIE_NAMESPACE_XHTML', 'http://www.w3.org/1999/xhtml'); /** * IANA Link Relations Registry */ define('SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY', 'http://www.iana.org/assignments/relation/'); /** * No file source */ define('SIMPLEPIE_FILE_SOURCE_NONE', 0); /** * Remote file source */ define('SIMPLEPIE_FILE_SOURCE_REMOTE', 1); /** * Local file source */ define('SIMPLEPIE_FILE_SOURCE_LOCAL', 2); /** * fsockopen() file source */ define('SIMPLEPIE_FILE_SOURCE_FSOCKOPEN', 4); /** * cURL file source */ define('SIMPLEPIE_FILE_SOURCE_CURL', 8); /** * file_get_contents() file source */ define('SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS', 16); /** * SimplePie * * @package SimplePie * @subpackage API */ class SimplePie { /** * @var array Raw data * @access private */ public $data = array(); /** * @var mixed Error string * @access private */ public $error; /** * @var object Instance of SimplePie_Sanitize (or other class) * @see SimplePie::set_sanitize_class() * @access private */ public $sanitize; /** * @var string SimplePie Useragent * @see SimplePie::set_useragent() * @access private */ public $useragent = SIMPLEPIE_USERAGENT; /** * @var string Feed URL * @see SimplePie::set_feed_url() * @access private */ public $feed_url; /** * @var object Instance of SimplePie_File to use as a feed * @see SimplePie::set_file() * @access private */ public $file; /** * @var string Raw feed data * @see SimplePie::set_raw_data() * @access private */ public $raw_data; /** * @var int Timeout for fetching remote files * @see SimplePie::set_timeout() * @access private */ public $timeout = 10; /** * @var bool Forces fsockopen() to be used for remote files instead * of cURL, even if a new enough version is installed * @see SimplePie::force_fsockopen() * @access private */ public $force_fsockopen = false; /** * @var bool Force the given data/URL to be treated as a feed no matter what * it appears like * @see SimplePie::force_feed() * @access private */ public $force_feed = false; /** * @var bool Enable/Disable Caching * @see SimplePie::enable_cache() * @access private */ public $cache = true; /** * @var int Cache duration (in seconds) * @see SimplePie::set_cache_duration() * @access private */ public $cache_duration = 3600; /** * @var int Auto-discovery cache duration (in seconds) * @see SimplePie::set_autodiscovery_cache_duration() * @access private */ public $autodiscovery_cache_duration = 604800; // 7 Days. /** * @var string Cache location (relative to executing script) * @see SimplePie::set_cache_location() * @access private */ public $cache_location = './cache'; /** * @var string Function that creates the cache filename * @see SimplePie::set_cache_name_function() * @access private */ public $cache_name_function = 'md5'; /** * @var bool Reorder feed by date descending * @see SimplePie::enable_order_by_date() * @access private */ public $order_by_date = true; /** * @var mixed Force input encoding to be set to the follow value * (false, or anything type-cast to false, disables this feature) * @see SimplePie::set_input_encoding() * @access private */ public $input_encoding = false; /** * @var int Feed Autodiscovery Level * @see SimplePie::set_autodiscovery_level() * @access private */ public $autodiscovery = SIMPLEPIE_LOCATOR_ALL; /** * Class registry object * * @var SimplePie_Registry */ public $registry; /** * @var int Maximum number of feeds to check with autodiscovery * @see SimplePie::set_max_checked_feeds() * @access private */ public $max_checked_feeds = 10; /** * @var array All the feeds found during the autodiscovery process * @see SimplePie::get_all_discovered_feeds() * @access private */ public $all_discovered_feeds = array(); /** * @var string Web-accessible path to the handler_image.php file. * @see SimplePie::set_image_handler() * @access private */ public $image_handler = ''; /** * @var array Stores the URLs when multiple feeds are being initialized. * @see SimplePie::set_feed_url() * @access private */ public $multifeed_url = array(); /** * @var array Stores SimplePie objects when multiple feeds initialized. * @access private */ public $multifeed_objects = array(); /** * @var array Stores the get_object_vars() array for use with multifeeds. * @see SimplePie::set_feed_url() * @access private */ public $config_settings = null; /** * @var integer Stores the number of items to return per-feed with multifeeds. * @see SimplePie::set_item_limit() * @access private */ public $item_limit = 0; /** * @var array Stores the default attributes to be stripped by strip_attributes(). * @see SimplePie::strip_attributes() * @access private */ public $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); /** * @var array Stores the default tags to be stripped by strip_htmltags(). * @see SimplePie::strip_htmltags() * @access private */ public $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); /** * The SimplePie class contains feed level data and options * * To use SimplePie, create the SimplePie object with no parameters. You can * then set configuration options using the provided methods. After setting * them, you must initialise the feed using $feed->init(). At that point the * object's methods and properties will be available to you. * * Previously, it was possible to pass in the feed URL along with cache * options directly into the constructor. This has been removed as of 1.3 as * it caused a lot of confusion. * * @since 1.0 Preview Release */ public function __construct() { if (version_compare(PHP_VERSION, '5.2', '<')) { trigger_error('PHP 4.x, 5.0 and 5.1 are no longer supported. Please upgrade to PHP 5.2 or newer.'); die(); } // Other objects, instances created here so we can set options on them $this->sanitize = new SimplePie_Sanitize(); $this->registry = new SimplePie_Registry(); if (func_num_args() > 0) { $level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING; trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_location() directly.', $level); $args = func_get_args(); switch (count($args)) { case 3: $this->set_cache_duration($args[2]); case 2: $this->set_cache_location($args[1]); case 1: $this->set_feed_url($args[0]); $this->init(); } } } /** * Used for converting object to a string */ public function __toString() { return md5(serialize($this->data)); } /** * Remove items that link back to this before destroying this object */ public function __destruct() { if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode')) { if (!empty($this->data['items'])) { foreach ($this->data['items'] as $item) { $item->__destruct(); } unset($item, $this->data['items']); } if (!empty($this->data['ordered_items'])) { foreach ($this->data['ordered_items'] as $item) { $item->__destruct(); } unset($item, $this->data['ordered_items']); } } } /** * Force the given data/URL to be treated as a feed * * This tells SimplePie to ignore the content-type provided by the server. * Be careful when using this option, as it will also disable autodiscovery. * * @since 1.1 * @param bool $enable Force the given data/URL to be treated as a feed */ public function force_feed($enable = false) { $this->force_feed = (bool) $enable; } /** * Set the URL of the feed you want to parse * * This allows you to enter the URL of the feed you want to parse, or the * website you want to try to use auto-discovery on. This takes priority * over any set raw data. * * You can set multiple feeds to mash together by passing an array instead * of a string for the $url. Remember that with each additional feed comes * additional processing and resources. * * @since 1.0 Preview Release * @see set_raw_data() * @param string|array $url This is the URL (or array of URLs) that you want to parse. */ public function set_feed_url($url) { $this->multifeed_url = array(); if (is_array($url)) { foreach ($url as $value) { $this->multifeed_url[] = $this->registry->call('Misc', 'fix_protocol', array($value, 1)); } } else { $this->feed_url = $this->registry->call('Misc', 'fix_protocol', array($url, 1)); } } /** * Set an instance of {@see SimplePie_File} to use as a feed * * @param SimplePie_File &$file * @return bool True on success, false on failure */ public function set_file(&$file) { if ($file instanceof SimplePie_File) { $this->feed_url = $file->url; $this->file =& $file; return true; } return false; } /** * Set the raw XML data to parse * * Allows you to use a string of RSS/Atom data instead of a remote feed. * * If you have a feed available as a string in PHP, you can tell SimplePie * to parse that data string instead of a remote feed. Any set feed URL * takes precedence. * * @since 1.0 Beta 3 * @param string $data RSS or Atom data as a string. * @see set_feed_url() */ public function set_raw_data($data) { $this->raw_data = $data; } /** * Set the the default timeout for fetching remote feeds * * This allows you to change the maximum time the feed's server to respond * and send the feed back. * * @since 1.0 Beta 3 * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed. */ public function set_timeout($timeout = 10) { $this->timeout = (int) $timeout; } /** * Force SimplePie to use fsockopen() instead of cURL * * @since 1.0 Beta 3 * @param bool $enable Force fsockopen() to be used */ public function force_fsockopen($enable = false) { $this->force_fsockopen = (bool) $enable; } /** * Enable/disable caching in SimplePie. * * This option allows you to disable caching all-together in SimplePie. * However, disabling the cache can lead to longer load times. * * @since 1.0 Preview Release * @param bool $enable Enable caching */ public function enable_cache($enable = true) { $this->cache = (bool) $enable; } /** * Set the length of time (in seconds) that the contents of a feed will be * cached * * @param int $seconds The feed content cache duration */ public function set_cache_duration($seconds = 3600) { $this->cache_duration = (int) $seconds; } /** * Set the length of time (in seconds) that the autodiscovered feed URL will * be cached * * @param int $seconds The autodiscovered feed URL cache duration. */ public function set_autodiscovery_cache_duration($seconds = 604800) { $this->autodiscovery_cache_duration = (int) $seconds; } /** * Set the file system location where the cached files should be stored * * @param string $location The file system location. */ public function set_cache_location($location = './cache') { $this->cache_location = (string) $location; } /** * Set whether feed items should be sorted into reverse chronological order * * @param bool $enable Sort as reverse chronological order. */ public function enable_order_by_date($enable = true) { $this->order_by_date = (bool) $enable; } /** * Set the character encoding used to parse the feed * * This overrides the encoding reported by the feed, however it will fall * back to the normal encoding detection if the override fails * * @param string $encoding Character encoding */ public function set_input_encoding($encoding = false) { if ($encoding) { $this->input_encoding = (string) $encoding; } else { $this->input_encoding = false; } } /** * Set how much feed autodiscovery to do * * @see SIMPLEPIE_LOCATOR_NONE * @see SIMPLEPIE_LOCATOR_AUTODISCOVERY * @see SIMPLEPIE_LOCATOR_LOCAL_EXTENSION * @see SIMPLEPIE_LOCATOR_LOCAL_BODY * @see SIMPLEPIE_LOCATOR_REMOTE_EXTENSION * @see SIMPLEPIE_LOCATOR_REMOTE_BODY * @see SIMPLEPIE_LOCATOR_ALL * @param int $level Feed Autodiscovery Level (level can be a combination of the above constants, see bitwise OR operator) */ public function set_autodiscovery_level($level = SIMPLEPIE_LOCATOR_ALL) { $this->autodiscovery = (int) $level; } /** * Get the class registry * * Use this to override SimplePie's default classes * @see SimplePie_Registry * @return SimplePie_Registry */ public function &get_registry() { return $this->registry; } /**#@+ * Useful when you are overloading or extending SimplePie's default classes. * * @deprecated Use {@see get_registry()} instead * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation * @param string $class Name of custom class * @return boolean True on success, false otherwise */ /** * Set which class SimplePie uses for caching */ public function set_cache_class($class = 'SimplePie_Cache') { return $this->registry->register('Cache', $class, true); } /** * Set which class SimplePie uses for auto-discovery */ public function set_locator_class($class = 'SimplePie_Locator') { return $this->registry->register('Locator', $class, true); } /** * Set which class SimplePie uses for XML parsing */ public function set_parser_class($class = 'SimplePie_Parser') { return $this->registry->register('Parser', $class, true); } /** * Set which class SimplePie uses for remote file fetching */ public function set_file_class($class = 'SimplePie_File') { return $this->registry->register('File', $class, true); } /** * Set which class SimplePie uses for data sanitization */ public function set_sanitize_class($class = 'SimplePie_Sanitize') { return $this->registry->register('Sanitize', $class, true); } /** * Set which class SimplePie uses for handling feed items */ public function set_item_class($class = 'SimplePie_Item') { return $this->registry->register('Item', $class, true); } /** * Set which class SimplePie uses for handling author data */ public function set_author_class($class = 'SimplePie_Author') { return $this->registry->register('Author', $class, true); } /** * Set which class SimplePie uses for handling category data */ public function set_category_class($class = 'SimplePie_Category') { return $this->registry->register('Category', $class, true); } /** * Set which class SimplePie uses for feed enclosures */ public function set_enclosure_class($class = 'SimplePie_Enclosure') { return $this->registry->register('Enclosure', $class, true); } /** * Set which class SimplePie uses for `<media:text>` captions */ public function set_caption_class($class = 'SimplePie_Caption') { return $this->registry->register('Caption', $class, true); } /** * Set which class SimplePie uses for `<media:copyright>` */ public function set_copyright_class($class = 'SimplePie_Copyright') { return $this->registry->register('Copyright', $class, true); } /** * Set which class SimplePie uses for `<media:credit>` */ public function set_credit_class($class = 'SimplePie_Credit') { return $this->registry->register('Credit', $class, true); } /** * Set which class SimplePie uses for `<media:rating>` */ public function set_rating_class($class = 'SimplePie_Rating') { return $this->registry->register('Rating', $class, true); } /** * Set which class SimplePie uses for `<media:restriction>` */ public function set_restriction_class($class = 'SimplePie_Restriction') { return $this->registry->register('Restriction', $class, true); } /** * Set which class SimplePie uses for content-type sniffing */ public function set_content_type_sniffer_class($class = 'SimplePie_Content_Type_Sniffer') { return $this->registry->register('Content_Type_Sniffer', $class, true); } /** * Set which class SimplePie uses item sources */ public function set_source_class($class = 'SimplePie_Source') { return $this->registry->register('Source', $class, true); } /**#@-*/ /** * Set the user agent string * * @param string $ua New user agent string. */ public function set_useragent($ua = SIMPLEPIE_USERAGENT) { $this->useragent = (string) $ua; } /** * Set callback function to create cache filename with * * @param mixed $function Callback function */ public function set_cache_name_function($function = 'md5') { if (is_callable($function)) { $this->cache_name_function = $function; } } /** * Set options to make SP as fast as possible * * Forgoes a substantial amount of data sanitization in favor of speed. This * turns SimplePie into a dumb parser of feeds. * * @param bool $set Whether to set them or not */ public function set_stupidly_fast($set = false) { if ($set) { $this->enable_order_by_date(false); $this->remove_div(false); $this->strip_comments(false); $this->strip_htmltags(false); $this->strip_attributes(false); $this->set_image_handler(false); } } /** * Set maximum number of feeds to check with autodiscovery * * @param int $max Maximum number of feeds to check */ public function set_max_checked_feeds($max = 10) { $this->max_checked_feeds = (int) $max; } public function remove_div($enable = true) { $this->sanitize->remove_div($enable); } public function strip_htmltags($tags = '', $encode = null) { if ($tags === '') { $tags = $this->strip_htmltags; } $this->sanitize->strip_htmltags($tags); if ($encode !== null) { $this->sanitize->encode_instead_of_strip($tags); } } public function encode_instead_of_strip($enable = true) { $this->sanitize->encode_instead_of_strip($enable); } public function strip_attributes($attribs = '') { if ($attribs === '') { $attribs = $this->strip_attributes; } $this->sanitize->strip_attributes($attribs); } /** * Set the output encoding * * Allows you to override SimplePie's output to match that of your webpage. * This is useful for times when your webpages are not being served as * UTF-8. This setting will be obeyed by {@see handle_content_type()}, and * is similar to {@see set_input_encoding()}. * * It should be noted, however, that not all character encodings can support * all characters. If your page is being served as ISO-8859-1 and you try * to display a Japanese feed, you'll likely see garbled characters. * Because of this, it is highly recommended to ensure that your webpages * are served as UTF-8. * * The number of supported character encodings depends on whether your web * host supports {@link http://php.net/mbstring mbstring}, * {@link http://php.net/iconv iconv}, or both. See * {@link http://simplepie.org/wiki/faq/Supported_Character_Encodings} for * more information. * * @param string $encoding */ public function set_output_encoding($encoding = 'UTF-8') { $this->sanitize->set_output_encoding($encoding); } public function strip_comments($strip = false) { $this->sanitize->strip_comments($strip); } /** * Set element/attribute key/value pairs of HTML attributes * containing URLs that need to be resolved relative to the feed * * Defaults to |a|@href, |area|@href, |blockquote|@cite, |del|@cite, * |form|@action, |img|@longdesc, |img|@src, |input|@src, |ins|@cite, * |q|@cite * * @since 1.0 * @param array|null $element_attribute Element/attribute key/value pairs, null for default */ public function set_url_replacements($element_attribute = null) { $this->sanitize->set_url_replacements($element_attribute); } /** * Set the handler to enable the display of cached images. * * @param str $page Web-accessible path to the handler_image.php file. * @param str $qs The query string that the value should be passed to. */ public function set_image_handler($page = false, $qs = 'i') { if ($page !== false) { $this->sanitize->set_image_handler($page . '?' . $qs . '='); } else { $this->image_handler = ''; } } /** * Set the limit for items returned per-feed with multifeeds * * @param integer $limit The maximum number of items to return. */ public function set_item_limit($limit = 0) { $this->item_limit = (int) $limit; } /** * Initialize the feed object * * This is what makes everything happen. Period. This is where all of the * configuration options get processed, feeds are fetched, cached, and * parsed, and all of that other good stuff. * * @return boolean True if successful, false otherwise */ public function init() { // Check absolute bare minimum requirements. if (!extension_loaded('xml') || !extension_loaded('pcre')) { return false; } // Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader. elseif (!extension_loaded('xmlreader')) { static $xml_is_sane = null; if ($xml_is_sane === null) { $parser_check = xml_parser_create(); xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); xml_parser_free($parser_check); $xml_is_sane = isset($values[0]['value']); } if (!$xml_is_sane) { return false; } } if (method_exists($this->sanitize, 'set_registry')) { $this->sanitize->set_registry($this->registry); } // Pass whatever was set with config options over to the sanitizer. // Pass the classes in for legacy support; new classes should use the registry instead $this->sanitize->pass_cache_data($this->cache, $this->cache_location, $this->cache_name_function, $this->registry->get_class('Cache')); $this->sanitize->pass_file_data($this->registry->get_class('File'), $this->timeout, $this->useragent, $this->force_fsockopen); if (!empty($this->multifeed_url)) { $i = 0; $success = 0; $this->multifeed_objects = array(); $this->error = array(); foreach ($this->multifeed_url as $url) { $this->multifeed_objects[$i] = clone $this; $this->multifeed_objects[$i]->set_feed_url($url); $single_success = $this->multifeed_objects[$i]->init(); $success |= $single_success; if (!$single_success) { $this->error[$i] = $this->multifeed_objects[$i]->error(); } $i++; } return (bool) $success; } elseif ($this->feed_url === null && $this->raw_data === null) { return false; } $this->error = null; $this->data = array(); $this->multifeed_objects = array(); $cache = false; if ($this->feed_url !== null) { $parsed_feed_url = $this->registry->call('Misc', 'parse_url', array($this->feed_url)); // Decide whether to enable caching if ($this->cache && $parsed_feed_url['scheme'] !== '') { $cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, call_user_func($this->cache_name_function, $this->feed_url), 'spc')); } // Fetch the data via SimplePie_File into $this->raw_data if (($fetched = $this->fetch_data($cache)) === true) { return true; } elseif ($fetched === false) { return false; } list($headers, $sniffed) = $fetched; } // Set up array of possible encodings $encodings = array(); // First check to see if input has been overridden. if ($this->input_encoding !== false) { $encodings[] = $this->input_encoding; } $application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity'); $text_types = array('text/xml', 'text/xml-external-parsed-entity'); // RFC 3023 (only applies to sniffed content) if (isset($sniffed)) { if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml') { if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) { $encodings[] = strtoupper($charset[1]); } $encodings = array_merge($encodings, $this->registry->call('Misc', 'xml_encoding', array($this->raw_data, &$this->registry))); $encodings[] = 'UTF-8'; } elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml') { if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) { $encodings[] = $charset[1]; } $encodings[] = 'US-ASCII'; } // Text MIME-type default elseif (substr($sniffed, 0, 5) === 'text/') { $encodings[] = 'US-ASCII'; } } // Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1 $encodings = array_merge($encodings, $this->registry->call('Misc', 'xml_encoding', array($this->raw_data, &$this->registry))); $encodings[] = 'UTF-8'; $encodings[] = 'ISO-8859-1'; // There's no point in trying an encoding twice $encodings = array_unique($encodings); // Loop through each possible encoding, till we return something, or run out of possibilities foreach ($encodings as $encoding) { // Change the encoding to UTF-8 (as we always use UTF-8 internally) if ($utf8_data = $this->registry->call('Misc', 'change_encoding', array($this->raw_data, $encoding, 'UTF-8'))) { // Create new parser $parser = $this->registry->create('Parser'); // If it's parsed fine if ($parser->parse($utf8_data, 'UTF-8')) { $this->data = $parser->get_data(); if (!($this->get_type() & ~SIMPLEPIE_TYPE_NONE)) { $this->error = "A feed could not be found at $this->feed_url. This does not appear to be a valid RSS or Atom feed."; $this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, __FILE__, __LINE__)); return false; } if (isset($headers)) { $this->data['headers'] = $headers; } $this->data['build'] = SIMPLEPIE_BUILD; // Cache the file if caching is enabled if ($cache && !$cache->save($this)) { trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); } return true; } } } if (isset($parser)) { // We have an error, just set SimplePie_Misc::error to it and quit $this->error = sprintf('This XML document is invalid, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column()); } else { $this->error = 'The data could not be converted to UTF-8. You MUST have either the iconv or mbstring extension installed. Upgrading to PHP 5.x (which includes iconv) is highly recommended.'; } $this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, __FILE__, __LINE__)); return false; } /** * Fetch the data via SimplePie_File * * If the data is already cached, attempt to fetch it from there instead * @param SimplePie_Cache|false $cache Cache handler, or false to not load from the cache * @return array|true Returns true if the data was loaded from the cache, or an array of HTTP headers and sniffed type */ protected function fetch_data(&$cache) { // If it's enabled, use the cache if ($cache) { // Load the Cache $this->data = $cache->load(); if (!empty($this->data)) { // If the cache is for an outdated build of SimplePie if (!isset($this->data['build']) || $this->data['build'] !== SIMPLEPIE_BUILD) { $cache->unlink(); $this->data = array(); } // If we've hit a collision just rerun it with caching disabled elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url) { $cache = false; $this->data = array(); } // If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL. elseif (isset($this->data['feed_url'])) { // If the autodiscovery cache is still valid use it. if ($cache->mtime() + $this->autodiscovery_cache_duration > time()) { // Do not need to do feed autodiscovery yet. if ($this->data['feed_url'] !== $this->data['url']) { $this->set_feed_url($this->data['feed_url']); return $this->init(); } $cache->unlink(); $this->data = array(); } } // Check if the cache has been updated elseif ($cache->mtime() + $this->cache_duration < time()) { // If we have last-modified and/or etag set if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) { $headers = array( 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', ); if (isset($this->data['headers']['last-modified'])) { $headers['if-modified-since'] = $this->data['headers']['last-modified']; } if (isset($this->data['headers']['etag'])) { $headers['if-none-match'] = $this->data['headers']['etag']; } $file = $this->registry->create('File', array($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen)); if ($file->success) { if ($file->status_code === 304) { $cache->touch(); return true; } } else { unset($file); } } } // If the cache is still valid, just return true else { $this->raw_data = false; return true; } } // If the cache is empty, delete it else { $cache->unlink(); $this->data = array(); } } // If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it. if (!isset($file)) { if ($this->file instanceof SimplePie_File && $this->file->url === $this->feed_url) { $file =& $this->file; } else { $headers = array( 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', ); $file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen)); } } // If the file connection has an error, set SimplePie::error to that and quit if (!$file->success && !($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) { $this->error = $file->error; return !empty($this->data); } if (!$this->force_feed) { // Check if the supplied URL is a feed, if it isn't, look for it. $locate = $this->registry->create('Locator', array(&$file, $this->timeout, $this->useragent, $this->max_checked_feeds)); if (!$locate->is_feed($file)) { // We need to unset this so that if SimplePie::set_file() has been called that object is untouched unset($file); try { if (!($file = $locate->find($this->autodiscovery, $this->all_discovered_feeds))) { $this->error = "A feed could not be found at $this->feed_url. A feed with an invalid mime type may fall victim to this error, or " . SIMPLEPIE_NAME . " was unable to auto-discover it.. Use force_feed() if you are certain this URL is a real feed."; $this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, __FILE__, __LINE__)); return false; } } catch (SimplePie_Exception $e) { // This is usually because DOMDocument doesn't exist $this->error = $e->getMessage(); $this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, $e->getFile(), $e->getLine())); return false; } if ($cache) { $this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD); if (!$cache->save($this)) { trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); } $cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, call_user_func($this->cache_name_function, $file->url), 'spc')); } $this->feed_url = $file->url; } $locate = null; } $this->raw_data = $file->body; $headers = $file->headers; $sniffer = $this->registry->create('Content_Type_Sniffer', array(&$file)); $sniffed = $sniffer->get_type(); return array($headers, $sniffed); } /** * Get the error message for the occured error * * @return string|array Error message, or array of messages for multifeeds */ public function error() { return $this->error; } /** * Get the raw XML * * This is the same as the old `$feed->enable_xml_dump(true)`, but returns * the data instead of printing it. * * @return string|boolean Raw XML data, false if the cache is used */ public function get_raw_data() { return $this->raw_data; } /** * Get the character encoding used for output * * @since Preview Release * @return string */ public function get_encoding() { return $this->sanitize->output_encoding; } /** * Send the content-type header with correct encoding * * This method ensures that the SimplePie-enabled page is being served with * the correct {@link http://www.iana.org/assignments/media-types/ mime-type} * and character encoding HTTP headers (character encoding determined by the * {@see set_output_encoding} config option). * * This won't work properly if any content or whitespace has already been * sent to the browser, because it relies on PHP's * {@link http://php.net/header header()} function, and these are the * circumstances under which the function works. * * Because it's setting these settings for the entire page (as is the nature * of HTTP headers), this should only be used once per page (again, at the * top). * * @param string $mime MIME type to serve the page as */ public function handle_content_type($mime = 'text/html') { if (!headers_sent()) { $header = "Content-type: $mime;"; if ($this->get_encoding()) { $header .= ' charset=' . $this->get_encoding(); } else { $header .= ' charset=UTF-8'; } header($header); } } /** * Get the type of the feed * * This returns a SIMPLEPIE_TYPE_* constant, which can be tested against * using {@link http://php.net/language.operators.bitwise bitwise operators} * * @since 0.8 (usage changed to using constants in 1.0) * @see SIMPLEPIE_TYPE_NONE Unknown. * @see SIMPLEPIE_TYPE_RSS_090 RSS 0.90. * @see SIMPLEPIE_TYPE_RSS_091_NETSCAPE RSS 0.91 (Netscape). * @see SIMPLEPIE_TYPE_RSS_091_USERLAND RSS 0.91 (Userland). * @see SIMPLEPIE_TYPE_RSS_091 RSS 0.91. * @see SIMPLEPIE_TYPE_RSS_092 RSS 0.92. * @see SIMPLEPIE_TYPE_RSS_093 RSS 0.93. * @see SIMPLEPIE_TYPE_RSS_094 RSS 0.94. * @see SIMPLEPIE_TYPE_RSS_10 RSS 1.0. * @see SIMPLEPIE_TYPE_RSS_20 RSS 2.0.x. * @see SIMPLEPIE_TYPE_RSS_RDF RDF-based RSS. * @see SIMPLEPIE_TYPE_RSS_SYNDICATION Non-RDF-based RSS (truly intended as syndication format). * @see SIMPLEPIE_TYPE_RSS_ALL Any version of RSS. * @see SIMPLEPIE_TYPE_ATOM_03 Atom 0.3. * @see SIMPLEPIE_TYPE_ATOM_10 Atom 1.0. * @see SIMPLEPIE_TYPE_ATOM_ALL Any version of Atom. * @see SIMPLEPIE_TYPE_ALL Any known/supported feed type. * @return int SIMPLEPIE_TYPE_* constant */ public function get_type() { if (!isset($this->data['type'])) { $this->data['type'] = SIMPLEPIE_TYPE_ALL; if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'])) { $this->data['type'] &= SIMPLEPIE_TYPE_ATOM_10; } elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'])) { $this->data['type'] &= SIMPLEPIE_TYPE_ATOM_03; } elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'])) { if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['channel']) || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['image']) || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']) || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['textinput'])) { $this->data['type'] &= SIMPLEPIE_TYPE_RSS_10; } if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['channel']) || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['image']) || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']) || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['textinput'])) { $this->data['type'] &= SIMPLEPIE_TYPE_RSS_090; } } elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'])) { $this->data['type'] &= SIMPLEPIE_TYPE_RSS_ALL; if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) { switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) { case '0.91': $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091; if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) { switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) { case '0': $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_NETSCAPE; break; case '24': $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_USERLAND; break; } } break; case '0.92': $this->data['type'] &= SIMPLEPIE_TYPE_RSS_092; break; case '0.93': $this->data['type'] &= SIMPLEPIE_TYPE_RSS_093; break; case '0.94': $this->data['type'] &= SIMPLEPIE_TYPE_RSS_094; break; case '2.0': $this->data['type'] &= SIMPLEPIE_TYPE_RSS_20; break; } } } else { $this->data['type'] = SIMPLEPIE_TYPE_NONE; } } return $this->data['type']; } /** * Get the URL for the feed * * May or may not be different from the URL passed to {@see set_feed_url()}, * depending on whether auto-discovery was used. * * @since Preview Release (previously called `get_feed_url()` since SimplePie 0.8.) * @todo If we have a perm redirect we should return the new URL * @todo When we make the above change, let's support <itunes:new-feed-url> as well * @todo Also, |atom:link|@rel=self * @return string|null */ public function subscribe_url() { if ($this->feed_url !== null) { return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI); } else { return null; } } /** * Get data for an feed-level element * * This method allows you to get access to ANY element/attribute that is a * sub-element of the opening feed tag. * * The return value is an indexed array of elements matching the given * namespace and tag name. Each element has `attribs`, `data` and `child` * subkeys. For `attribs` and `child`, these contain namespace subkeys. * `attribs` then has one level of associative name => value data (where * `value` is a string) after the namespace. `child` has tag-indexed keys * after the namespace, each member of which is an indexed array matching * this same format. * * For example: * <pre> * // This is probably a bad example because we already support * // <media:content> natively, but it shows you how to parse through * // the nodes. * $group = $item->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group'); * $content = $group[0]['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content']; * $file = $content[0]['attribs']['']['url']; * echo $file; * </pre> * * @since 1.0 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces * @param string $namespace The URL of the XML namespace of the elements you're trying to access * @param string $tag Tag name * @return array */ public function get_feed_tags($namespace, $tag) { $type = $this->get_type(); if ($type & SIMPLEPIE_TYPE_ATOM_10) { if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag])) { return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag]; } } if ($type & SIMPLEPIE_TYPE_ATOM_03) { if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag])) { return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag]; } } if ($type & SIMPLEPIE_TYPE_RSS_RDF) { if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag])) { return $this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag]; } } if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) { if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag])) { return $this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag]; } } return null; } /** * Get data for an channel-level element * * This method allows you to get access to ANY element/attribute in the * channel/header section of the feed. * * See {@see SimplePie::get_feed_tags()} for a description of the return value * * @since 1.0 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces * @param string $namespace The URL of the XML namespace of the elements you're trying to access * @param string $tag Tag name * @return array */ public function get_channel_tags($namespace, $tag) { $type = $this->get_type(); if ($type & SIMPLEPIE_TYPE_ATOM_ALL) { if ($return = $this->get_feed_tags($namespace, $tag)) { return $return; } } if ($type & SIMPLEPIE_TYPE_RSS_10) { if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'channel')) { if (isset($channel[0]['child'][$namespace][$tag])) { return $channel[0]['child'][$namespace][$tag]; } } } if ($type & SIMPLEPIE_TYPE_RSS_090) { if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'channel')) { if (isset($channel[0]['child'][$namespace][$tag])) { return $channel[0]['child'][$namespace][$tag]; } } } if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) { if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'channel')) { if (isset($channel[0]['child'][$namespace][$tag])) { return $channel[0]['child'][$namespace][$tag]; } } } return null; } /** * Get data for an channel-level element * * This method allows you to get access to ANY element/attribute in the * image/logo section of the feed. * * See {@see SimplePie::get_feed_tags()} for a description of the return value * * @since 1.0 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces * @param string $namespace The URL of the XML namespace of the elements you're trying to access * @param string $tag Tag name * @return array */ public function get_image_tags($namespace, $tag) { $type = $this->get_type(); if ($type & SIMPLEPIE_TYPE_RSS_10) { if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'image')) { if (isset($image[0]['child'][$namespace][$tag])) { return $image[0]['child'][$namespace][$tag]; } } } if ($type & SIMPLEPIE_TYPE_RSS_090) { if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'image')) { if (isset($image[0]['child'][$namespace][$tag])) { return $image[0]['child'][$namespace][$tag]; } } } if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) { if ($image = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'image')) { if (isset($image[0]['child'][$namespace][$tag])) { return $image[0]['child'][$namespace][$tag]; } } } return null; } /** * Get the base URL value from the feed * * Uses `<xml:base>` if available, otherwise uses the first link in the * feed, or failing that, the URL of the feed itself. * * @see get_link * @see subscribe_url * * @param array $element * @return string */ public function get_base($element = array()) { if (!($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION) && !empty($element['xml_base_explicit']) && isset($element['xml_base'])) { return $element['xml_base']; } elseif ($this->get_link() !== null) { return $this->get_link(); } else { return $this->subscribe_url(); } } /** * Sanitize feed data * * @access private * @see SimplePie_Sanitize::sanitize() * @param string $data Data to sanitize * @param int $type One of the SIMPLEPIE_CONSTRUCT_* constants * @param string $base Base URL to resolve URLs against * @return string Sanitized data */ public function sanitize($data, $type, $base = '') { return $this->sanitize->sanitize($data, $type, $base); } /** * Get the title of the feed * * Uses `<atom:title>`, `<title>` or `<dc:title>` * * @since 1.0 (previously called `get_feed_title` since 0.8) * @return string|null */ public function get_title() { if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { return null; } } /** * Get a category for the feed * * @since Unknown * @param int $key The category that you want to return. Remember that arrays begin with 0, not 1 * @return SimplePie_Category|null */ public function get_category($key = 0) { $categories = $this->get_categories(); if (isset($categories[$key])) { return $categories[$key]; } else { return null; } } /** * Get all categories for the feed * * Uses `<atom:category>`, `<category>` or `<dc:subject>` * * @since Unknown * @return array|null List of {@see SimplePie_Category} objects */ public function get_categories() { $categories = array(); foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['attribs']['']['term'])) { $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); } $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); } foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) { // This is really the label, but keep this as the term also for BC. // Label will also work on retrieving because that falls back to term. $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); if (isset($category['attribs']['']['domain'])) { $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); } else { $scheme = null; } $categories[] = $this->registry->create('Category', array($term, $scheme, null)); } foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) { $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) { $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } if (!empty($categories)) { return array_unique($categories); } else { return null; } } /** * Get an author for the feed * * @since 1.1 * @param int $key The author that you want to return. Remember that arrays begin with 0, not 1 * @return SimplePie_Author|null */ public function get_author($key = 0) { $authors = $this->get_authors(); if (isset($authors[$key])) { return $authors[$key]; } else { return null; } } /** * Get all authors for the feed * * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>` * * @since 1.1 * @return array|null List of {@see SimplePie_Author} objects */ public function get_authors() { $authors = array(); foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) { $name = null; $uri = null; $email = null; if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); } if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $authors[] = $this->registry->create('Author', array($name, $uri, $email)); } } if ($author = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) { $name = null; $url = null; $email = null; if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); } if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $authors[] = $this->registry->create('Author', array($name, $url, $email)); } } foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) { $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); } if (!empty($authors)) { return array_unique($authors); } else { return null; } } /** * Get a contributor for the feed * * @since 1.1 * @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1 * @return SimplePie_Author|null */ public function get_contributor($key = 0) { $contributors = $this->get_contributors(); if (isset($contributors[$key])) { return $contributors[$key]; } else { return null; } } /** * Get all contributors for the feed * * Uses `<atom:contributor>` * * @since 1.1 * @return array|null List of {@see SimplePie_Author} objects */ public function get_contributors() { $contributors = array(); foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) { $name = null; $uri = null; $email = null; if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $contributors[] = $this->registry->create('Author', array($name, $uri, $email)); } } foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) { $name = null; $url = null; $email = null; if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); } if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $contributors[] = $this->registry->create('Author', array($name, $url, $email)); } } if (!empty($contributors)) { return array_unique($contributors); } else { return null; } } /** * Get a single link for the feed * * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8) * @param int $key The link that you want to return. Remember that arrays begin with 0, not 1 * @param string $rel The relationship of the link to return * @return string|null Link URL */ public function get_link($key = 0, $rel = 'alternate') { $links = $this->get_links($rel); if (isset($links[$key])) { return $links[$key]; } else { return null; } } /** * Get the permalink for the item * * Returns the first link available with a relationship of "alternate". * Identical to {@see get_link()} with key 0 * * @see get_link * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8) * @internal Added for parity between the parent-level and the item/entry-level. * @return string|null Link URL */ public function get_permalink() { return $this->get_link(0); } /** * Get all links for the feed * * Uses `<atom:link>` or `<link>` * * @since Beta 2 * @param string $rel The relationship of links to return * @return array|null Links found for the feed (strings) */ public function get_links($rel = 'alternate') { if (!isset($this->data['links'])) { $this->data['links'] = array(); if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link')) { foreach ($links as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); } } } if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link')) { foreach ($links as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); } } } if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); } $keys = array_keys($this->data['links']); foreach ($keys as $key) { if ($this->registry->call('Misc', 'is_isegment_nz_nc', array($key))) { if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) { $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; } else { $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; } } elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) { $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; } $this->data['links'][$key] = array_unique($this->data['links'][$key]); } } if (isset($this->data['links'][$rel])) { return $this->data['links'][$rel]; } else { return null; } } public function get_all_discovered_feeds() { return $this->all_discovered_feeds; } /** * Get the content for the item * * Uses `<atom:subtitle>`, `<atom:tagline>`, `<description>`, * `<dc:description>`, `<itunes:summary>` or `<itunes:subtitle>` * * @since 1.0 (previously called `get_feed_description()` since 0.8) * @return string|null */ public function get_description() { if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); } else { return null; } } /** * Get the copyright info for the feed * * Uses `<atom:rights>`, `<atom:copyright>` or `<dc:rights>` * * @since 1.0 (previously called `get_feed_copyright()` since 0.8) * @return string|null */ public function get_copyright() { if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright')) { return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { return null; } } /** * Get the language for the feed * * Uses `<language>`, `<dc:language>`, or @xml_lang * * @since 1.0 (previously called `get_feed_language()` since 0.8) * @return string|null */ public function get_language() { if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'])) { return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'])) { return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'])) { return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif (isset($this->data['headers']['content-language'])) { return $this->sanitize($this->data['headers']['content-language'], SIMPLEPIE_CONSTRUCT_TEXT); } else { return null; } } /** * Get the latitude coordinates for the item * * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications * * Uses `<geo:lat>` or `<georss:point>` * * @since 1.0 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo * @link http://www.georss.org/ GeoRSS * @return string|null */ public function get_latitude() { if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) { return (float) $return[0]['data']; } elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[1]; } else { return null; } } /** * Get the longitude coordinates for the feed * * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications * * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>` * * @since 1.0 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo * @link http://www.georss.org/ GeoRSS * @return string|null */ public function get_longitude() { if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) { return (float) $return[0]['data']; } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) { return (float) $return[0]['data']; } elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[2]; } else { return null; } } /** * Get the feed logo's title * * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" title. * * Uses `<image><title>` or `<image><dc:title>` * * @return string|null */ public function get_image_title() { if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); } else { return null; } } /** * Get the feed logo's URL * * RSS 0.9.0, 2.0, Atom 1.0, and feeds with iTunes RSS tags are allowed to * have a "feed logo" URL. This points directly to the image itself. * * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`, * `<image><title>` or `<image><dc:title>` * * @return string|null */ public function get_image_url() { if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image')) { return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'url')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'url')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } else { return null; } } /** * Get the feed logo's link * * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" link. This * points to a human-readable page that the image should link to. * * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`, * `<image><title>` or `<image><dc:title>` * * @return string|null */ public function get_image_link() { if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) { return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); } else { return null; } } /** * Get the feed logo's link * * RSS 2.0 feeds are allowed to have a "feed logo" width. * * Uses `<image><width>` or defaults to 88.0 if no width is specified and * the feed is an RSS 2.0 feed. * * @return int|float|null */ public function get_image_width() { if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'width')) { return round($return[0]['data']); } elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) { return 88.0; } else { return null; } } /** * Get the feed logo's height * * RSS 2.0 feeds are allowed to have a "feed logo" height. * * Uses `<image><height>` or defaults to 31.0 if no height is specified and * the feed is an RSS 2.0 feed. * * @return int|float|null */ public function get_image_height() { if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'height')) { return round($return[0]['data']); } elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) { return 31.0; } else { return null; } } /** * Get the number of items in the feed * * This is well-suited for {@link http://php.net/for for()} loops with * {@see get_item()} * * @param int $max Maximum value to return. 0 for no limit * @return int Number of items in the feed */ public function get_item_quantity($max = 0) { $max = (int) $max; $qty = count($this->get_items()); if ($max === 0) { return $qty; } else { return ($qty > $max) ? $max : $qty; } } /** * Get a single item from the feed * * This is better suited for {@link http://php.net/for for()} loops, whereas * {@see get_items()} is better suited for * {@link http://php.net/foreach foreach()} loops. * * @see get_item_quantity() * @since Beta 2 * @param int $key The item that you want to return. Remember that arrays begin with 0, not 1 * @return SimplePie_Item|null */ public function get_item($key = 0) { $items = $this->get_items(); if (isset($items[$key])) { return $items[$key]; } else { return null; } } /** * Get all items from the feed * * This is better suited for {@link http://php.net/for for()} loops, whereas * {@see get_items()} is better suited for * {@link http://php.net/foreach foreach()} loops. * * @see get_item_quantity * @since Beta 2 * @param int $start Index to start at * @param int $end Number of items to return. 0 for all items after `$start` * @return array|null List of {@see SimplePie_Item} objects */ public function get_items($start = 0, $end = 0) { if (!isset($this->data['items'])) { if (!empty($this->multifeed_objects)) { $this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit); } else { $this->data['items'] = array(); if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'entry')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->registry->create('Item', array($this, $items[$key])); } } if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'entry')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->registry->create('Item', array($this, $items[$key])); } } if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'item')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->registry->create('Item', array($this, $items[$key])); } } if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'item')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->registry->create('Item', array($this, $items[$key])); } } if ($items = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'item')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->registry->create('Item', array($this, $items[$key])); } } } } if (!empty($this->data['items'])) { // If we want to order it by date, check if all items have a date, and then sort it if ($this->order_by_date && empty($this->multifeed_objects)) { if (!isset($this->data['ordered_items'])) { $do_sort = true; foreach ($this->data['items'] as $item) { if (!$item->get_date('U')) { $do_sort = false; break; } } $item = null; $this->data['ordered_items'] = $this->data['items']; if ($do_sort) { usort($this->data['ordered_items'], array(get_class($this), 'sort_items')); } } $items = $this->data['ordered_items']; } else { $items = $this->data['items']; } // Slice the data as desired if ($end === 0) { return array_slice($items, $start); } else { return array_slice($items, $start, $end); } } else { return array(); } } /** * Set the favicon handler * * @deprecated Use your own favicon handling instead */ public function set_favicon_handler($page = false, $qs = 'i') { $level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING; trigger_error('Favicon handling has been removed, please use your own handling', $level); return false; } /** * Get the favicon for the current feed * * @deprecated Use your own favicon handling instead */ public function get_favicon() { $level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING; trigger_error('Favicon handling has been removed, please use your own handling', $level); if (($url = $this->get_link()) !== null) { return 'http://g.etfv.co/' . urlencode($url); } return false; } /** * Magic method handler * * @param string $method Method name * @param array $args Arguments to the method * @return mixed */ public function __call($method, $args) { if (strpos($method, 'subscribe_') === 0) { $level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING; trigger_error('subscribe_*() has been deprecated, implement the callback yourself', $level); return ''; } if ($method === 'enable_xml_dump') { $level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING; trigger_error('enable_xml_dump() has been deprecated, use get_raw_data() instead', $level); return false; } $class = get_class($this); $trace = debug_backtrace(); $file = $trace[0]['file']; $line = $trace[0]['line']; trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR); } /** * Sorting callback for items * * @access private * @param SimplePie $a * @param SimplePie $b * @return boolean */ public static function sort_items($a, $b) { return $a->get_date('U') <= $b->get_date('U'); } /** * Merge items from several feeds into one * * If you're merging multiple feeds together, they need to all have dates * for the items or else SimplePie will refuse to sort them. * * @link http://simplepie.org/wiki/tutorial/sort_multiple_feeds_by_time_and_date#if_feeds_require_separate_per-feed_settings * @param array $urls List of SimplePie feed objects to merge * @param int $start Starting item * @param int $end Number of items to return * @param int $limit Maximum number of items per feed * @return array */ public static function merge_items($urls, $start = 0, $end = 0, $limit = 0) { if (is_array($urls) && sizeof($urls) > 0) { $items = array(); foreach ($urls as $arg) { if ($arg instanceof SimplePie) { $items = array_merge($items, $arg->get_items(0, $limit)); } else { trigger_error('Arguments must be SimplePie objects', E_USER_WARNING); } } $do_sort = true; foreach ($items as $item) { if (!$item->get_date('U')) { $do_sort = false; break; } } $item = null; if ($do_sort) { usort($items, array(get_class($urls[0]), 'sort_items')); } if ($end === 0) { return array_slice($items, $start); } else { return array_slice($items, $start, $end); } } else { trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING); return array(); } } } ================================================ FILE: license.txt ================================================ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>. ================================================ FILE: makefulltextfeed.php ================================================ <?php // Full-Text RSS: Create Full-Text Feeds // Author: Keyvan Minoukadeh // Copyright (c) 2013 Keyvan Minoukadeh // License: AGPLv3 // Version: 3.2 // Date: 2013-05-13 // More info: http://fivefilters.org/content-only/ // Help: http://help.fivefilters.org /* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ // Usage // ----- // Request this file passing it a web page or feed URL in the querystring: makefulltextfeed.php?url=example.org/article // For more request parameters, see http://help.fivefilters.org/customer/portal/articles/226660-usage error_reporting(E_ALL ^ E_NOTICE); ini_set('display_errors', 'On'); ini_set('xdebug.overload_var_dump', '0'); mb_internal_encoding('UTF-8'); @set_time_limit(600); // Deal with magic quotes if (get_magic_quotes_gpc()) { $process = array(&$_GET, &$_POST, &$_REQUEST); while (list($key, $val) = each($process)) { foreach ($val as $k => $v) { unset($process[$key][$k]); if (is_array($v)) { $process[$key][stripslashes($k)] = $v; $process[] = &$process[$key][stripslashes($k)]; } else { $process[$key][stripslashes($k)] = stripslashes($v); } } } unset($process); } // set include path set_include_path(realpath(dirname(__FILE__).'/libraries').PATH_SEPARATOR.get_include_path()); function encodeURI($url) { // http://php.net/manual/en/function.rawurlencode.php // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI $reserved = array( '%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')' ); $unescaped = array( '%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':', '%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$'); $score = array( '%23'=>'#' ); static $url_entities = null; if ($url_entities === null) $url_entities = array_merge($reserved, $unescaped, $score); $parsed_url = parse_url($url); $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; $pass = ($user || $pass) ? "$pass@" : ''; $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; return $scheme.$user.$pass.$host.$port.strtr(rawurlencode($path.$query.$fragment), $url_entities); } // Autoloading of classes allows us to include files only when they're // needed. If we've got a cached copy, for example, only Zend_Cache is loaded. function autoload($class_name) { static $dir = null; if ($dir === null) $dir = dirname(__FILE__).'/libraries/'; static $mapping = array( // Include FeedCreator for RSS/Atom creation 'FeedWriter' => 'feedwriter/FeedWriter.php', 'FeedItem' => 'feedwriter/FeedItem.php', // Include ContentExtractor and Readability for identifying and extracting content from URLs 'ContentExtractor' => 'content-extractor/ContentExtractor.php', 'SiteConfig' => 'content-extractor/SiteConfig.php', 'Readability' => 'readability/Readability.php', // Include Humble HTTP Agent to allow parallel requests and response caching 'HumbleHttpAgent' => 'humble-http-agent/HumbleHttpAgent.php', 'SimplePie_HumbleHttpAgent' => 'humble-http-agent/SimplePie_HumbleHttpAgent.php', 'CookieJar' => 'humble-http-agent/CookieJar.php', // Include Zend Cache to improve performance (cache results) 'Zend_Cache' => 'Zend/Cache.php', // Language detect 'Text_LanguageDetect' => 'language-detect/LanguageDetect.php', // HTML5 Lib 'HTML5_Parser' => 'html5/Parser.php', // htmLawed - used if XSS filter is enabled (xss_filter) 'htmLawed' => 'htmLawed/htmLawed2.php' ); if (isset($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 dirname(__FILE__).'/libraries/simplepie/autoloader.php'; //////////////////////////////// // Load config file //////////////////////////////// require dirname(__FILE__).'/config.php'; //////////////////////////////// // Prevent indexing/following by search engines because: // 1. The content is already public and presumably indexed (why create duplicates?) // 2. Not doing so might increase number of requests from search engines, thus increasing server load // Note: feed readers and services such as Yahoo Pipes will not be affected by this header. // Note: Using Disallow in a robots.txt file will be more effective (search engines will check // that before even requesting makefulltextfeed.php). //////////////////////////////// header('X-Robots-Tag: noindex, nofollow'); //////////////////////////////// // Check if service is enabled //////////////////////////////// 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']) && intval($_GET['debug']) === 1) { if ($options->debug === true || $options->debug === 'user') { $debug_mode = true; } elseif ($options->debug == 'admin') { session_start(); $debug_mode = (intval(@$_SESSION['auth']) === 1); } if ($debug_mode) { header('Content-Type: text/html; charset=utf-8'); echo '<style>body {font:12px "Lucida Console", Monaco, monospace;}</style>'; debug('Running in debugging mode...'); } else { if ($options->debug == 'admin') { die('You must be logged in to the <a href="admin/">admin area</a> 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 //////////////////////////////// if (!isset($_GET['url'])) { die('No URL supplied'); } $url = filter_var(trim($_GET['url']), FILTER_SANITIZE_URL); if (strtolower(substr($url, 0, 7)) == 'feed://') { $url = 'http://'.substr($url, 7); } if (!preg_match('!^https?://.+!i', $url)) { $url = 'http://'.$url; } $test = filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); // deal with bug http://bugs.php.net/51192 (present in PHP 5.2.13 and PHP 5.3.2) if ($test === false) { $test = filter_var(strtr($url, '-', '_'), FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); } if ($test !== false && $test !== null && preg_match('!^https?://!', $url)) { // all okay unset($test); } else { die('Invalid URL supplied'); } debug("Supplied URL: $url"); ///////////////////////////////// // Redirect to hide API key ///////////////////////////////// if (isset($_GET['key']) && ($key_index = array_search($_GET['key'], $options->api_keys)) !== false) { $host = $_SERVER['HTTP_HOST']; $path = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\'); $_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['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['use_extracted_title'])) $redirect .= '&use_extracted_title'; if (isset($_GET['content'])) $redirect .= '&content='.urlencode($_GET['content']); if (isset($_GET['summary'])) $redirect .= '&summary='.urlencode($_GET['summary']); if (isset($_GET['debug'])) $redirect .= '&debug'; if ($debug_mode) { debug('Redirecting to hide access key, follow URL below to continue'); debug("Location: $redirect"); } else { header("Location: $redirect"); } exit; } /////////////////////////////////////////////// // Set timezone. // Prevents warnings, but needs more testing perhaps if timezone is set in php.ini we // don't need to set it at all... /////////////////////////////////////////////// if (!ini_get('date.timezone') || !@date_default_timezone_set(ini_get('date.timezone'))) { date_default_timezone_set('UTC'); } /////////////////////////////////////////////// // Check if the request is explicitly for an HTML page /////////////////////////////////////////////// $html_only = (isset($_GET['html']) && (intval($_GET['html']) == 1 || $_GET['html'] == 'true')); /////////////////////////////////////////////// // Check if valid key supplied /////////////////////////////////////////////// $valid_key = false; if (isset($_GET['key']) && isset($_GET['hash']) && isset($options->api_keys[(int)$_GET['key']])) { $valid_key = ($_GET['hash'] == sha1($options->api_keys[(int)$_GET['key']].$url)); } $key_index = ($valid_key) ? intval($_GET['key']) : 0; if (!$valid_key && $options->key_required) { die('A valid key must be supplied'); } if (!$valid_key && isset($_GET['key']) && $_GET['key'] != '') { die('The entered key is invalid'); } if (file_exists('custom_init.php')) require 'custom_init.php'; /////////////////////////////////////////////// // Check URL against list of blacklisted URLs /////////////////////////////////////////////// if (!url_allowed($url)) die('URL blocked'); /////////////////////////////////////////////// // Max entries // see config.php to find these values /////////////////////////////////////////////// if (isset($_GET['max'])) { $max = intval($_GET['max']); if ($valid_key) { $max = min($max, $options->max_entries_with_key); } else { $max = min($max, $options->max_entries); } } else { if ($valid_key) { $max = $options->default_entries_with_key; } else { $max = $options->default_entries; } } /////////////////////////////////////////////// // Link handling /////////////////////////////////////////////// if (isset($_GET['links']) && in_array($_GET['links'], array('preserve', 'footnotes', 'remove'))) { $links = $_GET['links']; } else { $links = 'preserve'; } /////////////////////////////////////////////// // Favour item titles in feed? /////////////////////////////////////////////// $favour_feed_titles = true; if ($options->favour_feed_titles == 'user') { $favour_feed_titles = !isset($_GET['use_extracted_title']); } else { $favour_feed_titles = $options->favour_feed_titles; } /////////////////////////////////////////////// // Include full content in output? /////////////////////////////////////////////// if ($options->content === 'user') { if (isset($_GET['content']) && intval($_GET['content']) === 0) { $options->content = false; } else { $options->content = true; } } /////////////////////////////////////////////// // Include summaries in output? /////////////////////////////////////////////// if ($options->summary === 'user') { if (isset($_GET['summary']) && intval($_GET['summary']) === 1) { $options->summary = true; } else { $options->summary = false; } } /////////////////////////////////////////////// // Exclude items if extraction fails /////////////////////////////////////////////// if ($options->exclude_items_on_fail === 'user') { $exclude_on_fail = (isset($_GET['exc']) && (intval($_GET['exc']) === 1)); } else { $exclude_on_fail = $options->exclude_items_on_fail; } /////////////////////////////////////////////// // Detect language /////////////////////////////////////////////// if ($options->detect_language === 'user') { if (isset($_GET['l'])) { $detect_language = intval($_GET['l']); } else { $detect_language = 1; } } else { $detect_language = $options->detect_language; } $use_cld = extension_loaded('cld') && (version_compare(PHP_VERSION, '5.3.0') >= 0); ///////////////////////////////////// // Check for valid format // (stick to RSS (or RSS as JSON) for the time being) ///////////////////////////////////// if (isset($_GET['format']) && $_GET['format'] == 'json') { $format = 'json'; } else { $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) { debug('Caching is enabled...'); $cache_id = md5((int)$max.$url.$links.(int)$favour_feed_titles.(int)$options->content.(int)$options->summary.(int)$xss_filter.(int)$exclude_on_fail.$format.$detect_language); $check_cache = true; if ($options->apc === true && $options->smart_cache === true) { apc_add("cache.$cache_id", 0, isset($options->cache_ttl) ? $options->cache_ttl : 10*60); $apc_cache_hits = intval(apc_fetch("cache.$cache_id")); apc_inc("cache.$cache_id"); if ($check_cache = ($apc_cache_hits >= 2)) { debug('Cache key found in APC, we\'ll try to load cache file from disk'); } else { debug('Cache key not found in APC'); } } if ($check_cache === true) { $cache = get_cache(); if ($data = $cache->load($cache_id)) { if ($debug_mode) { debug('Loaded cached copy of RSS'); 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; } } } ////////////////////////////////// // Set Expires header ////////////////////////////////// if (!$debug_mode) { header('Expires: ' . gmdate('D, d M Y H:i:s', time()+ (isset($options->cache_ttl) ? $options->cache_ttl : 10*60)) . ' 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; //$http->initCache($options->cache_dir, $options->cache_directory_level, $options->cache_cleanup, isset($options->http_cache_ttl) ? $options->http_cache_ttl : 12*60*60); ////////////////////////////////// // 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; $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 SimplePie_HumbleHttpAgent::set_agent($http); $feed = new SimplePie(); // some feeds use the text/html content type - force_feed tells SimplePie to process anyway $feed->force_feed(true); $feed->set_file_class('SimplePie_HumbleHttpAgent'); //$feed->set_feed_url($url); // colons appearing in the URL's path get encoded $feed->feed_url = $url; $feed->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE); $feed->set_timeout(20); $feed->enable_cache(false); $feed->set_stupidly_fast(true); $feed->enable_order_by_date(false); // we don't want to do anything to the feed $feed->set_url_replacements(array()); // initialise the feed // the @ suppresses notices which on some servers causes a 500 internal server error $result = @$feed->init(); //$feed->handle_content_type(); //$feed->get_title(); if ($result && (!is_array($feed->data) || count($feed->data) == 0)) { die('Sorry, no feed items found'); } // from now on, we'll identify ourselves as a browser $http->userAgentDefault = HumbleHttpAgent::UA_BROWSER; // Enable caching for multiple downloader if (class_exists('HttpRequestPool')) { $http->method = $http::METHOD_REQUEST_POOL; } elseif (function_exists('curl_multi_init')) { $http->method = $http::METHOD_CURL_MULTI; } else { $http->method = $http::METHOD_FILE_GET_CONTENTS; } } //////////////////////////////////////////////////////////////////////////////// // Our given URL is not a feed, so let's create our own feed with a single item: // the given URL. This basically treats all non-feed URLs as if they were // single-item feeds. //////////////////////////////////////////////////////////////////////////////// $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 class DummySingleItemFeed { public $item; function __construct($url) { $this->item = new DummySingleItem($url); } public function get_title() { return ''; } public function get_description() { return 'Content extracted from '.$this->item->url; } public function get_link() { return $this->item->url; } public function get_language() { return false; } public function get_image_url() { return false; } public function get_items($start=0, $max=1) { return array(0=>$this->item); } } class DummySingleItem { public $url; function __construct($url) { $this->url = $url; } public function get_permalink() { return $this->url; } public function get_title() { return null; } public function get_date($format='') { return false; } public function get_author($key=0) { return null; } public function get_authors() { return null; } public function get_description() { return ''; } public function get_enclosure($key=0, $prefer=null) { return null; } public function get_enclosures() { return null; } public function get_categories() { return null; } } $feed = new DummySingleItemFeed($url); } //////////////////////////////////////////// // Create full-text feed //////////////////////////////////////////// $output = new FeedWriter(); $output->setTitle(strip_tags($feed->get_title())); $output->setDescription(strip_tags($feed->get_description())); $output->setXsl('css/feed.xsl'); // Chrome uses this, most browsers ignore it if ($valid_key && isset($_GET['pubsub'])) { // used only on fivefilters.org at the moment $output->addHub('http://fivefilters.superfeedr.com/'); $output->addHub('http://pubsubhubbub.appspot.com/'); $output->setSelf('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); } $output->setLink($feed->get_link()); // Google Reader uses this for pulling in favicons if ($img_url = $feed->get_image_url()) { $output->setImage($feed->get_title(), $feed->get_link(), $img_url); } //////////////////////////////////////////// // Loop through feed items //////////////////////////////////////////// $items = $feed->get_items(0, $max); // Request all feed items in parallel (if supported) $urls_sanitized = array(); $urls = array(); foreach ($items as $key => $item) { $permalink = htmlspecialchars_decode($item->get_permalink()); // Colons in URL path segments get encoded by SimplePie, yet some sites expect them unencoded $permalink = str_replace('%3A', ':', $permalink); // simplepie already sanitizes URLs so let's not do it again here. if ($permalink && !is_cached($permalink)) $urls_sanitized[] = $permalink; $urls[$key] = encodeURI($permalink); } debug('--------'); debug('Fetching feed items'); $http->fetchAll($urls_sanitized); // count number of items added to full feed $item_count = 0; foreach ($items as $key => $item) { debug('--------'); $do_content_extraction = true; debug('Processing feed item '.sprintf('%02d', $item_count+1).'.'); $extraction_successful = false; $text_sample = null; $permalink = $urls[$key]; debug("Item URL: $permalink"); $extracted_title = ''; $feed_item_title = $item->get_title(); if ($feed_item_title !== null) { $feed_item_title = strip_tags(htmlspecialchars_decode($feed_item_title)); } $newitem = $output->createNewItem(); $newitem->setTitle($feed_item_title); if (isset($permalink) && is_string($permalink)) { $newitem->setLink($permalink); } else { $permalink = $item->get_permalink(); $newitem->setLink($permalink); } if (isset($permalink) && is_string($permalink) && strlen($permalink) < 9) { continue; } //if ($permalink && ($response = $http->get($permalink, true)) && $response['status_code'] < 300) { // Allowing error codes - some sites return correct content with error status // e.g. prospectmagazine.co.uk returns 403 $cached_page = is_cached($permalink); if ($cached_page) { debug('Loading processed page form cache...'); $html = get_cached($permalink); $extraction_successful = true; } else if ((($response = $http->get($permalink, true)) && ($response['status_code'] < 300 || $response['status_code'] > 400))) { $effective_url = $response['effective_url']; if (!url_allowed($effective_url)) continue; // check if action defined for returned Content-Type $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 = "<a href=\"$effective_url\"><img src=\"$effective_url\" alt=\"{$mime_info['name']}\" /></a>"; } else { $html = "<a href=\"$effective_url\">Download {$mime_info['name']}</a>"; } $extracted_title = $mime_info['name']; $do_content_extraction = false; } } if ($do_content_extraction) { $html = $response['body']; // remove strange things $html = str_replace('</[>', '', $html); $html = convert_to_utf8($html, $response['headers']); // 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; $effective_url = $single_page_response['effective_url']; // check if action defined for returned Content-Type $mime_info = get_mime_action_info($single_page_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 = "<a href=\"$effective_url\"><img src=\"$effective_url\" alt=\"{$mime_info['name']}\" /></a>"; } else { $html = "<a href=\"$effective_url\">Download {$mime_info['name']}</a>"; } $extracted_title = $mime_info['name']; $do_content_extraction = false; } } if ($do_content_extraction) { $html = $single_page_response['body']; // remove strange things $html = str_replace('</[>', '', $html); $html = convert_to_utf8($html, $single_page_response['headers']); debug("Retrieved single-page view from $effective_url"); } unset($single_page_response); } } if ($do_content_extraction) { debug('--------'); debug('Attempting to extract content'); $extraction_successful = $extractor->process($html, $effective_url); $readability = $extractor->readability; $content_block = ($extraction_successful) ? $extractor->getContent() : null; $extracted_title = ($extraction_successful) ? $extractor->getTitle() : ''; // Deal with multi-page articles //die('Next: '.$extractor->getNextPageUrl()); $is_multi_page = (!$is_single_page && $extraction_successful && $extractor->getNextPageUrl()); if ($options->multipage && $is_multi_page && $options->content) { 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; } // 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'); $_page = $readability->dom->createElement('p'); $_page->innerHTML = '<em>This article appears to continue on subsequent pages which we could not extract</em>'; $multi_page_content[] = $_page; } 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, $_page); } } // use extracted title for both feed and item title if we're using single-item dummy feed if ($isDummyFeed) { $output->setTitle($extracted_title); $newitem->setTitle($extracted_title); } else { // use extracted title instead of feed item title? if (!$favour_feed_titles && $extracted_title != '') { debug('Using extracted title in generated feed'); $newitem->setTitle($extracted_title); } } } if ($do_content_extraction) { // if we failed to extract content... if (!$extraction_successful) { 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 { if (!$cached_page) { $readability->clean($content_block, 'select'); if ($options->rewrite_relative_urls) makeAbsolute($effective_url, $content_block); // footnotes if (($links == 'footnotes') && (strpos($effective_url, 'wikipedia.org') === false)) { $readability->addFootnotes($content_block); } // remove nesting: <div><div><div><p>test</p></div></div></div> = <p>test</p> 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; $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 } // post-processing cleanup $html = preg_replace('!<p>[\s\h\v]*</p>!u', '', $html); if ($links == 'remove') { $html = preg_replace('!</?a[^>]*>!', '', $html); } // get text sample for language detection $text_sample = strip_tags(substr($html, 0, 500)); if ($options->message_to_prepend) $html = make_substitutions($options->message_to_prepend).$html; if ($options->message_to_append) $html .= make_substitutions($options->message_to_append); // 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)); } set_cached($permalink, $html); } } } $newitem->addElement('guid', $item->get_permalink(), array('isPermaLink'=>'true')); // add content if ($options->summary === true) { // get summary $summary = ''; if (!$do_content_extraction) { $summary = $html; } else { // Try to get first few paragraphs if (isset($content_block) && ($content_block instanceof DOMElement)) { $_paras = $content_block->getElementsByTagName('p'); foreach ($_paras as $_para) { $summary .= preg_replace("/[\n\r\t ]+/", ' ', $_para->textContent).' '; if (strlen($summary) > 200) break; } } else { $summary = $html; } } $summary = get_excerpt($summary); $newitem->setDescription($summary); if ($options->content) $newitem->setElement('content:encoded', $html); unset($_paras, $_para, $content_block); } else { if ($options->content) $newitem->setDescription($html); unset($content_block); } // set date if ((int)$item->get_date('U') > 0) { $newitem->setDate((int)$item->get_date('U')); } elseif ($extractor->getDate()) { $newitem->setDate($extractor->getDate()); } // add authors if ($authors = $item->get_authors()) { foreach ($authors as $author) { // for some feeds, SimplePie stores author's name as email, e.g. http://feeds.feedburner.com/nymag/intel if ($author->get_name() !== null) { $newitem->addElement('dc:creator', $author->get_name()); } elseif ($author->get_email() !== null) { $newitem->addElement('dc:creator', $author->get_email()); } } } elseif ($authors = $extractor->getAuthors()) { //TODO: make sure the list size is reasonable foreach ($authors as $author) { // TODO: xpath often selects authors from other articles linked from the page. // for now choose first item $newitem->addElement('dc:creator', $author); break; } } // add language if ($detect_language) { $language = $extractor->getLanguage(); if (!$language) $language = $feed->get_language(); if (($detect_language == 3 || (!$language && $detect_language == 2)) && $text_sample) { try { if ($use_cld) { // Use PHP-CLD extension $php_cld = 'CLD\detect'; // in quotes to prevent PHP 5.2 parse error $res = $php_cld($text_sample); if (is_array($res) && count($res) > 0) { $language = $res[0]['code']; } } else { //die('what'); // Use PEAR's Text_LanguageDetect if (!isset($l)) { $l = new Text_LanguageDetect(); $l->setNameMode(2); // return ISO 639-1 codes (e.g. "en") } $l_result = $l->detect($text_sample, 1); if (count($l_result) > 0) { $language = key($l_result); } } } catch (Exception $e) { //die('error: '.$e); // do nothing } } if ($language && (strlen($language) < 7)) { $newitem->addElement('dc:language', $language); } } // add MIME type (if it appeared in our exclusions lists) 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. //http://www.siasat.pk/forum/showthread.php?108883-Pakistan-Chowk-by-Rana-Mubashir--25th-March-2012-Special-Program-from-Liari-(Karachi) //temporary measure: use utf8_encode() $newitem->addElement('dc:identifier', remove_url_cruft(utf8_encode($effective_url))); } else { $newitem->addElement('dc:identifier', remove_url_cruft($item->get_permalink())); } // add categories if ($categories = $item->get_categories()) { foreach ($categories as $category) { if ($category->get_label() !== null) { $newitem->addElement('category', $category->get_label()); } } } // check for enclosures if ($options->keep_enclosures) { if ($enclosures = $item->get_enclosures()) { foreach ($enclosures as $enclosure) { // thumbnails foreach ((array)$enclosure->get_thumbnails() as $thumbnail) { $newitem->addElement('media:thumbnail', '', array('url'=>$thumbnail)); } if (!$enclosure->get_link()) continue; $enc = array(); // Media RSS spec ($enc): http://search.yahoo.com/mrss // SimplePie methods ($enclosure): http://simplepie.org/wiki/reference/start#methods4 $enc['url'] = $enclosure->get_link(); if ($enclosure->get_length()) $enc['fileSize'] = $enclosure->get_length(); if ($enclosure->get_type()) $enc['type'] = $enclosure->get_type(); if ($enclosure->get_medium()) $enc['medium'] = $enclosure->get_medium(); if ($enclosure->get_expression()) $enc['expression'] = $enclosure->get_expression(); if ($enclosure->get_bitrate()) $enc['bitrate'] = $enclosure->get_bitrate(); if ($enclosure->get_framerate()) $enc['framerate'] = $enclosure->get_framerate(); if ($enclosure->get_sampling_rate()) $enc['samplingrate'] = $enclosure->get_sampling_rate(); if ($enclosure->get_channels()) $enc['channels'] = $enclosure->get_channels(); if ($enclosure->get_duration()) $enc['duration'] = $enclosure->get_duration(); if ($enclosure->get_height()) $enc['height'] = $enclosure->get_height(); if ($enclosure->get_width()) $enc['width'] = $enclosure->get_width(); if ($enclosure->get_language()) $enc['lang'] = $enclosure->get_language(); $newitem->addElement('media:content', '', $enc); } } } $output->addItem($newitem); unset($html); $item_count++; } // output feed 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 === true); // is smart cache mode enabled? if ($add_to_cache && $options->apc === true && $options->smart_cache === true) { // 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']); } } } } } 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(); $cache->save($output, $cache_id); } echo $output; } else { $output->genarateFeed(); } if ($callback) echo ');'; } /////////////////////////////// // HELPER FUNCTIONS /////////////////////////////// // Adapted from WordPress: http://core.trac.wordpress.org/browser/tags/3.5.1/wp-includes/formatting.php#L2173 function get_excerpt($text, $num_words=55, $more=null) { if (null === $more) $more = '…'; $text = strip_tags($text); //TODO: Check if word count is based on single characters (East Asian characters) /* if (1==2) { $text = trim(preg_replace("/[\n\r\t ]+/", ' ', $text), ' '); preg_match_all('/./u', $text, $words_array); $words_array = array_slice($words_array[0], 0, $num_words + 1); $sep = ''; } else { $words_array = preg_split("/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY); $sep = ' '; } */ $words_array = preg_split("/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY); $sep = ' '; if (count($words_array) > $num_words) { array_pop($words_array); $text = implode($sep, $words_array); $text = $text.$more; } else { $text = implode($sep, $words_array); } // trim whitespace at beginning or end of string // See: http://stackoverflow.com/questions/4166896/trim-unicode-whitespace-in-php-5-2 $text = preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $text); return $text; } function url_allowed($url) { global $options; if (!empty($options->allowed_urls)) { $allowed = false; foreach ($options->allowed_urls as $allowurl) { if (stristr($url, $allowurl) !== false) { $allowed = true; break; } } if (!$allowed) return false; } else { foreach ($options->blocked_urls as $blockurl) { if (stristr($url, $blockurl) !== false) { return false; } } } return true; } ////////////////////////////////////////////// // Convert $html to UTF8 // (uses HTTP headers and HTML to find encoding) // adapted from http://stackoverflow.com/questions/910793/php-detect-encoding-and-make-everything-utf-8 ////////////////////////////////////////////// function convert_to_utf8($html, $header=null) { $encoding = null; if ($html || $header) { 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"); } // TODO: check to see if encoding is supported (can we convert it?) // If it's not, result will be empty string. // 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 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('/<meta\s+http-equiv=["\']?Content-Type["\']? content=["\'][^;]+;\s*charset=["\']?([^;"\'>]+)/i', $html_head, $match)) { $encoding = trim($match[1]); } elseif (preg_match_all('/<meta\s+([^>]+)>/i', $html_head, $match)) { foreach ($match[1] as $_test) { if (preg_match('/charset=["\']?([^"\']+)/i', $_test, $_m)) { $encoding = trim($_m[1]); break; } } } } if (isset($encoding)) $encoding = trim($encoding); // trim is important here! if (!$encoding || (strtolower($encoding) == 'iso-8859-1')) { // replace MS Word smart qutoes $trans = array(); $trans[chr(130)] = '‚'; // Single Low-9 Quotation Mark $trans[chr(131)] = 'ƒ'; // Latin Small Letter F With Hook $trans[chr(132)] = '„'; // Double Low-9 Quotation Mark $trans[chr(133)] = '…'; // Horizontal Ellipsis $trans[chr(134)] = '†'; // Dagger $trans[chr(135)] = '‡'; // Double Dagger $trans[chr(136)] = 'ˆ'; // Modifier Letter Circumflex Accent $trans[chr(137)] = '‰'; // Per Mille Sign $trans[chr(138)] = 'Š'; // Latin Capital Letter S With Caron $trans[chr(139)] = '‹'; // Single Left-Pointing Angle Quotation Mark $trans[chr(140)] = 'Œ'; // Latin Capital Ligature OE $trans[chr(145)] = '‘'; // Left Single Quotation Mark $trans[chr(146)] = '’'; // Right Single Quotation Mark $trans[chr(147)] = '“'; // Left Double Quotation Mark $trans[chr(148)] = '”'; // Right Double Quotation Mark $trans[chr(149)] = '•'; // Bullet $trans[chr(150)] = '–'; // En Dash $trans[chr(151)] = '—'; // Em Dash $trans[chr(152)] = '˜'; // Small Tilde $trans[chr(153)] = '™'; // Trade Mark Sign $trans[chr(154)] = 'š'; // Latin Small Letter S With Caron $trans[chr(155)] = '›'; // Single Right-Pointing Angle Quotation Mark $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'); } } } return $html; } function makeAbsolute($base, $elem) { $base = new SimplePie_IRI($base); // remove '//' in URL path (used to prevent URLs from resolving properly) // TODO: check if this is still the case if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path); foreach(array('a'=>'href', 'img'=>'src') as $tag => $attr) { $elems = $elem->getElementsByTagName($tag); for ($i = $elems->length-1; $i >= 0; $i--) { $e = $elems->item($i); //$e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e); makeAbsoluteAttr($base, $e, $attr); } if (strtolower($elem->tagName) == $tag) makeAbsoluteAttr($base, $elem, $attr); } } function makeAbsoluteAttr($base, $e, $attr) { if ($e->hasAttribute($attr)) { // Trim leading and trailing white space. I don't really like this but // unfortunately it does appear on some sites. e.g. <img src=" /path/to/image.jpg" /> $url = trim(str_replace('%20', ' ', $e->getAttribute($attr))); $url = str_replace(' ', '%20', $url); if (!preg_match('!https?://!i', $url)) { if ($absolute = SimplePie_IRI::absolutize($base, $url)) { $e->setAttribute($attr, $absolute); } } } } function makeAbsoluteStr($base, $url) { $base = new SimplePie_IRI($base); // remove '//' in URL path (causes URLs not to resolve properly) if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path); if (preg_match('!^https?://!i', $url)) { // already absolute return $url; } else { if ($absolute = SimplePie_IRI::absolutize($base, $url)) { return $absolute; } return false; } } // returns single page response, or false if not found function getSinglePage($item, $html, $url) { global $http, $extractor; 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; } elseif (!empty($site_config->single_page_link_in_feed)) { // single page link xpath is targeted at feed $splink = $site_config->single_page_link_in_feed; // so let's replace HTML with feed item description $html = $item->get_description(); } if (isset($splink)) { // Build DOM tree from HTML $readability = new Readability($html, $url); $xpath = new DOMXPath($readability->dom); // Loop through single_page_link xpath expressions $single_page_url = null; foreach ($splink as $pattern) { $elems = @$xpath->evaluate($pattern, $readability->dom); if (is_string($elems)) { $single_page_url = trim($elems); break; } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { foreach ($elems as $item) { if ($item instanceof DOMElement && $item->hasAttribute('href')) { $single_page_url = $item->getAttribute('href'); break 2; } elseif ($item instanceof DOMAttr && $item->value) { $single_page_url = $item->value; break 2; } } } } // If we've got URL, resolve against $url if (isset($single_page_url) && ($single_page_url = makeAbsoluteStr($url, $single_page_url))) { // check it's not what we have already! if ($single_page_url != $url) { // it's not, so let's try to fetch it... $_prev_ref = $http->referer; $http->referer = $single_page_url; if (($response = $http->get($single_page_url, true)) && $response['status_code'] < 300) { $http->referer = $_prev_ref; return $response; } $http->referer = $_prev_ref; } } } 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/ // https://gist.github.com/758177 return preg_replace('/(\?|\&)utm_[a-z]+=[^\&]+/', '', $url); } function make_substitutions($string) { if ($string == '') return $string; global $item, $effective_url; $string = str_replace('{url}', htmlspecialchars($item->get_permalink()), $string); $string = str_replace('{effective-url}', htmlspecialchars($effective_url), $string); return $string; } function get_cache() { global $options, $valid_key; if (!$options->caching) return; static $cache = null; if ($cache === null) { debug('RSS cache TTL is set to '.(isset($options->cache_ttl) ? $options->cache_ttl : 10*60).' sec.'); $frontendOptions = array( 'lifetime' => isset($options->cache_ttl) ? $options->cache_ttl : 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 get_url_cache() { global $options, $valid_key; if (!$options->caching) return; static $url_cache = null; if ($url_cache === null) { debug('HTTP cache TTL is set to '.(isset($options->http_cache_ttl) ? $options->http_cache_ttl : 3*24*60*60).' sec.'); $frontendOptions = array( 'lifetime' => isset($options->http_cache_ttl) ? $options->http_cache_ttl : 3*24*60*60, // cache lifetime 'automatic_serialization' => false, 'write_control' => false, 'automatic_cleaning_factor' => $options->cache_cleanup, 'ignore_user_abort' => false ); $backendOptions = array( 'cache_dir' => $options->cache_dir.'/urls/', // 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 $url_cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions); } return $url_cache; } function is_cached($url) { if (!($cache = get_url_cache()) || !$url) return false; return ($cache->test('request'.md5($url)) !== false); } function set_cached($url, $data) { if (!($cache = get_url_cache()) || !$url || !$data) return; $cache->save(serialize($data), 'request'.md5($url)); } function get_cached($url) { if (!($cache = get_url_cache()) || !$url) return null; return unserialize($cache->load('request'.md5($url))); } function debug($msg) { global $debug_mode; if ($debug_mode) { echo "* ",$msg,"<br />"; ob_flush(); flush(); } } ?> ================================================ FILE: manifest.yml ================================================ # This file is only used when deploying Full-Text RSS to AppFog. # See http://help.fivefilters.org/customer/portal/articles/1143210-hosting --- 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 ================================================ FILE: site_config/README.txt ================================================ Full-Text RSS Site Patterns --------------------------- Site patterns allow you to specify what should be extracted from specific sites. Please see http://help.fivefilters.org/customer/portal/articles/223153-site-patterns for more information. ================================================ FILE: site_config/custom/index.php ================================================ <?php // this is here to prevent directory listing over the web ?> ================================================ FILE: site_config/index.php ================================================ <?php // this is here to prevent directory listing over the web ================================================ FILE: site_config/standard/.wikipedia.org.txt ================================================ title: //h1[@id='firstHeading'] body: //div[@id = 'bodyContent'] strip_id_or_class: editsection #strip_id_or_class: toc strip_id_or_class: vertical-navbox strip: //table[@id='toc'] strip: //div[@id='catlinks'] strip: //div[@id='jump-to-nav'] strip: //div[@class='thumbcaption']//div[@class='magnify'] strip: //table[@class='navbox'] strip: //table[contains(@class, 'infobox')] strip: //div[@class='dablink'] strip: //div[@id='contentSub'] strip: //table[contains(@class, 'metadata')] strip: //*[contains(@class, 'noprint')] strip: //span[@title='pronunciation:'] prune: no tidy: no test_url: http://en.wikipedia.org/wiki/Christopher_Lloyd ================================================ FILE: site_config/standard/version.txt ================================================ 2013-05-12T22:53:07Z