/src/Symfony/Component/String/Resources/WcswidthDataGenerator.php

https://github.com/stof/symfony · PHP · 113 lines · 69 code · 27 blank · 17 comment · 5 complexity · 6e2322366f4066a0216c855e39b25327 MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\String\Resources;
  11. use Symfony\Component\HttpClient\HttpClient;
  12. use Symfony\Component\String\Exception\RuntimeException;
  13. use Symfony\Component\VarExporter\VarExporter;
  14. use Symfony\Contracts\HttpClient\HttpClientInterface;
  15. /**
  16. * @internal
  17. */
  18. final class WcswidthDataGenerator
  19. {
  20. private string $outDir;
  21. private HttpClientInterface $client;
  22. public function __construct(string $outDir)
  23. {
  24. $this->outDir = $outDir;
  25. $this->client = HttpClient::createForBaseUri('https://www.unicode.org/Public/UNIDATA/');
  26. }
  27. public function generate(): void
  28. {
  29. $this->writeWideWidthData();
  30. $this->writeZeroWidthData();
  31. }
  32. private function writeWideWidthData(): void
  33. {
  34. if (!preg_match('/^# EastAsianWidth-(\d+\.\d+\.\d+)\.txt/', $content = $this->client->request('GET', 'EastAsianWidth.txt')->getContent(), $matches)) {
  35. throw new RuntimeException('The Unicode version could not be determined.');
  36. }
  37. $version = $matches[1];
  38. if (!preg_match_all('/^([A-H\d]{4,})(?:\.\.([A-H\d]{4,}))?;[W|F]/m', $content, $matches, \PREG_SET_ORDER)) {
  39. throw new RuntimeException('The wide width pattern did not match anything.');
  40. }
  41. $this->write('wcswidth_table_wide.php', $version, $matches);
  42. }
  43. private function writeZeroWidthData(): void
  44. {
  45. if (!preg_match('/^# DerivedGeneralCategory-(\d+\.\d+\.\d+)\.txt/', $content = $this->client->request('GET', 'extracted/DerivedGeneralCategory.txt')->getContent(), $matches)) {
  46. throw new RuntimeException('The Unicode version could not be determined.');
  47. }
  48. $version = $matches[1];
  49. if (!preg_match_all('/^([A-H\d]{4,})(?:\.\.([A-H\d]{4,}))? *; (?:Me|Mn)/m', $content, $matches, \PREG_SET_ORDER)) {
  50. throw new RuntimeException('The zero width pattern did not match anything.');
  51. }
  52. $this->write('wcswidth_table_zero.php', $version, $matches);
  53. }
  54. private function write(string $fileName, string $version, array $rawData): void
  55. {
  56. $content = $this->getHeader($version).'return '.VarExporter::export($this->format($rawData)).";\n";
  57. if (!file_put_contents($this->outDir.'/'.$fileName, $content)) {
  58. throw new RuntimeException(sprintf('The "%s" file could not be written.', $fileName));
  59. }
  60. }
  61. private function getHeader(string $version): string
  62. {
  63. $date = (new \DateTimeImmutable())->format('c');
  64. return <<<EOT
  65. <?php
  66. /*
  67. * This file has been auto-generated by the Symfony String Component for internal use.
  68. *
  69. * Unicode version: $version
  70. * Date: $date
  71. */
  72. EOT;
  73. }
  74. private function format(array $rawData): array
  75. {
  76. $data = array_map(static function (array $row): array {
  77. $start = $row[1];
  78. $end = $row[2] ?? $start;
  79. return [hexdec($start), hexdec($end)];
  80. }, $rawData);
  81. usort($data, static function (array $a, array $b): int {
  82. return $a[0] - $b[0];
  83. });
  84. return $data;
  85. }
  86. }