[ Index ]

PHP Cross Reference of YOURLS

title

Body

[close]

/includes/vendor/symfony/polyfill-intl-normalizer/ -> Normalizer.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of the Symfony package.
   5   *
   6   * (c) Fabien Potencier <[email protected]>
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  namespace Symfony\Polyfill\Intl\Normalizer;
  13  
  14  /**
  15   * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension.
  16   *
  17   * It has been validated with Unicode 6.3 Normalization Conformance Test.
  18   * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations.
  19   *
  20   * @author Nicolas Grekas <[email protected]>
  21   *
  22   * @internal
  23   */
  24  class Normalizer
  25  {
  26      public const FORM_D = \Normalizer::FORM_D;
  27      public const FORM_KD = \Normalizer::FORM_KD;
  28      public const FORM_C = \Normalizer::FORM_C;
  29      public const FORM_KC = \Normalizer::FORM_KC;
  30      public const NFD = \Normalizer::NFD;
  31      public const NFKD = \Normalizer::NFKD;
  32      public const NFC = \Normalizer::NFC;
  33      public const NFKC = \Normalizer::NFKC;
  34  
  35      private static $C;
  36      private static $D;
  37      private static $KD;
  38      private static $cC;
  39      private static $rawD;
  40      private static $rawKD;
  41      private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
  42      private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
  43  
  44      public static function isNormalized(string $s, int $form = self::FORM_C)
  45      {
  46          if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) {
  47              return false;
  48          }
  49          if (!isset($s[strspn($s, self::$ASCII)])) {
  50              return true;
  51          }
  52          if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) {
  53              return true;
  54          }
  55  
  56          return self::normalize($s, $form) === $s;
  57      }
  58  
  59      public static function getRawDecomposition(string $s, int $form = self::FORM_C)
  60      {
  61          if ('' === $s || !preg_match('//u', $s)) {
  62              return null;
  63          }
  64  
  65          $ulen = $s[0] < "\x80" ? 1 : (self::$ulenMask[$s[0] & "\xF0"] ?? 0);
  66          if (!$ulen || \strlen($s) !== $ulen) {
  67              return null;
  68          }
  69  
  70          if (self::NFC !== $form && self::NFD !== $form && self::NFKC !== $form && self::NFKD !== $form) {
  71              return '';
  72          }
  73  
  74          if ($s >= "\xEA\xB0\x80" && $s <= "\xED\x9E\xA3") {
  75              $u = unpack('C*', $s);
  76              $j = (($u[1] - 224) << 12) + (($u[2] - 128) << 6) + $u[3] - 0xAC80;
  77  
  78              if ($t = $j % 28) {
  79                  $j -= $t;
  80                  $lv = 0xAC00 + $j;
  81                  $r = \chr(0xE0 | $lv >> 12).\chr(0x80 | $lv >> 6 & 0x3F).\chr(0x80 | $lv & 0x3F);
  82                  $r .= $t < 25
  83                      ? ("\xE1\x86".\chr(0xA7 + $t))
  84                      : ("\xE1\x87".\chr(0x67 + $t));
  85  
  86                  return $r;
  87              }
  88  
  89              return "\xE1\x84".\chr(0x80 + (int) ($j / 588))
  90                    ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28));
  91          }
  92  
  93          if (null === self::$rawD) {
  94              self::$rawD = self::getData('rawCanonicalDecomposition');
  95          }
  96  
  97          if (isset(self::$rawD[$s])) {
  98              return self::$rawD[$s];
  99          }
 100  
 101          if (self::NFKC === $form || self::NFKD === $form) {
 102              if (null === self::$rawKD) {
 103                  self::$rawKD = self::getData('rawCompatibilityDecomposition');
 104              }
 105  
 106              return self::$rawKD[$s] ?? null;
 107          }
 108  
 109          return null;
 110      }
 111  
 112      public static function normalize(string $s, int $form = self::FORM_C)
 113      {
 114          if (!preg_match('//u', $s)) {
 115              return false;
 116          }
 117  
 118          switch ($form) {
 119              case self::NFC: $C = true; $K = false; break;
 120              case self::NFD: $C = false; $K = false; break;
 121              case self::NFKC: $C = true; $K = true; break;
 122              case self::NFKD: $C = false; $K = true; break;
 123              default:
 124                  if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) {
 125                      return $s;
 126                  }
 127  
 128                  if (80000 > \PHP_VERSION_ID) {
 129                      return false;
 130                  }
 131  
 132                  throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form');
 133          }
 134  
 135          if ('' === $s) {
 136              return '';
 137          }
 138  
 139          if ($K && null === self::$KD) {
 140              self::$KD = self::getData('compatibilityDecomposition');
 141          }
 142  
 143          if (null === self::$D) {
 144              self::$D = self::getData('canonicalDecomposition');
 145              self::$cC = self::getData('combiningClass');
 146          }
 147  
 148          if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) {
 149              mb_internal_encoding('8bit');
 150          }
 151  
 152          $r = self::decompose($s, $K);
 153  
 154          if ($C) {
 155              if (null === self::$C) {
 156                  self::$C = self::getData('canonicalComposition');
 157              }
 158  
 159              $r = self::recompose($r);
 160          }
 161          if (null !== $mbEncoding) {
 162              mb_internal_encoding($mbEncoding);
 163          }
 164  
 165          return $r;
 166      }
 167  
 168      private static function recompose($s)
 169      {
 170          $ASCII = self::$ASCII;
 171          $compMap = self::$C;
 172          $combClass = self::$cC;
 173          $ulenMask = self::$ulenMask;
 174  
 175          $result = $tail = '';
 176  
 177          $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"];
 178          $len = \strlen($s);
 179  
 180          $lastUchr = substr($s, 0, $i);
 181          $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0;
 182  
 183          while ($i < $len) {
 184              if ($s[$i] < "\x80") {
 185                  // ASCII chars
 186  
 187                  if ($tail) {
 188                      $lastUchr .= $tail;
 189                      $tail = '';
 190                  }
 191  
 192                  if ($j = strspn($s, $ASCII, $i + 1)) {
 193                      $lastUchr .= substr($s, $i, $j);
 194                      $i += $j;
 195                  }
 196  
 197                  $result .= $lastUchr;
 198                  $lastUchr = $s[$i];
 199                  $lastUcls = 0;
 200                  ++$i;
 201                  continue;
 202              }
 203  
 204              $ulen = $ulenMask[$s[$i] & "\xF0"];
 205              $uchr = substr($s, $i, $ulen);
 206  
 207              if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr
 208                  || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr
 209                  || $lastUcls) {
 210                  // Table lookup and combining chars composition
 211  
 212                  $ucls = $combClass[$uchr] ?? 0;
 213  
 214                  if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) {
 215                      $lastUchr = $compMap[$lastUchr.$uchr];
 216                  } elseif ($lastUcls = $ucls) {
 217                      $tail .= $uchr;
 218                  } else {
 219                      if ($tail) {
 220                          $lastUchr .= $tail;
 221                          $tail = '';
 222                      }
 223  
 224                      $result .= $lastUchr;
 225                      $lastUchr = $uchr;
 226                  }
 227              } else {
 228                  // Hangul chars
 229  
 230                  $L = \ord($lastUchr[2]) - 0x80;
 231                  $V = \ord($uchr[2]) - 0xA1;
 232                  $T = 0;
 233  
 234                  $uchr = substr($s, $i + $ulen, 3);
 235  
 236                  if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") {
 237                      $T = \ord($uchr[2]) - 0xA7;
 238                      0 > $T && $T += 0x40;
 239                      $ulen += 3;
 240                  }
 241  
 242                  $L = 0xAC00 + ($L * 21 + $V) * 28 + $T;
 243                  $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F);
 244              }
 245  
 246              $i += $ulen;
 247          }
 248  
 249          return $result.$lastUchr.$tail;
 250      }
 251  
 252      private static function decompose($s, $c)
 253      {
 254          $result = '';
 255  
 256          $ASCII = self::$ASCII;
 257          $decompMap = self::$D;
 258          $combClass = self::$cC;
 259          $ulenMask = self::$ulenMask;
 260          if ($c) {
 261              $compatMap = self::$KD;
 262          }
 263  
 264          $c = [];
 265          $i = 0;
 266          $len = \strlen($s);
 267  
 268          while ($i < $len) {
 269              if ($s[$i] < "\x80") {
 270                  // ASCII chars
 271  
 272                  if ($c) {
 273                      ksort($c);
 274                      $result .= implode('', $c);
 275                      $c = [];
 276                  }
 277  
 278                  $j = 1 + strspn($s, $ASCII, $i + 1);
 279                  $result .= substr($s, $i, $j);
 280                  $i += $j;
 281                  continue;
 282              }
 283  
 284              $ulen = $ulenMask[$s[$i] & "\xF0"];
 285              $uchr = substr($s, $i, $ulen);
 286              $i += $ulen;
 287  
 288              if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) {
 289                  // Table lookup
 290  
 291                  if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) {
 292                      $uchr = $j;
 293  
 294                      $j = \strlen($uchr);
 295                      $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"];
 296  
 297                      if ($ulen != $j) {
 298                          // Put trailing chars in $s
 299  
 300                          $j -= $ulen;
 301                          $i -= $j;
 302  
 303                          if (0 > $i) {
 304                              $s = str_repeat(' ', -$i).$s;
 305                              $len -= $i;
 306                              $i = 0;
 307                          }
 308  
 309                          while ($j--) {
 310                              $s[$i + $j] = $uchr[$ulen + $j];
 311                          }
 312  
 313                          $uchr = substr($uchr, 0, $ulen);
 314                      }
 315                  }
 316                  if (isset($combClass[$uchr])) {
 317                      // Combining chars, for sorting
 318  
 319                      if (!isset($c[$combClass[$uchr]])) {
 320                          $c[$combClass[$uchr]] = '';
 321                      }
 322                      $c[$combClass[$uchr]] .= $uchr;
 323                      continue;
 324                  }
 325              } else {
 326                  // Hangul chars
 327  
 328                  $uchr = unpack('C*', $uchr);
 329                  $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80;
 330  
 331                  $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588))
 332                         ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28));
 333  
 334                  if ($j %= 28) {
 335                      $uchr .= $j < 25
 336                          ? ("\xE1\x86".\chr(0xA7 + $j))
 337                          : ("\xE1\x87".\chr(0x67 + $j));
 338                  }
 339              }
 340              if ($c) {
 341                  ksort($c);
 342                  $result .= implode('', $c);
 343                  $c = [];
 344              }
 345  
 346              $result .= $uchr;
 347          }
 348  
 349          if ($c) {
 350              ksort($c);
 351              $result .= implode('', $c);
 352          }
 353  
 354          return $result;
 355      }
 356  
 357      private static function getData($file)
 358      {
 359          if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
 360              return require $file;
 361          }
 362  
 363          return false;
 364      }
 365  }


Generated: Sun Jul 12 05:10:03 2026 Cross-referenced by PHPXref 0.7.1