PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/phpBB/develop/generate_utf_tables.php

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