425 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			425 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php # -*- coding: utf-8 mode: php -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
 | |
| namespace nur;
 | |
| 
 | |
| use nur\b\IllegalAccessException;
 | |
| use nur\b\io\StringReader;
 | |
| 
 | |
| /**
 | |
|  * Class str: gestion des chaines de caractère "simples". utiliser plutôt la
 | |
|  * classe {@link txt} pour le support des chaines multi-octets.
 | |
|  */
 | |
| class str {
 | |
|   /**
 | |
|    * Retourner $s converti en chaine, ou "" si $s est faux
 | |
|    *
 | |
|    * $strict indique s'il faut utiliser les règles de PHP pour décider si une
 | |
|    * valeur est fausse. Par exemple, en PHP la chaine "0" est fausse.
 | |
|    *
 | |
|    * ainsi:
 | |
|    * - with(0) vaut "0"
 | |
|    * - with("0") vaut "0"
 | |
|    * - with(0, true) vaut ""
 | |
|    * - with("0", true) vaut ""
 | |
|    */
 | |
|   static final function with($s, bool $strict=false): string {
 | |
|     if (!is_string($s)) $s = strval($s);
 | |
|     if (!$strict) return $s;
 | |
|     elseif ($s) return $s;
 | |
|     else return "";
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * tronquer si nécessaire $s à la valeur $length.
 | |
|    * la chaine $suffix est rajoutée le cas échéant de façon que la taille
 | |
|    * totale n'excède pas $length caractères.
 | |
|    *
 | |
|    * si $ellips est true et que le troncage est nécessaire, remplacer les 3
 | |
|    * derniers caractères par "..."
 | |
|    */
 | |
|   static final function trunc($s, int $length, bool $ellips=false, ?string $suffix=null) {
 | |
|     if (base::z($s)) return $s;
 | |
|     $s = strval($s);
 | |
|     if ($suffix !== null) $length -= strlen($suffix);
 | |
|     if (strlen($s) > $length) {
 | |
|       if ($ellips && $length > 3) $s = substr($s, 0, $length - 3)."...";
 | |
|       else $s = substr($s, 0, $length);
 | |
|     }
 | |
|     if ($suffix !== null) $s .= $suffix;
 | |
|     return $s;
 | |
|   }
 | |
| 
 | |
|   /** trimmer $s */
 | |
|   static final function trim($s) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return trim(strval($s));
 | |
|   }
 | |
| 
 | |
|   /** trimmer $s à gauche */
 | |
|   static final function ltrim($s) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return ltrim(strval($s));
 | |
|   }
 | |
| 
 | |
|   /** trimmer $s à droite */
 | |
|   static final function rtrim($s) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return rtrim(strval($s));
 | |
|   }
 | |
| 
 | |
|   static final function left($s, int $size) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return str_pad($s, $size);
 | |
|   }
 | |
| 
 | |
|   static final function right($s, int $size) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return str_pad($s, $size, " ", STR_PAD_LEFT);
 | |
|   }
 | |
| 
 | |
|   static final function center($s, int $size) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return str_pad($s, $size, " ", STR_PAD_BOTH);
 | |
|   }
 | |
| 
 | |
|   static final function pad0($s, int $size) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return str_pad($s, $size, "0", STR_PAD_LEFT);
 | |
|   }
 | |
| 
 | |
|   static final function lower($s) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return strtolower(strval($s));
 | |
|   }
 | |
| 
 | |
|   static final function lower1($s) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return lcfirst(strval($s));
 | |
|   }
 | |
| 
 | |
|   static final function upper($s) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return strtoupper(strval($s));
 | |
|   }
 | |
| 
 | |
|   static final function upper1($s) {
 | |
|     if (base::z($s)) return $s;
 | |
|     else return ucfirst(strval($s));
 | |
|   }
 | |
| 
 | |
|   static final function upperw($s, ?string $delimiters=null) {
 | |
|     if (base::z($s)) return $s;
 | |
|     $s = strval($s);
 | |
|     if ($delimiters !== null) return ucwords($s, $delimiters);
 | |
|     else return ucwords($s, " _-\t\r\n\f\v");
 | |
|   }
 | |
| 
 | |
|   static final function _starts_with(string $prefix, string $s, ?int $min_len=null): bool {
 | |
|     if ($prefix === $s) return true;
 | |
|     $len = strlen($prefix);
 | |
|     if ($min_len !== null && ($len < $min_len || $len > strlen($s))) return false;
 | |
|     return $len == 0 || $prefix === substr($s, 0, $len);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * tester si $s commence par $prefix
 | |
|    * par exemple:
 | |
|    * - starts_with("", "whatever") est true
 | |
|    * - starts_with("fi", "first") est true
 | |
|    * - starts_with("no", "yes") est false
 | |
|    *
 | |
|    * si $min_len n'est pas null, c'est la longueur minimum requise de $prefix
 | |
|    * pour qu'on teste la correspondance. dans le cas contraire, la valeur de
 | |
|    * retour est toujours false, sauf s'il y a égalité. e.g
 | |
|    * - starts_with("a", "abc", 2) est false
 | |
|    * - starts_with("a", "a", 2) est true
 | |
|    */
 | |
|   static final function starts_with(?string $prefix, $s, ?int $min_len=null): bool {
 | |
|     if (base::z($s) || $prefix === null) return false;
 | |
|     else return self::_starts_with($prefix, strval($s), $min_len);
 | |
|   }
 | |
| 
 | |
|   /** Retourner $s sans le préfixe $prefix s'il existe */
 | |
|   static final function without_prefix(?string $prefix, $s) {
 | |
|     if (base::z($s) || $prefix === null) return $s;
 | |
|     $s = strval($s);
 | |
|     if (self::_starts_with($prefix, $s)) {
 | |
|       $s = substr($s, strlen($prefix));
 | |
|     }
 | |
|     return $s;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * modifier $s en place pour supprimer le préfixe $prefix s'il existe
 | |
|    *
 | |
|    * retourner true si le préfixe a été enlevé.
 | |
|    */
 | |
|   static final function del_prefix(&$s, ?string $prefix): bool {
 | |
|     if ($s === null || !self::_starts_with($prefix, $s)) return false;
 | |
|     $s = self::without_prefix($prefix, $s);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Retourner $s avec le préfixe $prefix
 | |
|    *
 | |
|    * Si $unless_exists, ne pas ajouter le préfixe s'il existe déjà
 | |
|    */
 | |
|   static final function with_prefix(?string $prefix, $s, ?string $sep=null, bool $unless_exists=false) {
 | |
|     if (base::z($s) || $prefix === null) return $s;
 | |
|     $s = strval($s);
 | |
|     if (!self::_starts_with($prefix, $s) || !$unless_exists) {
 | |
|       $s = $prefix.$sep.$s;
 | |
|     }
 | |
|     return $s;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * modifier $s en place pour ajouter le préfixe $prefix
 | |
|    *
 | |
|    * retourner true si le préfixe a été ajouté.
 | |
|    */
 | |
|   static final function add_prefix(&$s, ?string $prefix, bool $unless_exists=true): bool {
 | |
|     if (($s === null || self::_starts_with($prefix, $s)) && $unless_exists) return false;
 | |
|     $s = self::with_prefix($prefix, $s, null, $unless_exists);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   static final function _ends_with(string $suffix, string $s, ?int $min_len=null): bool {
 | |
|     if ($suffix === $s) return true;
 | |
|     $len = strlen($suffix);
 | |
|     if ($min_len !== null && ($len < $min_len || $len > strlen($s))) return false;
 | |
|     return $len == 0 || $suffix === substr($s, -$len);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * tester si $string se termine par $suffix
 | |
|    * par exemple:
 | |
|    * - ends_with("", "whatever") est true
 | |
|    * - ends_with("st", "first") est true
 | |
|    * - ends_with("no", "yes") est false
 | |
|    *
 | |
|    * si $min_len n'est pas null, c'est la longueur minimum requise de $prefix
 | |
|    * pour qu'on teste la correspondance. dans le cas contraire, la valeur de
 | |
|    * retour est toujours false, sauf s'il y a égalité. e.g
 | |
|    * - ends_with("c", "abc", 2) est false
 | |
|    * - ends_with("c", "c", 2) est true
 | |
|    */
 | |
|   static final function ends_with(?string $suffix, $s, ?int $min_len=null): bool {
 | |
|     if (base::z($s) || $suffix === null) return false;
 | |
|     else return self::_ends_with($suffix, strval($s), $min_len);
 | |
|   }
 | |
| 
 | |
|   /** Retourner $s sans le suffixe $suffix s'il existe */
 | |
|   static final function without_suffix(?string $suffix, $s) {
 | |
|     if (base::z($s) || $suffix === null) return $s;
 | |
|     $s = strval($s);
 | |
|     if (self::_ends_with($suffix, $s)) {
 | |
|       $s = substr($s, 0, -strlen($suffix));
 | |
|     }
 | |
|     return $s;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * modifier $s en place pour supprimer le suffixe $suffix s'il existe
 | |
|    *
 | |
|    * retourner true si le suffixe a été enlevé.
 | |
|    */
 | |
|   static final function del_suffix(&$s, ?string $suffix): bool {
 | |
|     if ($s === null || !self::_ends_with($suffix, $s)) return false;
 | |
|     $s = self::without_suffix($suffix, $s);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Retourner $s avec le suffixe $suffix
 | |
|    *
 | |
|    * Si $unless_exists, ne pas ajouter le suffixe s'il existe déjà
 | |
|    */
 | |
|   static final function with_suffix(?string $suffix, $s, ?string $sep=null, bool $unless_exists=false) {
 | |
|     if (base::z($s) || $suffix === null) return $s;
 | |
|     $s = strval($s);
 | |
|     if (!self::_ends_with($suffix, $s) || !$unless_exists) {
 | |
|       $s = $s.$sep.$suffix;
 | |
|     }
 | |
|     return $s;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * modifier $s en place pour ajouter le suffixe $suffix
 | |
|    *
 | |
|    * retourner true si le suffixe a été ajouté.
 | |
|    */
 | |
|   static final function add_suffix(&$s, ?string $suffix, bool $unless_exists=true): bool {
 | |
|     if (($s === null || self::_ends_with($suffix, $s)) && $unless_exists) return false;
 | |
|     $s = self::with_suffix($suffix, $s, null, $unless_exists);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /** splitter $s en deux chaines séparées par $sep */
 | |
|   static final function split_pair($s, string $sep=":"): array {
 | |
|     if (base::z($s)) return [null, null];
 | |
|     $parts = explode($sep, strval($s), 2);
 | |
|     if ($parts === false) return [null, null];
 | |
|     if (count($parts) < 2) $parts[] = null;
 | |
|     return $parts;
 | |
|   }
 | |
| 
 | |
|   /** retourner $line sans son caractère de fin de ligne */
 | |
|   static final function strip_nl(string $line): string {
 | |
|     if (substr($line, -2) == "\r\n") {
 | |
|       $line = substr($line, 0, -2);
 | |
|     } elseif (substr($line, -1) == "\n") {
 | |
|       $line = substr($line, 0, -1);
 | |
|     } elseif (substr($line, -1) == "\r") {
 | |
|       $line = substr($line, 0, -1);
 | |
|     }
 | |
|     return $line;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * normaliser le caractère de fin de ligne: tous les occurrences de [CR]LF et CR sont remplacées par LF
 | |
|    */
 | |
|   static final function norm_nl(string $s): string {
 | |
|     $s = str_replace("\r\n", "\n", $s);
 | |
|     $s = str_replace("\r", "\n", $s);
 | |
|     return $s;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * retourner un tableau dont chaque élément est une ligne de $s
 | |
|    *
 | |
|    * Dans la chaine source, les lignes peuvent être terminées par [CR]LF. Par
 | |
|    * contre, elles sont retournées sans le caractère de fin de ligne
 | |
|    */
 | |
|   static final function split_nl($s): array {
 | |
|     if (base::z($s)) return [];
 | |
|     $s = strval($s);
 | |
|     $length = strlen($s);
 | |
|     $index = 0;
 | |
|     $lines = [];
 | |
|     while ($index < $length) {
 | |
|       [$line, $index] = StringReader::next_line($s, $index, $length);
 | |
|       $lines[] = $line;
 | |
|     }
 | |
|     return $lines;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * joindre les éléments de $parts comme avec implode(), mais en ignorant les
 | |
|    * valeurs fausses (cela n'inclue pas la chaine "0")
 | |
|    */
 | |
|   static final function join(string $glue, ?array $values): string {
 | |
|     $pieces = [];
 | |
|     if ($values !== null) {
 | |
|       foreach ($values as $value) {
 | |
|         if (base::t($value)) $pieces[] = $value;
 | |
|       }
 | |
|     }
 | |
|     return implode($glue, $pieces);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * joindre les éléments de $parts comme avec implode(), mais en ignorant les
 | |
|    * valeurs fausses (selon les règles de PHP)
 | |
|    */
 | |
|   static final function join2(string $glue, ?array $values): string {
 | |
|     $pieces = [];
 | |
|     if ($values !== null) {
 | |
|       foreach ($values as $value) {
 | |
|         if ($value) $pieces[] = $value;
 | |
|       }
 | |
|     }
 | |
|     return implode($glue, $pieces);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * joindre les éléments en les séparant par des espaces, en ignorant les
 | |
|    * valeurs fausses (cela n'inclue pas la chaine "0")
 | |
|    *
 | |
|    * pour chaque valeur du tableau avec une clé associative, c'est la clé qui est
 | |
|    * utilisée mais uniquement si la valeur est vraie
 | |
|    */
 | |
|   static final function join3(?array $values, string $glue=" "): ?string {
 | |
|     if ($values === null) return null;
 | |
|     $parts = [];
 | |
|     $index = 0;
 | |
|     foreach ($values as $key => $value) {
 | |
|       if (is_array($value)) $value = self::join3($value, $glue);
 | |
|       if ($key === $index) {
 | |
|         $index++;
 | |
|         if (base::t($value)) $parts[] = $value;
 | |
|       } elseif ($value) {
 | |
|         $parts[] = $key;
 | |
|       }
 | |
|     }
 | |
|     return implode($glue, $parts);
 | |
|   }
 | |
| 
 | |
|   const CAMEL_PATTERN0 = '/([A-Z0-9]+)$/A';
 | |
|   const CAMEL_PATTERN1 = '/([A-Z0-9]+)[A-Z]/A';
 | |
|   const CAMEL_PATTERN2 = '/([^A-Z]+)/A';
 | |
|   const CAMEL_PATTERN3 = '/([A-Z][^A-Z]*)/A';
 | |
| 
 | |
|   /**
 | |
|    * convertir une chaine de la forme "camelCase" en "under_score". le premier
 | |
|    * ensemble de caractères en majuscule est considéré comme étant en minuscule
 | |
|    *
 | |
|    * par exemple:
 | |
|    * - 'myCamelCase' devient 'my_camel_case'
 | |
|    * - 'AValue' devient 'a_value'
 | |
|    * - 'UPPERValue' devient 'upper_value'
 | |
|    * - 'UPPER' devient 'upper'
 | |
|    * - 'aXYZ' devient 'a_x_y_z'
 | |
|    *
 | |
|    * $delimiter est le séparateur en sortie ('_' par défaut)
 | |
|    * $upper indique s'il faut transformer le résultat en majuscule
 | |
|    */
 | |
|   static final function camel2us($camel, bool $upper=false, string $delimiter="_") {
 | |
|     if (base::z($camel)) return $camel;
 | |
|     $camel = strval($camel);
 | |
|     if ($camel === "") return "";
 | |
|     $parts = [];
 | |
|     if (preg_match(self::CAMEL_PATTERN0, $camel, $vs, PREG_OFFSET_CAPTURE)) {
 | |
|       # que des majuscules
 | |
|     } elseif (preg_match(self::CAMEL_PATTERN1, $camel, $vs, PREG_OFFSET_CAPTURE)) {
 | |
|       # préfixe en majuscule
 | |
|     } elseif (preg_match(self::CAMEL_PATTERN2, $camel, $vs, PREG_OFFSET_CAPTURE)) {
 | |
|       # préfixe en minuscule
 | |
|     } else {
 | |
|       throw IllegalAccessException::unexpected_state();
 | |
|     }
 | |
|     $parts[] = strtolower($vs[1][0]);
 | |
|     $index = $vs[1][1] + strlen($vs[1][0]);
 | |
|     while (preg_match(self::CAMEL_PATTERN3, $camel, $vs, PREG_OFFSET_CAPTURE, $index)) {
 | |
|       $parts[] = strtolower($vs[1][0]);
 | |
|       $index = $vs[1][1] + strlen($vs[1][0]);
 | |
|     }
 | |
|     $us = implode($delimiter, $parts);
 | |
|     if ($upper) $us = strtoupper($us);
 | |
|     return $us;
 | |
|   }
 | |
| 
 | |
|   const US_PATTERN = '/([ _\-\t\r\n\f\v])/';
 | |
| 
 | |
|   /**
 | |
|    * convertir une chaine de la forme "under_score" en "camelCase"
 | |
|    *
 | |
|    * par exemple, 'my_camel_case' devient 'myCamelCalse'
 | |
|    * et 'UPPER_VALUE' devient 'upperValue'
 | |
|    */
 | |
|   static final function us2camel($us, ?string $delimiters=null) {
 | |
|     if (base::z($us)) return $us;
 | |
|     $us = strval($us);
 | |
|     if ($delimiters === null) $pattern = self::US_PATTERN;
 | |
|     else $pattern = '/(['.preg_quote($delimiters).'])/';
 | |
|     $parts = preg_split($pattern, $us);
 | |
|     $count = count($parts);
 | |
|     for ($i = 0; $i < $count; $i++) {
 | |
|       $part = strtolower($parts[$i]);
 | |
|       if ($i > 0) $part = ucfirst($part);
 | |
|       $parts[$i] = $part;
 | |
|     }
 | |
|     return implode("", $parts);
 | |
|   }
 | |
| }
 |