Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3911999
D1601.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D1601.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D1601: Allow to pad an UTF-8 or other multibyte string
Attached
Detach File
Event Timeline
Log In to Comment