Page MenuHomeDevCentral

D1601.diff
No OneTemporary

D1601.diff

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -3,3 +3,6 @@
* Sébastien Santoro aka Dereckson<br>
https://www.dereckson.be<br>
_Car la connaissance s'accroît quand on la partage._
+
+* Ronald Ulysses Swanson aka Wes
+https://twitter.com/WesNetmo
diff --git a/src/Strings/Multibyte/OmniString.php b/src/Strings/Multibyte/OmniString.php
new file mode 100644
--- /dev/null
+++ b/src/Strings/Multibyte/OmniString.php
@@ -0,0 +1,68 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Strings\Multibyte;
+
+class OmniString {
+
+ use WithEncoding;
+
+ ///
+ /// Private members
+ ///
+
+ /**
+ * @var string
+ */
+ private $value;
+
+ ///
+ /// Constructor
+ ///
+
+ public function __construct (string $value = '', ?string $encoding = null) {
+ $this->value = $value;
+ $this->setEncoding($encoding ?? "UTF-8");
+ }
+
+ ///
+ /// Magic methods
+ ///
+
+ public function __toString() : string {
+ return $this->value;
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ public function pad(
+ int $padLength = 0,
+ string $padString = ' ',
+ int $padType = STR_PAD_RIGHT
+ ) : string {
+ return (new StringPad)
+ ->setInput($this->value)
+ ->setEncoding($this->encoding)
+ ->setPadLength($padLength)
+ ->setPadString($padString)
+ ->setPadType($padType)
+ ->pad();
+ }
+
+ /**
+ * @return string
+ */
+ public function getValue () : string {
+ return $this->value;
+ }
+
+ /**
+ * @param string $value
+ */
+ public function setValue (string $value) {
+ $this->value = $value;
+ }
+
+}
diff --git a/src/Strings/Multibyte/StringPad.php b/src/Strings/Multibyte/StringPad.php
new file mode 100644
--- /dev/null
+++ b/src/Strings/Multibyte/StringPad.php
@@ -0,0 +1,213 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Strings\Multibyte;
+
+use InvalidArgumentException;
+
+class StringPad {
+
+ use WithEncoding;
+
+ ///
+ /// Private members for user-defined or default values
+ ///
+
+ /**
+ * @var string
+ */
+ private $input;
+
+ /**
+ * @var int
+ */
+ private $padLength;
+
+ /**
+ * @var string
+ */
+ private $padString;
+
+ /**
+ * @var int
+ */
+ private $padType;
+
+ ///
+ /// Private members for computed values
+ ///
+
+ /**
+ * @var string
+ */
+ private $repeatedString;
+
+ /**
+ * @var float
+ */
+ private $targetLength;
+
+ ///
+ /// Constructor
+ ///
+
+ public function __construct (
+ string $input = '',
+ int $padLength = 0,
+ string $padString = ' ',
+ int $padType = STR_PAD_RIGHT,
+ ?string $encoding = null
+ ) {
+ $this->input = $input;
+ $this->padLength = $padLength;
+ $this->padString = $padString;
+
+ $this->setPadType($padType);
+ $this->setEncoding($encoding ?? mb_internal_encoding());
+ }
+
+ ///
+ /// Getters and setters
+ ///
+
+ public function getInput () : string {
+ return $this->input;
+ }
+
+ public function setInput (string $input) : StringPad {
+ $this->input = $input;
+
+ return $this;
+ }
+
+ public function getPadLength () : int {
+ return $this->padLength;
+ }
+
+ public function setPadLength (int $padLength) : StringPad {
+ $this->padLength = $padLength;
+
+ return $this;
+ }
+
+ public function getPadString () : string {
+ return $this->padString;
+ }
+
+ public function setPadString (string $padString) : StringPad {
+ $this->padString = $padString;
+
+ return $this;
+ }
+
+ public function getPadType () : int {
+ return $this->padType;
+ }
+
+ public function setPadType (int $padType) : StringPad {
+ if (!self::isValidPadType($padType)) {
+ throw new InvalidArgumentException;
+ }
+
+ $this->padType = $padType;
+
+ return $this;
+ }
+
+ ///
+ /// Helper methods to get and set
+ ///
+
+ public function setBothPad () : StringPad {
+ $this->padType = STR_PAD_BOTH;
+
+ return $this;
+ }
+
+ public function setLeftPad () : StringPad {
+ $this->padType = STR_PAD_LEFT;
+
+ return $this;
+ }
+
+ public function setRightPad () : StringPad {
+ $this->padType = STR_PAD_RIGHT;
+
+ return $this;
+ }
+
+ public static function isValidPadType (int $padType) : bool {
+ return $padType >= 0 && $padType <= 2;
+ }
+
+ ///
+ /// Pad methods
+ ///
+
+ public function pad () : string {
+ $this->computeLengths();
+ return $this->getLeftPad() . $this->input . $this->getRightPad();
+ }
+
+ private function getLeftPad () : string {
+ if (!$this->hasPaddingBefore()) {
+ return '';
+ }
+
+ $length = (int)floor($this->targetLength);
+ return mb_substr($this->repeatedString, 0, $length, $this->encoding);
+ }
+
+ private function getRightPad () : string {
+ if (!$this->hasPaddingAfter()) {
+ return '';
+ }
+
+ $length = (int)ceil($this->targetLength);
+ return mb_substr($this->repeatedString, 0, $length, $this->encoding);
+ }
+
+ private function computeLengths () : void {
+ $this->targetLength = $this->computeNeededPadLength();
+ $this->repeatedString = $this->computeRepeatedString();
+ }
+
+ private function computeRepeatedString () : string {
+ // Inspired by Ronald Ulysses Swanson method
+ // https://stackoverflow.com/a/27194169/1930997
+ // who followed the str_pad PHP implementation.
+
+ $strToRepeatLength = mb_strlen($this->padString, $this->encoding);
+ $repeatTimes = (int)ceil($this->targetLength / $strToRepeatLength);
+
+ // Safe if used with valid Unicode sequences (any charset).
+ return str_repeat($this->padString, max(0, $repeatTimes));
+ }
+
+ private function computeNeededPadLength () : float {
+ $length = $this->padLength - mb_strlen($this->input, $this->encoding);
+
+ if ($this->hasPaddingBeforeAndAfter()) {
+ return $length / 2;
+ }
+
+ return $length;
+ }
+
+ private function hasPaddingBefore () : bool {
+ return $this->padType === STR_PAD_LEFT || $this->padType === STR_PAD_BOTH;
+ }
+
+ private function hasPaddingAfter () : bool {
+ return $this->padType === STR_PAD_RIGHT || $this->padType === STR_PAD_BOTH;
+ }
+
+ private function hasPaddingBeforeAndAfter () : bool {
+ return
+ $this->padType === STR_PAD_BOTH
+ ||
+ ($this->padType === STR_PAD_LEFT && $this->padType === STR_PAD_RIGHT)
+ ;
+ }
+
+}
diff --git a/src/Strings/Multibyte/StringUtilities.php b/src/Strings/Multibyte/StringUtilities.php
new file mode 100644
--- /dev/null
+++ b/src/Strings/Multibyte/StringUtilities.php
@@ -0,0 +1,46 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Strings\Multibyte;
+
+class StringUtilities {
+
+ /**
+ * Pads a multibyte string to a certain length with another string
+ *
+ * @param string $input the input string
+ * @param int $padLength the target string size
+ * @param string $padString the padding characters (optional, default is space)
+ * @param int $padType STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH (optional, default is STR_PAD_RIGHT)
+ * @param string $encoding the character encoding (optional)
+ *
+ * @return string the padded string
+ *
+ */
+ public static function pad (
+ string $input,
+ int $padLength,
+ string $padString = ' ',
+ int $padType = STR_PAD_RIGHT,
+ string $encoding = ''
+ ) : string {
+ return (new StringPad)
+ ->setInput($input)
+ ->setPadLength($padLength)
+ ->setPadString($padString)
+ ->setPadType($padType)
+ ->setEncoding($encoding)
+ ->pad();
+ }
+
+ public static function isSupportedEncoding (string $encoding) : bool {
+ foreach (mb_list_encodings() as $supportedEncoding) {
+ if ($encoding === $supportedEncoding) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/Strings/Multibyte/WithEncoding.php b/src/Strings/Multibyte/WithEncoding.php
new file mode 100644
--- /dev/null
+++ b/src/Strings/Multibyte/WithEncoding.php
@@ -0,0 +1,29 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Strings\Multibyte;
+
+use InvalidArgumentException;
+
+trait WithEncoding {
+
+ /**
+ * @var string
+ */
+ private $encoding;
+
+ public function getEncoding () : string {
+ return $this->encoding;
+ }
+
+ public function setEncoding (string $encoding) : self {
+ if (!StringUtilities::isSupportedEncoding($encoding)) {
+ throw new InvalidArgumentException;
+ }
+
+ $this->encoding = $encoding;
+
+ return $this;
+ }
+
+}
diff --git a/tests/Strings/Multibyte/OmniStringTest.php b/tests/Strings/Multibyte/OmniStringTest.php
new file mode 100644
--- /dev/null
+++ b/tests/Strings/Multibyte/OmniStringTest.php
@@ -0,0 +1,32 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Tests\Strings\Multibyte;
+
+use Keruald\OmniTools\Strings\Multibyte\OmniString;
+use PHPUnit\Framework\TestCase;
+
+use InvalidArgumentException;
+
+class OmniStringTest extends TestCase {
+
+ /**
+ * @var OmniString
+ */
+ private $string;
+
+ protected function setUp () {
+ $this->string = new OmniString("foo");
+ }
+
+ public function testToString () : void {
+ $this->assertEquals("foo", (string)$this->string);
+ $this->assertEquals("foo", $this->string->__toString());
+ }
+
+ public function testPad () : void {
+ $paddedString = $this->string->pad(9, '-=-', STR_PAD_BOTH);
+ $this->assertEquals("-=-foo-=-", $paddedString);
+ }
+
+}
diff --git a/tests/Strings/Multibyte/StringPadTest.php b/tests/Strings/Multibyte/StringPadTest.php
new file mode 100644
--- /dev/null
+++ b/tests/Strings/Multibyte/StringPadTest.php
@@ -0,0 +1,56 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Tests\Strings\Multibyte;
+
+use Keruald\OmniTools\Strings\Multibyte\StringPad as Pad;
+use PHPUnit\Framework\TestCase;
+
+use InvalidArgumentException;
+
+class StringPadTest extends TestCase {
+
+ public function testSetPadTypeWithBogusValue () : void {
+ $this->expectException(InvalidArgumentException::class);
+
+ $pad = new Pad;
+ $pad->setPadType(7);
+ }
+
+ public function testIsValidPadType () : void {
+ $this->assertTrue(Pad::isValidPadType(STR_PAD_LEFT));
+ $this->assertTrue(Pad::isValidPadType(STR_PAD_RIGHT));
+ $this->assertTrue(Pad::isValidPadType(STR_PAD_BOTH));
+
+ $this->assertFalse(Pad::isValidPadType(7));
+ }
+
+ public function testSetPadTypeWithBogusEncoding () : void {
+ $this->expectException(InvalidArgumentException::class);
+
+ $pad = new Pad;
+ $pad->setEncoding("notexisting");
+ }
+
+ public function testSetLeftPad () : void {
+ $pad = new Pad;
+ $pad->setLeftPad();
+
+ $this->assertEquals(STR_PAD_LEFT, $pad->getPadType());
+ }
+
+ public function testSetRightPad () : void {
+ $pad = new Pad;
+ $pad->setRightPad();
+
+ $this->assertEquals(STR_PAD_RIGHT, $pad->getPadType());
+ }
+
+ public function testSetBothPad () : void {
+ $pad = new Pad;
+ $pad->setBothPad();
+
+ $this->assertEquals(STR_PAD_BOTH, $pad->getPadType());
+ }
+
+}
diff --git a/tests/Strings/Multibyte/StringUtilitiesTest.php b/tests/Strings/Multibyte/StringUtilitiesTest.php
new file mode 100644
--- /dev/null
+++ b/tests/Strings/Multibyte/StringUtilitiesTest.php
@@ -0,0 +1,69 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Tests\Strings\Multibyte;
+
+use Keruald\OmniTools\Strings\Multibyte\StringUtilities;
+use PHPUnit\Framework\TestCase;
+
+class StringUtilitiesTest extends TestCase {
+
+ ///
+ /// Tests
+ ///
+
+ /**
+ * @dataProvider providePadding
+ */
+ public function testPad (
+ string $expected,
+ string $input, int $padLength, string $padString, int $padType
+ ) : void {
+ $paddedString = StringUtilities::pad(
+ $input, $padLength, $padString, $padType, "UTF-8"
+ );
+
+ $this->assertEquals($expected, $paddedString);
+ }
+
+ public function testSupportedEncoding () : void {
+ $this->assertTrue(StringUtilities::isSupportedEncoding("UTF-8"));
+ $this->assertFalse(StringUtilities::isSupportedEncoding("notexisting"));
+ }
+
+ ///
+ /// Data providers
+ ///
+
+ public function providePadding () : iterable {
+ // Tests from http://3v4l.org/UnXTF
+ // http://web.archive.org/web/20150711100913/http://3v4l.org/UnXTF
+
+ yield ['àèòàFOOàèòà', "FOO", 11, "àèò", STR_PAD_BOTH];
+ yield ['àèòFOOàèòà', "FOO", 10, "àèò", STR_PAD_BOTH];
+ yield ['àèòBAAZàèòà', "BAAZ", 11, "àèò", STR_PAD_BOTH];
+ yield ['àèòBAAZàèò', "BAAZ", 10, "àèò", STR_PAD_BOTH];
+ yield ['FOOBAR', "FOOBAR", 6, "àèò", STR_PAD_BOTH];
+ yield ['FOOBAR', "FOOBAR", 1, "àèò", STR_PAD_BOTH];
+ yield ['FOOBAR', "FOOBAR", 0, "àèò", STR_PAD_BOTH];
+ yield ['FOOBAR', "FOOBAR", -10, "àèò", STR_PAD_BOTH];
+
+ yield ['àèòàèòàèFOO', "FOO", 11, "àèò", STR_PAD_LEFT];
+ yield ['àèòàèòàFOO', "FOO", 10, "àèò", STR_PAD_LEFT];
+ yield ['àèòàèòàBAAZ', "BAAZ", 11, "àèò", STR_PAD_LEFT];
+ yield ['àèòàèòBAAZ', "BAAZ", 10, "àèò", STR_PAD_LEFT];
+ yield ['FOOBAR', "FOOBAR", 6, "àèò", STR_PAD_LEFT];
+ yield ['FOOBAR', "FOOBAR", 1, "àèò", STR_PAD_LEFT];
+ yield ['FOOBAR', "FOOBAR", 0, "àèò", STR_PAD_LEFT];
+ yield ['FOOBAR', "FOOBAR", -10, "àèò", STR_PAD_LEFT];
+
+ yield ['FOOàèòàèòàè', "FOO", 11, "àèò", STR_PAD_RIGHT];
+ yield ['FOOàèòàèòà', "FOO", 10, "àèò", STR_PAD_RIGHT];
+ yield ['BAAZàèòàèòà', "BAAZ", 11, "àèò", STR_PAD_RIGHT];
+ yield ['BAAZàèòàèò', "BAAZ", 10, "àèò", STR_PAD_RIGHT];
+ yield ['FOOBAR', "FOOBAR", 6, "àèò", STR_PAD_RIGHT];
+ yield ['FOOBAR', "FOOBAR", 1, "àèò", STR_PAD_RIGHT];
+ yield ['FOOBAR', "FOOBAR", 0, "àèò", STR_PAD_RIGHT];
+ yield ['FOOBAR', "FOOBAR", -10, "àèò", STR_PAD_RIGHT];
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Fri, Dec 20, 04:44 (21 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2307419
Default Alt Text
D1601.diff (14 KB)

Event Timeline