Removed AString and IString and turned WString into a utility class like XString for multibyte strings.

This commit is contained in:
Pachira 2024-01-04 02:07:43 +00:00
parent e31781c69f
commit 18397477d6
14 changed files with 285 additions and 1377 deletions

View file

@ -1 +1 @@
0.2311.201900
0.2401.40206

108
composer.lock generated
View file

@ -68,16 +68,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.17.1",
"version": "v4.18.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"shasum": ""
},
"require": {
@ -118,9 +118,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0"
},
"time": "2023-08-13T19:53:39+00:00"
"time": "2023-12-10T21:03:43+00:00"
},
{
"name": "phar-io/manifest",
@ -235,16 +235,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.41",
"version": "1.10.50",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "c6174523c2a69231df55bdc65b61655e72876d76"
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6174523c2a69231df55bdc65b61655e72876d76",
"reference": "c6174523c2a69231df55bdc65b61655e72876d76",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4",
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4",
"shasum": ""
},
"require": {
@ -293,27 +293,27 @@
"type": "tidelift"
}
],
"time": "2023-11-05T12:57:57+00:00"
"time": "2023-12-13T10:59:42+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.7",
"version": "10.1.11",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "355324ca4980b8916c18b9db29f3ef484078f26e"
"reference": "78c3b7625965c2513ee96569a4dbb62601784145"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/355324ca4980b8916c18b9db29f3ef484078f26e",
"reference": "355324ca4980b8916c18b9db29f3ef484078f26e",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145",
"reference": "78c3b7625965c2513ee96569a4dbb62601784145",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.15",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1",
"phpunit/php-file-iterator": "^4.0",
"phpunit/php-text-template": "^3.0",
@ -363,7 +363,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.7"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11"
},
"funding": [
{
@ -371,7 +371,7 @@
"type": "github"
}
],
"time": "2023-10-04T15:34:17+00:00"
"time": "2023-12-21T15:38:30+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -618,16 +618,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.4.2",
"version": "10.5.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "cacd8b9dd224efa8eb28beb69004126c7ca1a1a1"
"reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/cacd8b9dd224efa8eb28beb69004126c7ca1a1a1",
"reference": "cacd8b9dd224efa8eb28beb69004126c7ca1a1a1",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ed21115d505b4b4f7dc7b5651464e19a2c7f7856",
"reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856",
"shasum": ""
},
"require": {
@ -667,7 +667,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "10.4-dev"
"dev-main": "10.5-dev"
}
},
"autoload": {
@ -699,7 +699,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.4.2"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.5"
},
"funding": [
{
@ -715,7 +715,7 @@
"type": "tidelift"
}
],
"time": "2023-10-26T07:21:45+00:00"
"time": "2023-12-27T15:13:52+00:00"
},
{
"name": "sebastian/cli-parser",
@ -963,20 +963,20 @@
},
{
"name": "sebastian/complexity",
"version": "3.1.0",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "68cfb347a44871f01e33ab0ef8215966432f6957"
"reference": "68ff824baeae169ec9f2137158ee529584553799"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68cfb347a44871f01e33ab0ef8215966432f6957",
"reference": "68cfb347a44871f01e33ab0ef8215966432f6957",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
"reference": "68ff824baeae169ec9f2137158ee529584553799",
"shasum": ""
},
"require": {
"nikic/php-parser": "^4.10",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
@ -985,7 +985,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.1-dev"
"dev-main": "3.2-dev"
}
},
"autoload": {
@ -1009,7 +1009,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
"security": "https://github.com/sebastianbergmann/complexity/security/policy",
"source": "https://github.com/sebastianbergmann/complexity/tree/3.1.0"
"source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
},
"funding": [
{
@ -1017,20 +1017,20 @@
"type": "github"
}
],
"time": "2023-09-28T11:50:59+00:00"
"time": "2023-12-21T08:37:17+00:00"
},
{
"name": "sebastian/diff",
"version": "5.0.3",
"version": "5.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b"
"reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
"reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
"reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
"shasum": ""
},
"require": {
@ -1043,7 +1043,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.0-dev"
"dev-main": "5.1-dev"
}
},
"autoload": {
@ -1076,7 +1076,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy",
"source": "https://github.com/sebastianbergmann/diff/tree/5.0.3"
"source": "https://github.com/sebastianbergmann/diff/tree/5.1.0"
},
"funding": [
{
@ -1084,7 +1084,7 @@
"type": "github"
}
],
"time": "2023-05-01T07:48:21+00:00"
"time": "2023-12-22T10:55:06+00:00"
},
{
"name": "sebastian/environment",
@ -1292,20 +1292,20 @@
},
{
"name": "sebastian/lines-of-code",
"version": "2.0.1",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d"
"reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/649e40d279e243d985aa8fb6e74dd5bb28dc185d",
"reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
"reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
"shasum": ""
},
"require": {
"nikic/php-parser": "^4.10",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
@ -1338,7 +1338,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.1"
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
},
"funding": [
{
@ -1346,7 +1346,7 @@
"type": "github"
}
],
"time": "2023-08-31T09:25:50+00:00"
"time": "2023-12-21T08:38:20+00:00"
},
{
"name": "sebastian/object-enumerator",
@ -1634,16 +1634,16 @@
},
{
"name": "theseer/tokenizer",
"version": "1.2.1",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"shasum": ""
},
"require": {
@ -1672,7 +1672,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.2.1"
"source": "https://github.com/theseer/tokenizer/tree/1.2.2"
},
"funding": [
{
@ -1680,7 +1680,7 @@
"type": "github"
}
],
"time": "2021-07-28T10:34:58+00:00"
"time": "2023-11-20T00:12:19+00:00"
}
],
"aliases": [],
@ -1693,5 +1693,5 @@
"ext-mbstring": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

View file

@ -1,278 +0,0 @@
<?php
// AString.php
// Created: 2021-04-26
// Updated: 2023-11-09
namespace Index;
use Traversable;
use InvalidArgumentException;
/**
* Provides an immutable ASCII string with arrow methods.
*
* @deprecated IString, AString and WString are deprecated and will be removed. Use XString methods instead.
*/
final class AString implements IString {
use XStringTrait;
private string $value;
/**
* Create an AString instance.
*
* @param string $value PHP string to inherit.
* @return AString New instance of AString.
*/
public function __construct(string $value) {
$this->value = $value;
}
public function getLength(): int {
return strlen($this->value);
}
public function isEmpty(): bool {
return $this->value === '';
}
public function __toString(): string {
return $this->value;
}
/**
* Checks if an offset exists in the string.
*
* You should call isset($string[$offset]) instead of $string->offsetExists($offset).
*
* @see https://www.php.net/manual/en/arrayaccess.offsetexists.php
* @param int $offset Character offset.
* @return bool true if it exists, false if not.
*/
public function offsetExists(mixed $offset): bool {
return isset($this->value[$offset]);
}
/**
* Gets an offset from the string.
*
* You should do $string[$offset] instead of $string->offsetGet($offset).
*
* @see https://www.php.net/manual/en/arrayaccess.offsetget.php
* @param int $offset Character offset.
* @return string Character at that offset.
*/
public function offsetGet(mixed $offset): mixed {
return $this->value[$offset];
}
/**
* Gets an iterator object for this string.
*
* @return StringIterator An iterator for this string.
*/
public function getIterator(): Traversable {
return new StringIterator($this);
}
/**
* Returns the data which should be serialized as json.
*
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed Data to be passed to json_encode.
*/
public function jsonSerialize(): mixed {
return $this->value;
}
public function bencodeSerialise(): mixed {
return $this->value;
}
/**
* Gets a serialized representation of this object.
*
* @return array Serialized data.
*/
public function __serialize(): array {
return [$this->value];
}
/**
* Reconstructs an object from a serialized string.
*
* @param array $serialized Serialized data.
*/
public function __unserialize(array $serialized): void {
$this->value = $serialized[0];
}
/**
* Checks whether this string is identical to another.
*
* @param mixed $other An instance of AString or a PHP string.
* @return bool true if the strings have the same value, false if not.
*/
public function equals(mixed $other): bool {
return $this->compare($other) === 0;
}
/**
* Compares whether this string is identical to another.
*
* @param mixed $other An instance of IString or a PHP string.
*/
public function compare(mixed $other): int {
return strcmp($this->value, (string)$other);
}
/**
* Creates a new identical AString instance.
*
* This method is somewhat pointless, given the immutable nature of this object,
* but rather people calling this instead of calling ->substring(0);
*
* @return AString A new identical instance of AString.
*/
public function clone(): mixed {
return new AString($this->value);
}
public function indexOf(IString|string $text, int $offset = 0): int {
$pos = strpos($this->value, (string)$text, $offset);
if($pos === false)
return -1;
return $pos;
}
public function contains(IString|string $text): bool {
return str_contains($this->value, (string)$text);
}
public function substring(int $offset, int|null $length = null): IString {
return new AString(substr($this->value, $offset, $length));
}
public function replace(IString|string $search, IString|string $replace): IString {
return new AString(str_replace((string)$search, (string)$replace, $this->value));
}
public function append(IString|string $string): IString {
return new AString($this->value . (string)$string);
}
public function prepend(IString|string $string): IString {
return new AString(((string)$string) . $this->value);
}
public function split(IString|string $separator, int $limit = PHP_INT_MAX): array {
$separator = (string)$separator;
if(empty($separator))
throw new InvalidArgumentException('$separator may not be empty.');
return XArray::select(
explode($separator, $this->value, $limit),
fn($str) => new AString($str)
);
}
public function chunk(int $chunkSize): array {
return XArray::select(
str_split($this->value, $chunkSize),
fn($str) => new AString($str)
);
}
public function trim(IString|string $characters = IString::TRIM_CHARS): IString {
return new AString(trim($this->value, (string)$characters));
}
public function trimStart(IString|string $characters = IString::TRIM_CHARS): IString {
return new AString(ltrim($this->value, (string)$characters));
}
public function trimEnd(IString|string $characters = IString::TRIM_CHARS): IString {
return new AString(rtrim($this->value, (string)$characters));
}
public function toLower(): IString {
return new AString(strtolower($this->value));
}
public function toUpper(): IString {
return new AString(strtoupper($this->value));
}
public function reverse(): IString {
return new AString(strrev($this->value));
}
public function countUnique(): int {
return XString::countUnique($this->value);
}
public function startsWith(IString|string $text): bool {
return str_starts_with($this->value, (string)$text);
}
public function endsWith(IString|string $text): bool {
return str_ends_with($this->value, (string)$text);
}
/**
* Casts this AString to a WString.
*
* @param ?string $encoding Intended encoding, null for the Index-level default.
* @param bool $convert true to convert the string to the target encoding, false to leave the bytes as-is.
* @return WString A WString of the provided encoding with the value of this AString.
*/
public function toWString(?string $encoding = null, bool $convert = true): WString {
$value = $this->value;
$encoding ??= WString::getDefaultEncoding();
if($convert)
$value = mb_convert_encoding($value, $encoding, 'ascii');
return new WString($value, $encoding);
}
/**
* Joins an iterable object together with a separator to create a string.
*
* @param iterable $source Source object.
* @param IString|string $separator Separator to use as glue.
* @return AString Resulting string.
*/
public static function join(iterable $source, IString|string $separator = ''): AString {
if(!is_array($source)) {
$parts = [];
foreach($source as $value)
$parts[] = $value;
$source = $parts;
}
return new AString(implode((string)$separator, $source));
}
/**
* Returns a reusable empty string instance.
*
* @return AString An empty string.
*/
public static function empty(): AString {
static $empty = null;
$empty ??= new AString('');
return $empty;
}
/**
* Converts a value to AString.
*
* @param mixed $value Source value.
* @return AString An AString representing the given value.
*/
public static function cast(mixed $value): AString {
if($value instanceof AString)
return $value;
if($value instanceof WString)
return $value->toAString();
return new AString((string)$value);
}
}

View file

@ -1,7 +1,7 @@
<?php
// DateTime.php
// Created: 2021-06-11
// Updated: 2023-01-01
// Updated: 2024-01-04
namespace Index;
@ -344,15 +344,6 @@ class DateTime extends DateTimeImmutable implements JsonSerializable, Stringable
return $this->add(TimeSpan::fromMicroseconds($micros));
}
/**
* Alias for subtract, must exist because of the DateTimeImmutable inheritance but I don't like short names.
*
* @internal
*/
public function sub(DateInterval $interval): DateTime {
return $this->subtract($interval);
}
/**
* Subtracts a time period from this DateTime.
*

View file

@ -1,18 +0,0 @@
<?php
// ICloneable.php
// Created: 2021-04-26
// Updated: 2021-05-12
namespace Index;
/**
* Provides an interface for creating a clone of an object in a controlled manner.
*/
interface ICloneable {
/**
* Create a new object that is a copy of the current instance.
*
* @return mixed A copy of the current instance.
*/
function clone(): mixed;
}

View file

@ -1,198 +0,0 @@
<?php
// IString.php
// Created: 2021-06-20
// Updated: 2023-11-09
namespace Index;
use ArrayAccess;
use Countable;
use IteratorAggregate;
use JsonSerializable;
use Stringable;
use Index\Serialisation\IBencodeSerialisable;
/**
* @deprecated IString, AString and WString are deprecated and will be removed. Use XString methods instead.
*/
interface IString extends ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Stringable, IComparable, ICloneable, IEquatable, IBencodeSerialisable {
/**
* Default trim characters.
*
* @var string
*/
const TRIM_CHARS = " \n\r\t\v\0";
/**
* Gets the length of the string.
*
* @return int Length of the string.
*/
function getLength(): int;
/**
* Returns whether the string is empty.
*
* @return bool true if the string is empty, false if not.
*/
function isEmpty(): bool;
/**
* Returns the index of a substring.
*
* @see https://www.php.net/manual/en/function.strpos
* @param IString|string $text Substring to look for.
* @param int $offset Offset from which to start looking.
* @return int Offset at which the substring can be found, -1 if the substring can't be found.
*/
function indexOf(IString|string $text, int $offset = 0): int;
/**
* Checks whether the string contains a substring.
*
* @see https://www.php.net/manual/en/function.str-contains
* @param IString|string $text Substring to look for.
* @return bool true if it could be found, false if not.
*/
function contains(IString|string $text): bool;
/**
* Returns a ranged substring.
*
* @see https://www.php.net/manual/en/function.substr
* @param int $offset Offset from which to start.
* @param int|null $length Amount of character to copy, null for the remainder of the string.
* @return IString An instance of IString containing the substring.
*/
function substring(int $offset, int|null $length = null): IString;
/**
* Checks if a string starts with a given substring.
*
* @see https://www.php.net/manual/en/function.str-starts-with
* @param IString|string $text The substring to check for.
* @return bool true if the substring was found, false if not.
*/
function startsWith(IString|string $text): bool;
/**
* Checks if a string ends with a given substring.
*
* @see https://www.php.net/manual/en/function.str-ends-with
* @param IString|string $text The substring to check for.
* @return bool true if the substring was found, false if not.
*/
function endsWith(IString|string $text): bool;
/**
* Replace a substring with another substring.
*
* @see https://www.php.net/manual/en/function.str-replace
* @param IString|string $search Substring that should be replaced.
* @param IString|string $replace Replacement values for what was found with search.
* @return IString A new IString instance with the replaced values.
*/
function replace(IString|string $search, IString|string $replace): IString;
/**
* Concatenates two strings with this one in the first position.
*
* @param IString|string $string String that should be appended.
* @return IString A new IString instance with the combined value.
*/
function append(IString|string $string): IString;
/**
* Concatenates two strings with this one in the last position.
*
* @param IString|string $string String that should be prepended.
* @return IString A new IString instance with the combined value.
*/
function prepend(IString|string $string): IString;
/**
* Split a string by a separator string into an array.
*
* Calling split with an empty string will not work, use ->chunk(1) instead.
*
* @see https://www.php.net/manual/en/function.explode
* @param IString|string $separator The boundary string.
* @param int $limit Maximum number of elements expected in the array.
* @return array The resulting array of substrings.
*/
function split(IString|string $separator, int $limit = PHP_INT_MAX): array;
/**
* Splits a string in substring chunks of a given length.
*
* @see https://www.php.net/manual/en/function.str-split.php
* @param int $chunkSize Maximum length of a chunk.
* @return array The resulting array of substrings.
*/
function chunk(int $chunkSize): array;
/**
* Strip whitespace or other characters from the beginning and end of a string.
*
* @see https://www.php.net/manual/en/function.trim
* @param IString|string $characters Characters to strip.
* @return IString A new instance of IString with the characters removed.
*/
function trim(IString|string $characters = self::TRIM_CHARS): IString;
/**
* Strip whitespace or other characters from the beginning of a string.
*
* @see https://www.php.net/manual/en/function.ltrim.php
* @param IString|string $characters Characters to strip.
* @return IString A new instance of IString with the characters removed.
*/
function trimStart(IString|string $characters = self::TRIM_CHARS): IString;
/**
* Strip whitespace or other characters from the end of a string.
*
* @see https://www.php.net/manual/en/function.rtrim.php
* @param IString|string $characters Characters to strip.
* @return IString A new instance of IString with the characters removed.
*/
function trimEnd(IString|string $characters = self::TRIM_CHARS): IString;
/**
* Convert string to lowercase.
*
* @see https://www.php.net/manual/en/function.strtolower.php
* @return IString A new IString instance with all lowercase characters.
*/
function toLower(): IString;
/**
* Convert string to uppercase.
*
* @see https://www.php.net/manual/en/function.strtoupper.php
* @return IString A new IString instance with all uppercase characters.
*/
function toUpper(): IString;
/**
* Reverses a string.
*
* @see https://www.php.net/manual/en/function.strrev
* @return IString A new IString instance containing the reversed string.
*/
function reverse(): IString;
/**
* Counts unique characters in a string.
*
* @return int Unique character count.
*/
function countUnique(): int;
/**
* Returns a copy of the standard PHP string.
*
* @return string PHP string.
*/
function __toString(): string;
}

View file

@ -1,83 +0,0 @@
<?php
// StringIterator.php
// Created: 2022-02-02
// Updated: 2023-11-09
namespace Index;
use Iterator;
/**
* Provides an iterator for IString types.
*
* @deprecated Will be removed along with IString, AString and WString.
*/
class StringIterator implements Iterator {
private IString $value;
private int $length;
private int $index = 0;
private bool $wasValid = false;
/**
* Creates the iterator.
*
* @param IString $string String to iterate.
*/
public function __construct(IString $string) {
$this->value = $string;
$this->length = $string->getLength();
}
/**
* Returns the current character.
*
* @see https://www.php.net/manual/en/iterator.current.php
* @return mixed Current character.
*/
public function current(): mixed {
return $this->value[$this->index];
}
/**
* Returns the index of the current character.
*
* @see https://www.php.net/manual/en/iterator.key.php
* @return int Index of the current character.
*/
public function key(): mixed {
return $this->index;
}
/**
* Move forward to the next character.
*
* @see https://www.php.net/manual/en/iterator.next.php
*/
public function next(): void {
$next = $this->index + 1;
$this->wasValid = $next < $this->length;
if($this->wasValid)
$this->index = $next;
}
/**
* Rewind to the first character.
*
* @see https://www.php.net/manual/en/iterator.rewind.php
*/
public function rewind(): void {
$this->index = 0;
$this->wasValid = true;
}
/**
* Checks if the current index is valid.
*
* @see https://www.php.net/manual/en/iterator.rewind.php
* @return bool Whether the current index is valid.
*/
public function valid(): bool {
return $this->wasValid;
}
}

View file

@ -1,394 +1,175 @@
<?php
// WString.php
// Created: 2021-06-22
// Updated: 2023-11-09
// Updated: 2024-01-04
namespace Index;
use Traversable;
use InvalidArgumentException;
use ValueError;
use Stringable;
/**
* Provides an immutable multi-byte string with arrow methods.
*
* @deprecated IString, AString and WString are deprecated and will be removed. Use XString methods instead.
* Provides various helper methods for multibyte strings.
*/
final class WString implements IString {
use XStringTrait;
final class WString {
/**
* Default characters for ::trim, ::trimStart and ::trimEnd.
*
* @var string
*/
public const TRIM_CHARS = "\0\t\n\v\f\r \u{85}\u{a0}\u{1680}\u{180e}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200a}\u{200b}\u{200c}\u{200d}\u{2028}\u{2029}\u{202f}\u{205f}\u{2060}\u{3000}\u{feff}";
private const TRIM_CHARS_CHARSET = 'UTF-8';
public const DEFAULT_ENCODING = 'utf-8';
private const TRIM_START = 0x01;
private const TRIM_END = 0x02;
private const TRIM_ALL = self::TRIM_START | self::TRIM_END;
private static $defaultEncoding = self::DEFAULT_ENCODING;
private string $value;
private string $encoding;
public function __construct(string $value = '', ?string $encoding = null) {
$encoding ??= self::$defaultEncoding;
if(!mb_check_encoding($value, $encoding))
throw new InvalidArgumentException('$value is not a valid value for the selected encoding.');
$this->value = $value;
$this->encoding = mb_preferred_mime_name($encoding);
}
public function getLength(): int {
return mb_strlen($this->value, $this->encoding);
/**
* Checks if a multibyte string starts with a given substring.
*
* @param Stringable|string $haystack String to search in.
* @param Stringable|string $needle Sustring to search for in the haystack.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return bool true if haystack begins with needle, false otherwise.
*/
public static function startsWith(Stringable|string $haystack, Stringable|string $needle, ?string $encoding = null): bool {
return mb_strpos((string)$haystack, (string)$needle, encoding: $encoding) === 0;
}
/**
* Returns the amount of bytes the string contains.
* Checks if a multibyte string ends with a given substring.
*
* @return int Amount of raw bytes.
* @param Stringable|string $haystack String to search in.
* @param Stringable|string $needle Sustring to search for in the haystack.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return bool true if haystack ends with needle, false otherwise.
*/
public function getByteCount(): int {
return strlen($this->value);
public static function endsWith(Stringable|string $haystack, Stringable|string $needle, ?string $encoding = null): bool {
$haystack = (string)$haystack;
$haystackLength = mb_strlen($haystack, $encoding);
$needle = (string)$needle;
$needleLength = mb_strlen($needle, $encoding);
return mb_substr($haystack, -$needleLength, encoding: $encoding) === $needle;
}
public function isEmpty(): bool {
// an empty string is an empty string so this should be fine regardless of encoding
return $this->value === '';
}
private static function trimInternal(Stringable|string $string, string $chars, ?string $encoding, ?string $charsEncoding, int $flags): string {
$encoding = $encoding === null ? mb_internal_encoding() : mb_preferred_mime_name($encoding);
$charsEncoding = $charsEncoding === null ? self::TRIM_CHARS_CHARSET : mb_preferred_mime_name($charsEncoding);
public function indexOf(IString|string $text, int $offset = 0): int {
$text = (string)self::castEncoding($text, $this->encoding);
$pos = mb_strpos($this->value, $text, $offset, $this->encoding);
if($pos === false)
return -1;
return $pos;
}
// this fucks, i hate character sets
if($encoding !== $charsEncoding) {
$questionMarkCharsEnc = mb_convert_encoding('?', $charsEncoding, 'utf-8');
$questionMarkStrEnc = mb_convert_encoding('?', $encoding, 'utf-8');
$hasQuestionMark = mb_strpos($chars, $questionMarkCharsEnc, encoding: $charsEncoding) !== false;
$chars = mb_convert_encoding($chars, $encoding, $charsEncoding);
public function contains(IString|string $text): bool {
return str_contains($this->value, (string)$text);
}
if(!$hasQuestionMark) {
$charsSplit = mb_str_split($chars, encoding: $encoding);
$chars = [];
foreach($charsSplit as $char) {
if(in_array($char, $chars))
continue;
$chars[] = $char;
}
public function substring(int $offset, int|null $length = null): IString {
return new WString(mb_substr($this->value, $offset, $length, $this->encoding), $this->encoding);
}
$chars = implode($chars);
}
}
public function startsWith(IString|string $text): bool {
$text = self::castEncoding($text, $this->encoding);
return $this->substring(0, $text->getLength())->equals($text);
}
public function endsWith(IString|string $text): bool {
$text = self::castEncoding($text, $this->encoding);
return $this->substring(0 - $text->getLength())->equals($text);
}
public function replace(IString|string $search, IString|string $replace): IString {
$search = (string)self::castEncoding($search, $this->encoding);
$replace = (string)self::castEncoding($replace, $this->encoding);
$parts = self::doRegex(fn() => mb_split(preg_quote($search), $this->value));
$subject = implode($replace, $parts);
return new WString($subject, $this->encoding);
}
public function append(IString|string $string): IString {
$string = self::castEncoding($string, $this->encoding);
return new WString($this->value . $string->value, $this->encoding);
}
public function prepend(IString|string $string): IString {
$string = self::castEncoding($string, $this->encoding);
return new WString($string->value . $this->value, $this->encoding);
}
public function split(IString|string $separator, int $limit = PHP_INT_MAX): array {
return XArray::select(
self::doRegex(fn() => mb_split(preg_quote((string)$separator), $this->value, $limit)),
fn($str) => new WString($str, $this->encoding)
);
}
public function chunk(int $chunkSize): array {
return XArray::select(
mb_str_split($this->value, $chunkSize, $this->encoding),
fn($str) => new WString($str, $this->encoding)
);
}
private function trimInternal(IString|string $characters, int $flags): IString {
if($flags & 0x01) {
if($characters instanceof WString)
$characters = (string)$characters->convertEncoding($this->encoding);
else
$characters = mb_convert_encoding((string)$characters, $this->encoding, 'ascii');
} else
$characters = (string)$characters;
$string = (string)$string;
$split = mb_str_split($string, encoding: $encoding);
$length = mb_strlen($string, $encoding);
$start = 0;
$end = $this->getLength() - 1;
$end = $length - 1;
if($flags & 0x02)
for(; $start < $this->getLength(); ++$start)
if(mb_strpos($characters, $this[$start], 0, $this->encoding) === false)
if($flags & self::TRIM_START)
for(; $start < $length; ++$start)
if(mb_strpos($chars, $split[$start], encoding: $encoding) === false)
break;
if($flags & 0x04)
if($flags & self::TRIM_END)
for(; $end > 0; --$end)
if(mb_strpos($characters, $this[$end], 0, $this->encoding) === false)
if(mb_strpos($chars, $split[$end], encoding: $encoding) === false)
break;
return $this->substring($start, $end - $start + 1);
return mb_substr($string, $start, $end - $start + 1, $encoding);
}
public function trim(IString|string $characters = IString::TRIM_CHARS, bool $convertChars = true): IString {
$flags = 0x06;
if($convertChars) $flags |= 0x01;
return $this->trimInternal($characters, $flags);
/**
* Strip whitespace (or other characters) from the start and end of a multibyte string.
*
* @param Stringable|string $string Input string.
* @param string $chars Characters to strip. List all characters you want. .. operator from trim is not supported.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return string Trimmed string.
*/
public static function trim(Stringable|string $string, string $chars = self::TRIM_CHARS, ?string $encoding = null, ?string $charsEncoding = null): string {
return self::trimInternal($string, $chars, $encoding, $charsEncoding, self::TRIM_ALL);
}
public function trimStart(IString|string $characters = IString::TRIM_CHARS, bool $convertChars = true): IString {
$flags = 0x02;
if($convertChars) $flags |= 0x01;
return $this->trimInternal($characters, $flags);
/**
* Strip whitespace (or other characters) from the start of a multibyte string.
*
* @param Stringable|string $string Input string.
* @param string $chars Characters to strip. List all characters you want. .. operator from ltrim is not supported.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return string Trimmed string.
*/
public static function trimStart(Stringable|string $string, string $chars = self::TRIM_CHARS, ?string $encoding = null, ?string $charsEncoding = null): string {
return self::trimInternal($string, $chars, $encoding, $charsEncoding, self::TRIM_START);
}
public function trimEnd(IString|string $characters = IString::TRIM_CHARS, bool $convertChars = true): IString {
$flags = 0x04;
if($convertChars) $flags |= 0x01;
return $this->trimInternal($characters, $flags);
/**
* Strip whitespace (or other characters) from the end of a multibyte string.
*
* @param Stringable|string $string Input string.
* @param string $chars Characters to strip. List all characters you want. .. operator from rtrim is not supported.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return string Trimmed string.
*/
public static function trimEnd(Stringable|string $string, string $chars = self::TRIM_CHARS, ?string $encoding = null, ?string $charsEncoding = null): string {
return self::trimInternal($string, $chars, $encoding, $charsEncoding, self::TRIM_END);
}
public function toLower(): IString {
return new WString(mb_strtolower($this->value, $this->encoding), $this->encoding);
/**
* Reverses a multibyte string.
*
* @param Stringable|string $string String to reverse.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return string Reversed string.
*/
public static function reverse(Stringable|string $string, ?string $encoding = null): string {
return implode(array_reverse(mb_str_split((string)$string, encoding: $encoding)));
}
public function toUpper(): IString {
return new WString(mb_strtoupper($this->value, $this->encoding), $this->encoding);
}
public function reverse(): IString {
$chars = [];
foreach($this as $char)
$chars[] = $char;
return new WString(implode(array_reverse($chars)), $this->encoding);
}
public function countUnique(): int {
/**
* Counts unique characters in a string.
*
* @param Stringable|string $string String to count unique characters of.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return int Unique character count.
*/
public static function countUnique(Stringable|string $string, ?string $encoding = null): int {
$string = mb_str_split((string)$string, encoding: $encoding);
$chars = [];
foreach($this as $char)
foreach($string as $char)
if(!in_array($char, $chars, true))
$chars[] = $char;
return count($chars);
}
public function __toString(): string {
return $this->value;
}
public function toAString(bool $convert = true): AString {
$value = $this->value;
if($convert && self::sameEncoding('ascii', $this->encoding))
$value = mb_convert_encoding($value, 'ascii', $this->encoding);
return new AString($value);
}
public function getEncoding(): string {
return $this->encoding;
}
public function convertEncoding(string $encoding): WString {
if(self::sameEncoding($encoding, $this->encoding))
return $this;
$value = mb_convert_encoding($this->value, $encoding, $this->encoding);
return new WString($value, $encoding);
}
/**
* Checks if an offset exists in the string.
* Check if a multibyte string is null or whitespace.
*
* You should call isset($string[$offset]) instead of $string->offsetExists($offset).
*
* @see https://www.php.net/manual/en/arrayaccess.offsetexists.php
* @param int $offset Character offset.
* @return bool true if it exists, false if not.
* @param Stringable|string|null $string String to check for whitespace.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return bool true if the string is whitespace, false if not.
*/
public function offsetExists(mixed $offset): bool {
$offset = (int)$offset;
return $offset >= 0 && $offset < $this->getLength();
}
/**
* Gets an offset from the string.
*
* You should do $string[$offset] instead of $string->offsetGet($offset).
*
* @see https://www.php.net/manual/en/arrayaccess.offsetget.php
* @param int $offset Character offset.
* @return string Character at that offset.
*/
public function offsetGet(mixed $offset): mixed {
return mb_substr($this->value, (int)$offset, 1, $this->encoding);
}
/**
* Gets an iterator object for this string.
*
* @return StringIterator An iterator for this string.
*/
public function getIterator(): Traversable {
return new StringIterator($this);
}
/**
* Returns the data which should be serialized as json.
*
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed Data to be passed to json_encode.
*/
public function jsonSerialize(): mixed {
return $this->value;
}
public function bencodeSerialise(): mixed {
return $this->value;
}
/**
* Gets a serialized representation of this object.
*
* @return array Serialized data.
*/
public function __serialize(): array {
return [$this->encoding, $this->value];
}
/**
* Reconstructs an object from a serialized string.
*
* @param array $serialized Serialized data.
*/
public function __unserialize(array $serialized): void {
[$this->encoding, $this->value] = $serialized;
}
/**
* Checks whether this string is identical to another.
*
* @param mixed $other An instance of IString or a PHP string.
* @return bool true if the strings have the same value, false if not.
*/
public function equals(mixed $other): bool {
return $this->compare($other) === 0;
}
/**
* Compares whether this string is identical to another.
*
* @param mixed $other An instance of IString or a PHP string.
*/
public function compare(mixed $other): int {
$other = self::castEncoding($other, $this->encoding);
return strcmp($this->value, (string)$other);
}
/**
* Creates a new identical WString instance.
*
* This method is somewhat pointless, given the immutable nature of this object,
* but rather people calling this instead of calling ->substring(0);
*
* @return WString A new identical instance of WString.
*/
public function clone(): mixed {
return new WString($this->value, $this->encoding);
}
private function doRegex(callable $callback): mixed {
$encoding = self::getRegexEncoding();
self::setRegexEncoding($this->encoding);
$result = $callback();
self::setRegexEncoding($encoding);
return $result;
}
private static function sameEncoding(string $name1, string $name2): bool {
return mb_preferred_mime_name($name1) === mb_preferred_mime_name($name2);
}
/**
* Joins an iterable object together with a separator to create a string.
*
* @param iterable $source Source object.
* @param IString|string $separator Separator to use as glue.
* @param ?string $encoding Desired encoding, null for Index default.
* @return WString Resulting string.
*/
public static function join(iterable $source, IString|string $separator = '', ?string $encoding = null): WString {
// func probably doesn't work entirely as intended
$encoding ??= self::getDefaultEncoding();
$separator = self::castEncoding($separator, $encoding);
if(!is_array($source)) {
$parts = [];
foreach($source as $value)
$parts[] = $value;
$source = $parts;
}
return new WString(implode((string)$separator, $source), $encoding);
}
public static function getDefaultEncoding(): string {
return self::$defaultEncoding;
}
public static function setDefaultEncoding(string $encoding = ''): void {
if(empty($encoding))
self::$defaultEncoding = self::DEFAULT_ENCODING;
else {
try {
if(mb_encoding_aliases($encoding) !== false)
self::$defaultEncoding = $encoding;
} catch(ValueError $ex) {}
}
}
public static function getInternalEncoding(): string {
return mb_internal_encoding();
}
public static function setInternalEncoding(string $encoding): void {
mb_internal_encoding($encoding);
}
public static function getRegexEncoding(): string {
return mb_regex_encoding();
}
public static function setRegexEncoding(string $encoding): void {
mb_regex_encoding($encoding);
}
public static function fromRequest(string $raw): WString {
return new WString($raw, self::getInternalEncoding());
}
/**
* Returns a reusable empty string instance.
*
* @return WString An empty string.
*/
public static function empty(): WString {
static $empty = null;
$empty ??= new WString('');
return $empty;
}
public static function cast(mixed $string): WString {
if($string instanceof WString)
return $string;
if($string instanceof AString)
return $string->toWString('ascii', false);
return new WString((string)$string, 'ascii');
}
public static function castEncoding(mixed $string, string $encoding): WString {
return self::cast($string)->convertEncoding($encoding);
public static function nullOrWhitespace(Stringable|string|null $string, ?string $encoding = null): bool {
return $string === null || self::trim((string)$string, encoding: $encoding) === '';
}
}

View file

@ -1,7 +1,7 @@
<?php
// XString.php
// Created: 2022-02-03
// Updated: 2023-11-09
// Updated: 2024-01-04
namespace Index;
@ -19,6 +19,28 @@ final class XString {
*/
public const RANDOM_CHARS = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789';
/**
* Generates a random string of user specified length.
*
* @param int $length Desired length of the string.
* @param string $chars Set of characters to pick from. Default set contains the alphabet in upper- and lowercase and numbers 0 thru 9.
* @return string The generated string.
*/
public static function random(int $length, string $chars = self::RANDOM_CHARS): string {
if($length < 1)
throw new InvalidArgumentException('$length must be at least 1.');
if($chars === '')
throw new InvalidArgumentException('$chars may not be empty.');
$string = '';
$count = strlen($chars) - 1;
while($length-- > 0)
$string .= $chars[random_int(0, $count)];
return $string;
}
/**
* Converts special characters to HTML entities.
*
@ -63,9 +85,7 @@ final class XString {
* @return bool true if the string is empty, false if not.
*/
public static function nullOrEmpty(Stringable|string|null $string): bool {
if($string === null)
return true;
return empty((string)$string);
return $string === null || (string)$string === '';
}
/**
@ -75,30 +95,6 @@ final class XString {
* @return bool true if the string is whitespace, false if not.
*/
public static function nullOrWhitespace(Stringable|string|null $string): bool {
if($string === null)
return true;
return empty(trim((string)$string));
}
/**
* Generates a random string of user specified length.
*
* @param int $length Desired length of the string.
* @param string $chars Set of characters to pick from. Default set contains the alphabet in upper- and lowercase and numbers 0 thru 9.
* @return string The generated string.
*/
public static function random(int $length, string $chars = self::RANDOM_CHARS): string {
if($length < 1)
throw new InvalidArgumentException('$length must be at least 1.');
if($chars === '')
throw new InvalidArgumentException('$chars may not be empty.');
$string = '';
$count = strlen($chars) - 1;
while($length-- > 0)
$string .= $chars[random_int(0, $count)];
return $string;
return $string === null || trim((string)$string) === '';
}
}

View file

@ -1,65 +0,0 @@
<?php
// XStringTrait.php
// Created: 2021-06-22
// Updated: 2023-11-09
namespace Index;
use BadMethodCallException;
/**
* Provides method implementations that are common between all string types.
* @internal
* @deprecated Will be removed along with IString, AString and WString.
*/
trait XStringTrait {
/**
* Gets the length of the string.
*
* For compliance with the Countable interface.
*
* @see https://www.php.net/manual/en/class.countable.php
* @return int Length of the string.
*/
public function count(): int {
return $this->getLength();
}
/**
* Necessary to comply with the ArrayAccess interface.
*
* @internal
*/
public function offsetSet(mixed $offset, mixed $value): void {
throw new BadMethodCallException('Strings are immutable.');
}
/**
* Necessary to comply with the ArrayAccess interface.
*
* @internal
*/
public function offsetUnset(mixed $offset): void {
throw new BadMethodCallException('Strings are immutable.');
}
public function toBool(): bool {
return boolval((string)$this);
}
public function toInt(int $base = 10): int {
return $base === 10 ? (int)(string)$this : intval((string)$this, $base);
}
public function toFloat(): float {
return (float)(string)$this;
}
public function escape(
int $flags = ENT_COMPAT | ENT_HTML5,
?string $encoding = null,
bool $doubleEncoding = true
): IString {
return new AString(XString::escape($this, $flags, $encoding, $doubleEncoding));
}
}

View file

@ -1,287 +0,0 @@
<?php
// StringTest.php
// Created: 2021-04-26
// Updated: 2023-07-05
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Index\XArray;
use Index\AString;
use Index\WString;
use Index\XString;
/**
* @covers AString
* @covers WString
* @covers XString
*/
final class StringTest extends TestCase {
public function testLength(): void {
$ascii = new AString('soup');
$this->assertEquals(4, $ascii->getLength());
$this->assertEquals(4, count($ascii));
$this->assertFalse($ascii->isEmpty());
$utf8 = new WString('ßóúþ', 'utf-8');
$this->assertEquals(4, $utf8->getLength());
$this->assertEquals(4, count($utf8));
$this->assertEquals(8, $utf8->getByteCount());
$this->assertFalse($utf8->isEmpty());
$utf16be = new WString("\x00\xDF\x00\xF3\x00\xFA\x00\xFE", 'UTF-16BE');
$this->assertEquals(4, $utf16be->getLength());
$this->assertEquals(4, count($utf16be));
$this->assertEquals(8, $utf16be->getByteCount());
$this->assertFalse($utf16be->isEmpty());
$utf32le = new WString("\xDF\x00\x00\x00\xF3\x00\x00\x00\xFA\x00\x00\x00\xFE\x00\x00\x00", 'UTF-32LE');
$this->assertEquals(4, $utf32le->getLength());
$this->assertEquals(4, count($utf32le));
$this->assertEquals(16, $utf32le->getByteCount());
$this->assertFalse($utf32le->isEmpty());
$this->assertTrue($utf8->equals($utf16be));
$this->assertTrue($utf16be->equals($utf32le));
$this->assertTrue($utf32le->equals($utf8));
}
public function testToString(): void {
$ascii = new AString('Windows XP');
$this->assertEquals('Windows XP', (string)$ascii);
$utf16be = new WString("\x00\xDF\x00\xF3\x00\xFA\x00\xFE", 'UTF-16BE');
$this->assertEquals("\x00\xDF\x00\xF3\x00\xFA\x00\xFE", (string)$utf16be);
$utf32le = $utf16be->convertEncoding('utf-32le');
$this->assertEquals("\xDF\x00\x00\x00\xF3\x00\x00\x00\xFA\x00\x00\x00\xFE\x00\x00\x00", (string)$utf32le);
}
public function testArrayAccess(): void {
$ascii = new AString('Akai Haato');
$this->assertEquals('a', $ascii[2]);
$this->assertEquals('H', $ascii[5]);
$this->assertTrue(isset($ascii[6]));
$this->assertFalse(isset($ascii[23]));
$this->expectException(\BadMethodCallException::class);
$ascii[4] = '_';
$this->expectException(\BadMethodCallException::class);
unset($ascii[7]);
$utf16le = $ascii->toWString('utf-16le');
$this->assertEquals('a', $utf16le[2]);
$this->assertEquals('H', $utf16le[5]);
$this->assertTrue(isset($utf16le[6]));
$this->assertFalse(isset($utf16le[23]));
$this->expectException(\BadMethodCallException::class);
$utf16le[4] = '_';
$this->expectException(\BadMethodCallException::class);
unset($utf16le[7]);
}
public function testJsonSerialize(): void {
$ascii = new AString('Mewow');
$this->assertEquals('Mewow', json_decode(json_encode($ascii)));
$this->assertEquals('Mewow', json_decode(json_encode($ascii->toWString('Shift_JIS'))));
}
public function testSerializable(): void {
$ascii = new AString('Futami Mami');
$asciiSerialised = serialize($ascii);
$sjis = new WString("\x91\x6F\x8A\x43\x90\x5E\x94\xFC", 'Shift_JIS');
$sjisSerialised = serialize($sjis);
$asciiUnserialised = unserialize($asciiSerialised);
$this->assertTrue($ascii->equals($asciiUnserialised));
$this->assertTrue($asciiUnserialised->equals('Futami Mami'));
$sjisUnserialised = unserialize($sjisSerialised);
$this->assertTrue($sjis->equals($sjisUnserialised));
$this->assertTrue($sjisUnserialised->equals(new WString('双海真美', 'utf-8')));
}
public function testEquals(): void {
$ascii = new AString('Misaka Mikoto');
$this->assertTrue($ascii->equals($ascii));
$this->assertTrue($ascii->equals('Misaka Mikoto'));
$this->assertTrue($ascii->equals(new AString('Misaka Mikoto')));
$this->assertFalse($ascii->equals('Mewow'));
$this->assertFalse($ascii->equals(new AString('Ndx Mewow')));
}
public function testConcat(): void {
$ndxString1 = new AString('Akai ');
$ndxString2 = $ndxString1->append('Haato');
$ndxString3 = $ndxString1->append(new AString('Haato'));
$ndxString4 = (new AString('Haato'))->prepend($ndxString1);
$this->assertEquals('Akai Haato', (string)$ndxString2);
$this->assertEquals('Akai Haato', (string)$ndxString3);
$this->assertEquals('Akai Haato', (string)$ndxString4);
}
public function testSplit(): void {
$ascii = new AString('Kasane Teto');
$asciiArr = ['Kasane', 'Teto'];
$this->assertTrue(XArray::sequenceEquals($asciiArr, $ascii->split(' ')));
$utf16le = new WString("\x42\x30\x02\x30\x44\x30\x02\x30\x46\x30\x02\x30\x48\x30\x02\x30\x4A\x30\x02\x30", 'utf-16le');
$utf16leArr = [
new WString("\x42\x30", 'utf-16le'),
new WString("\x44\x30", 'utf-16le'),
new WString("\x46\x30", 'utf-16le'),
new WString("\x48\x30", 'utf-16le'),
new WString("\x4A\x30", 'utf-16le'),
new WString("", 'utf-16le'),
];
$this->assertTrue(XArray::sequenceEquals($utf16leArr, $utf16le->split(new WString("\x02\x30", 'utf-16le'))));
$utf8 = new WString('あ。い。う。え。お。', 'utf-8');
$utf8Arr = [
new Wstring('あ', 'utf-8'),
new Wstring('い', 'utf-8'),
new Wstring('う', 'utf-8'),
new Wstring('え', 'utf-8'),
new Wstring('お', 'utf-8'),
new Wstring('', 'utf-8'),
];
$this->assertTrue(XArray::sequenceEquals($utf8Arr, $utf8->split(new WString('。', 'utf-8'))));
}
public function testChunk(): void {
$ndxString = new AString('abcdefghijklmnopqrstuvwxyz');
$ndxArray1 = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz'];
$ndxArray2 = ['ab', 'cd', 'ef', 'gh', 'ij', 'kl', 'mn', 'op', 'qr', 'st', 'uv', 'wx', 'yz'];
$ndxArray3 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
$this->assertTrue(XArray::sequenceEquals($ndxString->chunk(3), $ndxArray1));
$this->assertTrue(XArray::sequenceEquals($ndxString->chunk(2), $ndxArray2));
$this->assertTrue(XArray::sequenceEquals($ndxString->chunk(1), $ndxArray3));
$utf8 = new WString('君は実にばかだな', 'utf-8');
$ndxArray4 = [
new WString('君は実', 'utf-8'),
new WString('にばか', 'utf-8'),
new WString('だな', 'utf-8'),
];
$ndxArray5 = [
new WString('君は', 'utf-8'),
new WString('実に', 'utf-8'),
new WString('ばか', 'utf-8'),
new WString('だな', 'utf-8'),
];
$ndxArray6 = [
new WString('君', 'utf-8'),
new WString('は', 'utf-8'),
new WString('実', 'utf-8'),
new WString('に', 'utf-8'),
new WString('ば', 'utf-8'),
new WString('か', 'utf-8'),
new WString('だ', 'utf-8'),
new WString('な', 'utf-8'),
];
$this->assertTrue(XArray::sequenceEquals($utf8->chunk(3), $ndxArray4));
$this->assertTrue(XArray::sequenceEquals($utf8->chunk(2), $ndxArray5));
$this->assertTrue(XArray::sequenceEquals($utf8->chunk(1), $ndxArray6));
}
public function testTrim(): void {
$ascii = new AString(' meow ');
$this->assertTrue($ascii->trim()->equals('meow'));
$this->assertTrue($ascii->trimStart()->equals('meow '));
$this->assertTrue($ascii->trimEnd()->equals(' meow'));
$utf16be = $ascii->toWString('utf-16be');
$this->assertTrue($utf16be->trim()->equals('meow'));
$this->assertTrue($utf16be->trimStart()->equals('meow '));
$this->assertTrue($utf16be->trimEnd()->equals(' meow'));
$utf8 = new WString(' にゃ ', 'utf-8');
$this->assertTrue($utf8->trim()->equals(new WString('にゃ', 'utf-8')));
$this->assertTrue($utf8->trimStart()->equals(new WString('にゃ ', 'utf-8')));
$this->assertTrue($utf8->trimEnd()->equals(new WString(' にゃ', 'utf-8')));
}
public function testCaseChange(): void {
$stringMixed = new AString('Sometimes');
$stringUpper = new AString('SOMETIMES');
$stringLower = new AString('sometimes');
$this->assertTrue($stringMixed->toUpper()->equals($stringUpper));
$this->assertTrue($stringMixed->toLower()->equals($stringLower));
}
public function testEscape(): void {
$stringDirty = new AString('<img onerror="alert(\'xss\')">');
$stringClean = new AString('&lt;img onerror=&quot;alert(\'xss\')&quot;&gt;');
$this->assertTrue($stringDirty->escape()->equals($stringClean));
}
public function testJoin(): void {
$ndxString1 = AString::join(['flash', 'wave']);
$ndxString2 = AString::join(['Misaka', 'Mikoto'], new AString(' '));
$this->assertEquals('flashwave', (string)$ndxString1);
$this->assertEquals('Misaka Mikoto', (string)$ndxString2);
}
public function testEmpty(): void {
$this->assertTrue(XString::nullOrEmpty(null));
$this->assertTrue(XString::nullOrEmpty(''));
$this->assertTrue(XString::nullOrEmpty(new AString('')));
$this->assertFalse(XString::nullOrEmpty('soap'));
$this->assertFalse(XString::nullOrEmpty(new AString('boat')));
$this->assertTrue(XString::nullOrWhitespace(''));
$this->assertTrue(XString::nullOrWhitespace(' '));
$this->assertTrue(XString::nullOrWhitespace(new AString(' ')));
}
public function testReverse(): void {
$ascii = new AString('Futami Mami');
$sjis = new WString("\x91\x6F\x8A\x43\x90\x5E\x94\xFC", 'Shift_JIS');
$asciiRev = $ascii->reverse();
$sjisRev = $sjis->reverse();
$this->assertEquals('imaM imatuF', (string)$asciiRev);
$this->assertEquals("\x94\xFC\x90\x5E\x8A\x43\x91\x6F", (string)$sjisRev);
}
public function testReplace(): void {
$ascii = new AString('aiueo');
$sjis = new WString("\x82\xA0\x82\xA2\x82\xA4\x82\xA6\x82\xA8", 'sjis');
$this->assertEquals('aikeo', $ascii->replace('u', 'k'));
$this->assertEquals('aeoeo', $ascii->replace('iu', 'eo'));
$this->assertTrue(
$sjis->replace(
new WString("\x46\x30", 'utf-16le'),
new WString("\xE3\x81\x8B", 'utf-8')
)->equals(new WString("\x82\xA0\x82\xA2\x82\xA9\x82\xA6\x82\xA8", 'sjis'))
);
$this->assertTrue(
$sjis->replace(
new WString("\xE3\x81\x84\xE3\x81\x86", 'utf-8'),
new WString("\x48\x30\x4A\x30", 'utf-16le')
)->equals(new WString("\x82\xA0\x82\xA6\x82\xA8\x82\xA6\x82\xA8", 'sjis'))
);
}
public function testCountUnique(): void {
$this->assertEquals(10, XString::countUnique('iaabbccddjjefghefghi'));
$this->assertEquals(11, (new AString('jeff has three apples'))->countUnique());
$this->assertEquals(5, (new WString('みさかみこと'))->countUnique());
}
}

47
tests/WStringTest.php Normal file
View file

@ -0,0 +1,47 @@
<?php
// WStringTest.php
// Created: 2021-04-26
// Updated: 2024-01-04
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Index\WString;
/**
* @covers WString
*/
final class WStringTest extends TestCase {
public function testStartsWith(): void {
$this->assertTrue(WString::startsWith('君は実にバカだな', '君は'));
}
public function testEndsWith(): void {
$this->assertTrue(WString::endsWith('君は実にバカだな', 'バカだな'));
}
public function testReverse(): void {
$this->assertEquals('なだカバに実は君', WString::reverse('君は実にバカだな'));
$this->assertEquals("\x82\xc8\x82\xbe\x83\x4a\x83\x6f\x82\xc9\x8e\xc0\x82\xcd\x8c\x4e", WString::reverse("\x8c\x4e\x82\xcd\x8e\xc0\x82\xc9\x83\x6f\x83\x4a\x82\xbe\x82\xc8", encoding: 'Shift_JIS'));
}
public function testTrim(): void {
$utf8 = ' にゃ ';
$this->assertEquals('にゃ', WString::trim($utf8, encoding: 'utf-8'));
$this->assertEquals('にゃ ', WString::trimStart($utf8, encoding: 'utf-8'));
$this->assertEquals(' にゃ', WString::trimEnd($utf8, encoding: 'utf-8'));
}
public function testEmpty(): void {
$this->assertTrue(WString::nullOrWhitespace(''));
$this->assertTrue(WString::nullOrWhitespace(' '));
$this->assertTrue(WString::nullOrWhitespace(' '));
$this->assertTrue(WString::nullOrWhitespace(' '));
}
public function testCountUnique(): void {
$this->assertEquals(10, WString::countUnique('iaabbccddjjefghefghi'));
$this->assertEquals(11, WString::countUnique('jeff has three apples'));
$this->assertEquals(5, WString::countUnique('みさかみこと'));
}
}

View file

@ -1,12 +1,11 @@
<?php
// XArrayTest.php
// Created: 2021-04-26
// Updated: 2022-02-03
// Updated: 2024-01-04
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Index\AString;
use Index\XArray;
/**
@ -75,17 +74,6 @@ final class XArrayTest extends TestCase {
$this->assertTrue(XArray::sequenceEquals($array1, $array2));
}
public function testConcat(): void {
$ndxString1 = new AString('abcde');
$ndxString2 = new AString('a_b_c_d_e');
$array = ['a', 'b', 'c', 'd', 'e'];
$this->assertTrue(AString::join($array)->equals($ndxString1));
$this->assertFalse(AString::join($array)->equals($ndxString2));
$this->assertFalse(AString::join($array, '_')->equals($ndxString1));
$this->assertTrue(AString::join($array, '_')->equals($ndxString2));
}
public function testMerge(): void {
$array1 = [0, 1, 2, 3, 4, 5, 6, 7];
$array2 = [0, 1, 2, 3];

34
tests/XStringTest.php Normal file
View file

@ -0,0 +1,34 @@
<?php
// XStringTest.php
// Created: 2021-04-26
// Updated: 2024-01-04
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Index\XString;
/**
* @covers XString
*/
final class XStringTest extends TestCase {
public function testEscape(): void {
$dirty = '<img onerror="alert(\'xss\')">';
$clean = '&lt;img onerror=&quot;alert(\'xss\')&quot;&gt;';
$this->assertEquals($clean, XString::escape($dirty));
}
public function testEmpty(): void {
$this->assertTrue(XString::nullOrEmpty(null));
$this->assertTrue(XString::nullOrEmpty(''));
$this->assertFalse(XString::nullOrEmpty('soap'));
$this->assertTrue(XString::nullOrWhitespace(''));
$this->assertTrue(XString::nullOrWhitespace(' '));
}
public function testCountUnique(): void {
$this->assertEquals(10, XString::countUnique('iaabbccddjjefghefghi'));
$this->assertEquals(11, XString::countUnique('jeff has three apples'));
}
}