PageRenderTime 59ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/phpBB/develop/generate_utf_tables.php

https://bitbucket.org/enitarzi/phpbb3-gorgon
PHP | 571 lines | 379 code | 64 blank | 128 comment | 43 complexity | 54dc8b45b60886e64650a28139844a81 MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * @package phpBB3
  5. * @copyright (c) 2005 phpBB Group
  6. * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
  7. *
  8. */
  9. if (php_sapi_name() != 'cli')
  10. {
  11. die("This program must be run from the command line.\n");
  12. }
  13. //
  14. // Security message:
  15. //
  16. // This script is potentially dangerous.
  17. // Remove or comment the next line (die(".... ) to enable this script.
  18. // Do NOT FORGET to either remove this script or disable it after you have used it.
  19. //
  20. die("Please read the first lines of this script for instructions on how to enable it");
  21. set_time_limit(0);
  22. define('IN_PHPBB', true);
  23. $phpbb_root_path = '../';
  24. $phpEx = substr(strrchr(__FILE__, '.'), 1);
  25. echo "Checking for required files\n";
  26. download('http://www.unicode.org/Public/UNIDATA/CompositionExclusions.txt');
  27. download('http://www.unicode.org/Public/UNIDATA/DerivedNormalizationProps.txt');
  28. download('http://www.unicode.org/Public/UNIDATA/UnicodeData.txt');
  29. echo "\n";
  30. require_once($phpbb_root_path . 'includes/utf/utf_normalizer.' . $phpEx);
  31. $file_contents = array();
  32. /**
  33. * Generate some Hangul/Jamo stuff
  34. */
  35. echo "\nGenerating Hangul and Jamo tables\n";
  36. for ($i = 0; $i < UNICODE_HANGUL_LCOUNT; ++$i)
  37. {
  38. $utf_char = cp_to_utf(UNICODE_HANGUL_LBASE + $i);
  39. $file_contents['utf_normalizer_common']['utf_jamo_index'][$utf_char] = $i * UNICODE_HANGUL_VCOUNT * UNICODE_HANGUL_TCOUNT + UNICODE_HANGUL_SBASE;
  40. $file_contents['utf_normalizer_common']['utf_jamo_type'][$utf_char] = UNICODE_JAMO_L;
  41. }
  42. for ($i = 0; $i < UNICODE_HANGUL_VCOUNT; ++$i)
  43. {
  44. $utf_char = cp_to_utf(UNICODE_HANGUL_VBASE + $i);
  45. $file_contents['utf_normalizer_common']['utf_jamo_index'][$utf_char] = $i * UNICODE_HANGUL_TCOUNT;
  46. $file_contents['utf_normalizer_common']['utf_jamo_type'][$utf_char] = UNICODE_JAMO_V;
  47. }
  48. for ($i = 0; $i < UNICODE_HANGUL_TCOUNT; ++$i)
  49. {
  50. $utf_char = cp_to_utf(UNICODE_HANGUL_TBASE + $i);
  51. $file_contents['utf_normalizer_common']['utf_jamo_index'][$utf_char] = $i;
  52. $file_contents['utf_normalizer_common']['utf_jamo_type'][$utf_char] = UNICODE_JAMO_T;
  53. }
  54. /**
  55. * Load the CompositionExclusions table
  56. */
  57. echo "Loading CompositionExclusion\n";
  58. $fp = fopen('CompositionExclusions.txt', 'rt');
  59. $exclude = array();
  60. while (!feof($fp))
  61. {
  62. $line = fgets($fp, 1024);
  63. if (!strpos(' 0123456789ABCDEFabcdef', $line[0]))
  64. {
  65. continue;
  66. }
  67. $cp = strtok($line, ' ');
  68. if ($pos = strpos($cp, '..'))
  69. {
  70. $start = hexdec(substr($cp, 0, $pos));
  71. $end = hexdec(substr($cp, $pos + 2));
  72. for ($i = $start; $i < $end; ++$i)
  73. {
  74. $exclude[$i] = 1;
  75. }
  76. }
  77. else
  78. {
  79. $exclude[hexdec($cp)] = 1;
  80. }
  81. }
  82. fclose($fp);
  83. /**
  84. * Load QuickCheck tables
  85. */
  86. echo "Generating QuickCheck tables\n";
  87. $fp = fopen('DerivedNormalizationProps.txt', 'rt');
  88. while (!feof($fp))
  89. {
  90. $line = fgets($fp, 1024);
  91. if (!strpos(' 0123456789ABCDEFabcdef', $line[0]))
  92. {
  93. continue;
  94. }
  95. $p = array_map('trim', explode(';', strtok($line, '#')));
  96. /**
  97. * Capture only NFC_QC, NFKC_QC
  98. */
  99. if (!preg_match('#^NFK?C_QC$#', $p[1]))
  100. {
  101. continue;
  102. }
  103. if ($pos = strpos($p[0], '..'))
  104. {
  105. $start = hexdec(substr($p[0], 0, $pos));
  106. $end = hexdec(substr($p[0], $pos + 2));
  107. }
  108. else
  109. {
  110. $start = $end = hexdec($p[0]);
  111. }
  112. if ($start >= UTF8_HANGUL_FIRST && $end <= UTF8_HANGUL_LAST)
  113. {
  114. /**
  115. * We do not store Hangul syllables in the array
  116. */
  117. continue;
  118. }
  119. if ($p[2] == 'M')
  120. {
  121. $val = UNICODE_QC_MAYBE;
  122. }
  123. else
  124. {
  125. $val = UNICODE_QC_NO;
  126. }
  127. if ($p[1] == 'NFKC_QC')
  128. {
  129. $file = 'utf_nfkc_qc';
  130. }
  131. else
  132. {
  133. $file = 'utf_nfc_qc';
  134. }
  135. for ($i = $start; $i <= $end; ++$i)
  136. {
  137. /**
  138. * The vars have the same name as the file: $utf_nfc_qc is in utf_nfc_qc.php
  139. */
  140. $file_contents[$file][$file][cp_to_utf($i)] = $val;
  141. }
  142. }
  143. fclose($fp);
  144. /**
  145. * Do mappings
  146. */
  147. echo "Loading Unicode decomposition mappings\n";
  148. $fp = fopen($phpbb_root_path . 'develop/UnicodeData.txt', 'rt');
  149. $map = array();
  150. while (!feof($fp))
  151. {
  152. $p = explode(';', fgets($fp, 1024));
  153. $cp = hexdec($p[0]);
  154. if (!empty($p[3]))
  155. {
  156. /**
  157. * Store combining class > 0
  158. */
  159. $file_contents['utf_normalizer_common']['utf_combining_class'][cp_to_utf($cp)] = (int) $p[3];
  160. }
  161. if (!isset($p[5]) || !preg_match_all('#[0-9A-F]+#', strip_tags($p[5]), $m))
  162. {
  163. continue;
  164. }
  165. if (strpos($p[5], '>'))
  166. {
  167. $map['NFKD'][$cp] = implode(' ', array_map('hexdec', $m[0]));
  168. }
  169. else
  170. {
  171. $map['NFD'][$cp] = $map['NFKD'][$cp] = implode(' ', array_map('hexdec', $m[0]));
  172. }
  173. }
  174. fclose($fp);
  175. /**
  176. * Build the canonical composition table
  177. */
  178. echo "Generating the Canonical Composition table\n";
  179. foreach ($map['NFD'] as $cp => $decomp_seq)
  180. {
  181. if (!strpos($decomp_seq, ' ') || isset($exclude[$cp]))
  182. {
  183. /**
  184. * Singletons are excluded from canonical composition
  185. */
  186. continue;
  187. }
  188. $utf_seq = implode('', array_map('cp_to_utf', explode(' ', $decomp_seq)));
  189. if (!isset($file_contents['utf_canonical_comp']['utf_canonical_comp'][$utf_seq]))
  190. {
  191. $file_contents['utf_canonical_comp']['utf_canonical_comp'][$utf_seq] = cp_to_utf($cp);
  192. }
  193. }
  194. /**
  195. * Decompose the NF[K]D mappings recursively and prepare the file contents
  196. */
  197. echo "Generating the Canonical and Compatibility Decomposition tables\n\n";
  198. foreach ($map as $type => $decomp_map)
  199. {
  200. foreach ($decomp_map as $cp => $decomp_seq)
  201. {
  202. $decomp_map[$cp] = decompose($decomp_map, $decomp_seq);
  203. }
  204. unset($decomp_seq);
  205. if ($type == 'NFKD')
  206. {
  207. $file = 'utf_compatibility_decomp';
  208. $var = 'utf_compatibility_decomp';
  209. }
  210. else
  211. {
  212. $file = 'utf_canonical_decomp';
  213. $var = 'utf_canonical_decomp';
  214. }
  215. /**
  216. * Generate the corresponding file
  217. */
  218. foreach ($decomp_map as $cp => $decomp_seq)
  219. {
  220. $file_contents[$file][$var][cp_to_utf($cp)] = implode('', array_map('cp_to_utf', explode(' ', $decomp_seq)));
  221. }
  222. }
  223. /**
  224. * Generate and/or alter the files
  225. */
  226. foreach ($file_contents as $file => $contents)
  227. {
  228. /**
  229. * Generate a new file
  230. */
  231. echo "Writing to $file.$phpEx\n";
  232. if (!$fp = fopen($phpbb_root_path . 'includes/utf/data/' . $file . '.' . $phpEx, 'wb'))
  233. {
  234. trigger_error('Cannot open ' . $file . ' for write');
  235. }
  236. fwrite($fp, '<?php');
  237. foreach ($contents as $var => $val)
  238. {
  239. fwrite($fp, "\n\$GLOBALS[" . my_var_export($var) . ']=' . my_var_export($val) . ";");
  240. }
  241. fclose($fp);
  242. }
  243. echo "\n*** UTF-8 normalization tables done\n\n";
  244. /**
  245. * Now we'll generate the files needed by the search indexer
  246. */
  247. echo "Generating search indexer tables\n";
  248. $fp = fopen($phpbb_root_path . 'develop/UnicodeData.txt', 'rt');
  249. $map = array();
  250. while ($line = fgets($fp, 1024))
  251. {
  252. /**
  253. * The current line is split, $m[0] hold the codepoint in hexadecimal and
  254. * all other fields numbered as in http://www.unicode.org/Public/UNIDATA/UCD.html#UnicodeData.txt
  255. */
  256. $m = explode(';', $line);
  257. /**
  258. * @var integer $cp Current char codepoint
  259. * @var string $utf_char UTF-8 representation of current char
  260. */
  261. $cp = hexdec($m[0]);
  262. $utf_char = cp_to_utf($cp);
  263. /**
  264. * $m[2] holds the "General Category" of the character
  265. * @link http://www.unicode.org/Public/UNIDATA/UCD.html#General_Category_Values
  266. */
  267. switch ($m[2][0])
  268. {
  269. case 'L':
  270. /**
  271. * We allow all letters and map them to their lowercased counterpart on the fly
  272. */
  273. $map_to_hex = (isset($m[13][0])) ? $m[13] : $m[0];
  274. if (preg_match('#^LATIN.*(?:LETTER|LIGATURE) ([A-Z]{2}(?![A-Z]))$#', $m[1], $capture))
  275. {
  276. /**
  277. * Special hack for some latin ligatures. Using the name of a character
  278. * is bad practice, but for now it works well enough.
  279. *
  280. * @todo Note that ligatures with combining marks such as U+01E2 are
  281. * not supported at this time
  282. */
  283. $map[$cp] = strtolower($capture[1]);
  284. }
  285. else if (isset($m[13][0]))
  286. {
  287. /**
  288. * If the letter has a lowercased form, use it
  289. */
  290. $map[$cp] = hex_to_utf($m[13]);
  291. }
  292. else
  293. {
  294. /**
  295. * In all other cases, map the letter to itself
  296. */
  297. $map[$cp] = $utf_char;
  298. }
  299. break;
  300. case 'M':
  301. /**
  302. * We allow all marks, they are mapped to themselves
  303. */
  304. $map[$cp] = $utf_char;
  305. break;
  306. case 'N':
  307. /**
  308. * We allow all numbers, but we map them to their numeric value whenever
  309. * possible. The numeric value (field #8) is in ASCII already
  310. *
  311. * @todo Note that fractions such as U+00BD will be converted to something
  312. * like "1/2", with a slash. However, "1/2" entered in ASCII is converted
  313. * to "1 2". This will have to be fixed.
  314. */
  315. $map[$cp] = (isset($m[8][0])) ? $m[8] : $utf_char;
  316. break;
  317. default:
  318. /**
  319. * Everything else is ignored, skip to the next line
  320. */
  321. continue 2;
  322. }
  323. }
  324. fclose($fp);
  325. /**
  326. * Add some cheating
  327. */
  328. $cheats = array(
  329. '00DF' => 'ss', # German sharp S
  330. '00C5' => 'ae', # Capital A with diaeresis
  331. '00E4' => 'ae', # Small A with diaeresis
  332. '00D6' => 'oe', # Capital O with diaeresis
  333. '00F6' => 'oe', # Small O with diaeresis
  334. '00DC' => 'ue', # Capital U with diaeresis
  335. '00FC' => 'ue', # Small U with diaeresis
  336. );
  337. /**
  338. * Add our "cheat replacements" to the map
  339. */
  340. foreach ($cheats as $hex => $map_to)
  341. {
  342. $map[hexdec($hex)] = $map_to;
  343. }
  344. /**
  345. * Split the map into smaller blocks
  346. */
  347. $file_contents = array();
  348. foreach ($map as $cp => $map_to)
  349. {
  350. $file_contents[$cp >> 11][cp_to_utf($cp)] = $map_to;
  351. }
  352. unset($map);
  353. foreach ($file_contents as $idx => $contents)
  354. {
  355. echo "Writing to search_indexer_$idx.$phpEx\n";
  356. $fp = fopen($phpbb_root_path . 'includes/utf/data/search_indexer_' . $idx . '.' . $phpEx, 'wb');
  357. fwrite($fp, '<?php return ' . my_var_export($contents) . ';');
  358. fclose($fp);
  359. }
  360. echo "\n*** Search indexer tables done\n\n";
  361. die("\nAll done!\n");
  362. ////////////////////////////////////////////////////////////////////////////////
  363. // Internal functions //
  364. ////////////////////////////////////////////////////////////////////////////////
  365. /**
  366. * Decompose a sequence recusively
  367. *
  368. * @param array $decomp_map Decomposition mapping, passed by reference
  369. * @param string $decomp_seq Decomposition sequence as decimal codepoints separated with a space
  370. * @return string Decomposition sequence, fully decomposed
  371. */
  372. function decompose(&$decomp_map, $decomp_seq)
  373. {
  374. $ret = array();
  375. foreach (explode(' ', $decomp_seq) as $cp)
  376. {
  377. if (isset($decomp_map[$cp]))
  378. {
  379. $ret[] = decompose($decomp_map, $decomp_map[$cp]);
  380. }
  381. else
  382. {
  383. $ret[] = $cp;
  384. }
  385. }
  386. return implode(' ', $ret);
  387. }
  388. /**
  389. * Return a parsable string representation of a variable
  390. *
  391. * This is function is limited to array/strings/integers
  392. *
  393. * @param mixed $var Variable
  394. * @return string PHP code representing the variable
  395. */
  396. function my_var_export($var)
  397. {
  398. if (is_array($var))
  399. {
  400. $lines = array();
  401. foreach ($var as $k => $v)
  402. {
  403. $lines[] = my_var_export($k) . '=>' . my_var_export($v);
  404. }
  405. return 'array(' . implode(',', $lines) . ')';
  406. }
  407. else if (is_string($var))
  408. {
  409. return "'" . str_replace(array('\\', "'"), array('\\\\', "\\'"), $var) . "'";
  410. }
  411. else
  412. {
  413. return $var;
  414. }
  415. }
  416. /**
  417. * Download a file to the develop/ dir
  418. *
  419. * @param string $url URL of the file to download
  420. * @return null
  421. */
  422. function download($url)
  423. {
  424. global $phpbb_root_path;
  425. if (file_exists($phpbb_root_path . 'develop/' . basename($url)))
  426. {
  427. return;
  428. }
  429. echo 'Downloading from ', $url, ' ';
  430. if (!$fpr = fopen($url, 'rb'))
  431. {
  432. die("Can't download from $url\nPlease download it yourself and put it in the develop/ dir, kthxbai");
  433. }
  434. if (!$fpw = fopen($phpbb_root_path . 'develop/' . basename($url), 'wb'))
  435. {
  436. die("Can't open develop/" . basename($url) . " for output... please check your permissions or something");
  437. }
  438. $i = 0;
  439. $chunk = 32768;
  440. $done = '';
  441. while (!feof($fpr))
  442. {
  443. $i += fwrite($fpw, fread($fpr, $chunk));
  444. echo str_repeat("\x08", strlen($done));
  445. $done = ($i >> 10) . ' KiB';
  446. echo $done;
  447. }
  448. fclose($fpr);
  449. fclose($fpw);
  450. echo "\n";
  451. }
  452. /**
  453. * Convert a codepoint in hexadecimal to a UTF-8 char
  454. *
  455. * @param string $hex Codepoint, in hexadecimal
  456. * @return string UTF-8 char
  457. */
  458. function hex_to_utf($hex)
  459. {
  460. return cp_to_utf(hexdec($hex));
  461. }
  462. /**
  463. * Return a UTF string formed from a sequence of codepoints in hexadecimal
  464. *
  465. * @param string $seq Sequence of codepoints, separated with a space
  466. * @return string UTF-8 string
  467. */
  468. function hexseq_to_utf($seq)
  469. {
  470. return implode('', array_map('hex_to_utf', explode(' ', $seq)));
  471. }
  472. /**
  473. * Convert a codepoint to a UTF-8 char
  474. *
  475. * @param integer $cp Unicode codepoint
  476. * @return string UTF-8 string
  477. */
  478. function cp_to_utf($cp)
  479. {
  480. if ($cp > 0xFFFF)
  481. {
  482. return chr(0xF0 | ($cp >> 18)) . chr(0x80 | (($cp >> 12) & 0x3F)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
  483. }
  484. else if ($cp > 0x7FF)
  485. {
  486. return chr(0xE0 | ($cp >> 12)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
  487. }
  488. else if ($cp > 0x7F)
  489. {
  490. return chr(0xC0 | ($cp >> 6)) . chr(0x80 | ($cp & 0x3F));
  491. }
  492. else
  493. {
  494. return chr($cp);
  495. }
  496. }