Wie behandelt PHP Whitespaces?
Knut
- javascript
- php
Hallo,
Ich bins nochmals mit einem weiteren "Problem"...
...und zwar verstehe ich nicht ganz, wie sich PHP bezüglich Whitespaces verhält.
Habe herumprobiert -
<?php
$eineVar = " Viele Leerzeichen ";
echo $eineVar;
?>
...alle überschüssigen Leerzeichen scheinen in diesem Fall zu verschwinden.
const options = {
method: "POST",
body: " Viele Leerzeichen "
};
const response = fetch("./testing_php_trim.php", options)
.then(data => data.text())
.then(text => console.log(text));
...und PHP-seitig (für Testzwecke) nur mal via file_get_contents zurückgeschickt (in der Anwendung dann wohl eher mit FormData):
// testing_php_trim.php
<?php
$content = trim(file_get_contents("php://input"));
echo $content;
echo "\n";
echo trim($content);
?>
...auch hier scheint ein automatisches trim() zu passieren, beide $contents werden clientseitig in der Konsole gleich ausgegeben.
Ich habe mich ein wenig damit gespielt, um ein Gefühl dafür zu bekommen, wie auf Whitespaces reagiert wird und hatte dann irgendwann eine Situation, wo die Whitespaces plötzlich ausgegeben wurden. Wie das Leben nun mal so spielt, dachte ich mir zu diesem Zeitpunkt nichts und überschrieb den Code, weswegen ich ihn hier leider nicht mehr reproduzieren kann :(
Bin aber nun ein wenig verunsichert.
Wie behandelt PHP Whitespaces genau und wann ist trim() notwendig?
Danke, Knut.
@@Knut
- Szenario 1: eine ganz einfache PHP Ausgabe im DOM-Tree:
<?php $eineVar = " Viele Leerzeichen "; echo $eineVar; ?>
...alle überschüssigen Leerzeichen scheinen in diesem Fall zu verschwinden.
Nein, nicht durch PHP. Das geschieht im Browser beim Parsen des generierten HTML.
😷 LLAP
Tach!
Wie behandelt PHP Whitespaces genau und wann ist trim() notwendig?
PHP hat keinen eingebauten Mechanismus, String automatisch zu verändern. trim() ist genauso wie alle andere Stringverarbeitung nur dann nötig, wenn du einen fachlichen Grund dafür siehst.
Abgesehen davon gibt es noch technische Gründe beim Kontextwechsel, wenn Anweisungen/Code und Nutzdaten in einem String zusammengefügt werden sollen (z.B. SQL-Statements oder HTML). Dann müssen die Regeln des jeweiligen Codes beachtet werden, ansonsten entstehen Parsefehler oder unerwüschte Ergebnise (auch sicherheitsrelevante).
dedlfix.
Hello,
...und PHP-seitig (für Testzwecke) nur mal via file_get_contents zurückgeschickt (in der Anwendung dann wohl eher mit FormData):
// testing_php_trim.php <?php $content = trim(file_get_contents("php://input")); echo $content; echo "\n"; echo trim($content); ?>
...auch hier scheint ein automatisches trim() zu passieren, beide $contents werden clientseitig in der Konsole gleich ausgegeben.
Du hast doch auch trim(file_get_contents("php://input"))
notiert.
Da wird der Eingabestream eben vorne und hinten um die in trim() voreingestellten Bytes gekürzt. Ich benutze hier "Bytes", weil trim()
eine Single-Byte-Funktion ist.
Vergleiche hierzu auch die mb_*-Funktionen. Für trim()
kann ich aber keine entsprechende finden.
Glück Auf
Tom vom Berg
Hallo TS,
nee, das scheint man per Regex lösen zu müssen. Ist aber nicht trivial, wenn ich mich so bei StackOverflow umschaue.
Rolf
Hello,
nee, das scheint man per Regex lösen zu müssen. Ist aber nicht trivial, wenn ich mich so bei StackOverflow umschaue.
Na, oder eben mit so 'nem Algendingsbums aus der Tiefsee der Programmierung (Parser). Schließlich sind RegExen und ihre Maschine nix Anderes.
Glück Auf
Tom vom Berg
Hallo TS,
öhm - das wird jetzt aber eine lange Tangente… Diese Gleichheiten sind so nicht gegeben. Einen Parser zu verwenden um Spaces zu entfernen, tja, das geht sicherlich. Ich kann auch mit dem 40-Tonner losfahren, um einen Kasten Bier zu kaufen.
Rolf
@@Rolf B
- Die interne Darstellung ist im einfachen Fall ein endlicher Automat - was aber bei modernen Regex auf Probleme läuft, weil ein endlicher Automat reguläre Sprachen abbilden kann (->Chomsky-Hierarchie), und eine Regex trotz ihres Namens nicht regulär ist. Für die heutigen Regexe braucht man komplexere Maschinen.
Das hab ich letztens im kleinen Kreis erzählt: RegExp ≠ regular expression.
Und da vieles hier aus dem Forum gezeigt: What a RegExp!
😷 LLAP
Ich habe „ideenhalber“ mal versucht, eine auf Arrays basierende Funktion zu schreiben, die mit UTF-Symbolen kann. Diese scheint zu funktionieren, mir auch ausreichend schnell zu sein - ist aber noch nicht genügend getestet.
Weshalb ich das Zeug ausdrücklich als „bad“ („schlecht“, „nicht verwenden”) markiere:
<?php
$nbsp = chr(0xC2).chr(0xA0);
$string = '🛠' . $nbsp . 'äüöödäipusö🛠' . $nbsp . 'öäü';
$toTrim = '🛠öä' . $nbsp . 'ü';
echo 'Zu durchsuchender String (HTML-kodiert:)'. PHP_EOL;
echo "\t". '"' . htmlentities( $string, ENT_HTML5 ) .'"' . PHP_EOL . PHP_EOL;
echo' Zu entfernende Zeichen: (HTML-kodiert:)' . PHP_EOL;
echo "\t". '"' . htmlentities( $toTrim, ENT_HTML5 ) .'"' . PHP_EOL . PHP_EOL;
echo 'Ergebnis: "' . raketen_trim( $string, $toTrim ) . '"' . PHP_EOL;
$rounds = 1000000;
$start = microtime(true);
for ($i=0; $i<$rounds; $i++) {
raketen_trim( $string, $toTrim ) . PHP_EOL;
}
echo round( ( microtime(true) - $start) * 1000) . ' Millisekunden für ' . $rounds . ' Runden.' . PHP_EOL;
function raketen_trim( $string, $toTrim ) {
if ( '' == $string ) return '';
if ( ! is_array( $toTrim ) ) {
$arToTrim = mb_str_split( $toTrim );
}
if ( ! count($arToTrim ) ) return $string;
$arString = mb_str_split( $string );
$changed = true;
while ( $changed ) {
$changed = false;
if ( in_array( $arString[ 0 ], $arToTrim ) ) {
array_shift( $arString );
if (0 == count( $arString ) ) return '';
$changed = true;
}
if ( in_array( $arString[ sizeof($arString) -1 ], $arToTrim ) ) {
array_pop( $arString );
if (0 == count( $arString ) ) return '';
$changed = true;
}
}
return implode( '', $arString );
}
Ausgaben: (Die Leerzeichen sind "geschützte Leerzeichen"):
Zu durchsuchender String (HTML-kodiert:)
"🛠 äüöödäipusö🛠 öäü"
Zu entfernende Zeichen: (HTML-kodiert:)
"🛠öä ü"
Ergebnis: "däipus"
10226 Millisekunden für 1000000 Runden.
Wettbewerb(e):
Hallo Raketenwilli,
lässt Du das auf einem RASPI laufen?
2611 Millisekunden für 1000000 Runden.
Und mein Hobel ist steinalt (Core i5-3470)
Rolf
Nö. lscpu behauptet:
AMD Ryzen 7 2700 Eight-Core Processor
Das System ist aber so konfiguriert, dass der wegen der kurzen Belastung nicht erst aus dem Stromsparmodus geht.
CPU MHz: 1550.000
Stepping: 2
Frequenzanhebung: aktiviert
CPU MHz: 1550.000
und ich hatte php-xdebug an Start. Das ha ich jetzt mal abgeschaltet:
Verbesserungen: (immer noch nicht genug getestet!)
<?php
### file: mb_trim.php
if ( ! function_exists ( 'mb_trim' ) ) {
function mb_trim( $string, $toTrim ) {
if ( '' == $string ) return '';
if ( ! is_array( $toTrim ) ) {
$arToTrim = mb_str_split( $toTrim );
}
if ( ! count( $arToTrim ) ) return $string;
$arString = mb_str_split( $string );
$changed = true;
while ( $changed ) {
$changed = false;
if ( in_array( $arString[ 0 ], $arToTrim ) ) {
array_shift( $arString );
if (0 == count( $arString ) ) return '';
$changed = true;
}
if ( in_array( $arString[ count( $arString ) -1 ], $arToTrim ) ) {
array_pop( $arString );
if (0 == count( $arString ) ) return '';
$changed = true;
}
}
return implode( '', $arString );
}
} else {
trigger_error( 'The function "mb_trim" exists! Pleace check your script(s)!', E_USER_ERROR );
}
if ( ! function_exists ( 'mb_rtrim' ) ) {
function mb_rtrim( $string, $toTrim ) {
if ( '' == $string ) return '';
if ( ! is_array( $toTrim ) ) {
$arToTrim = mb_str_split( $toTrim );
}
if ( ! count( $arToTrim ) ) return $string;
$arString = mb_str_split( $string );
$changed = true;
while ( $changed ) {
$changed = false;
if ( in_array( $arString[ count( $arString ) -1 ], $arToTrim ) ) {
array_pop( $arString );
if (0 == count( $arString ) ) return '';
$changed = true;
}
}
return implode( '', $arString );
}
} else {
trigger_error( 'The function "mb_rtrim" exists! Pleace check your script(s)!', E_USER_ERROR );
}
if ( ! function_exists ( 'mb_ltrim' ) ) {
function mb_ltrim( $string, $toTrim ) {
if ( '' == $string ) return '';
if ( ! is_array( $toTrim ) ) {
$arToTrim = mb_str_split( $toTrim );
}
if ( ! count( $arToTrim ) ) return $string;
$arString = mb_str_split( $string );
$changed = true;
while ( $changed ) {
$changed = false;
if ( in_array( $arString[ 0 ], $arToTrim ) ) {
array_shift( $arString );
if (0 == count( $arString ) ) return '';
$changed = true;
}
}
return implode( '', $arString );
}
} else {
trigger_error( 'The function "mb_ltrim" exists! Pleace check your script(s)!', E_USER_ERROR );
}
Tests:
<?php
require_once ('mb_trim.php');
$rounds = 100000;
$nbsp = chr(0xC2).chr(0xA0);
$string = '🛠' . $nbsp . 'äüöödäipusö🛠' . $nbsp . 'öäü';
$toTrim = '🛠öä' . $nbsp . 'ü';
echo 'Zu durchsuchender String (HTML-kodiert:)'. PHP_EOL;
echo "\t". '"' . htmlentities( $string, ENT_HTML5 ) .'"' . PHP_EOL . PHP_EOL;
echo' Zu entfernende Zeichen: (HTML-kodiert:)' . PHP_EOL;
echo "\t". '"' . htmlentities( $toTrim, ENT_HTML5 ) .'"' . PHP_EOL . PHP_EOL;
echo 'Ergebnis für mb_trim(): "' . mb_trim( $string, $toTrim ) . '"' . PHP_EOL;
echo 'Ergebnis für mb_rtrim(): "' . mb_rtrim( $string, $toTrim ) . '"' . PHP_EOL;
echo 'Ergebnis für mb_ltrim(): "' . mb_ltrim( $string, $toTrim ) . '"' . PHP_EOL;
echo PHP_EOL . 'Zeitmessung:'. PHP_EOL;
####################################################
echo PHP_EOL . 'mb_trim:' .PHP_EOL;
$start = microtime(true);
for ($i=0; $i<$rounds; $i++) {
mb_trim( $string, $toTrim ) . PHP_EOL;
}
$time = ( microtime(true) - $start ) ;
echo $time . PHP_EOL;
echo round( $time * 1000) . ' Millisekunden für ' . $rounds . ' Runden. (' . round($time/$rounds * 1000000) . ' Mikrosekunden pro Aufruf).' . PHP_EOL;
####################################################
echo PHP_EOL . 'mb_rtrim:' . PHP_EOL;
$start = microtime(true);
for ($i=0; $i<$rounds; $i++) {
mb_rtrim( $string, $toTrim ) . PHP_EOL;
}
$time = ( microtime(true) - $start ) ;
echo $time . PHP_EOL;
echo round( $time * 1000) . ' Millisekunden für ' . $rounds . ' Runden. (' . round($time/$rounds * 1000000) . ' Mikrosekunden pro Aufruf).' . PHP_EOL;
####################################################
echo PHP_EOL . 'mb_ltrim:' . PHP_EOL;
$start = microtime(true);
for ($i=0; $i<$rounds; $i++) {
mb_ltrim( $string, $toTrim ) . PHP_EOL;
}
$time = ( microtime(true) - $start ) ;
echo $time . PHP_EOL;
echo round( $time * 1000) . ' Millisekunden für ' . $rounds . ' Runden. (' . round($time/$rounds * 1000000) . ' Mikrosekunden pro Aufruf).' . PHP_EOL;
Ausgaben von php -dxdebug.mode=off test_mb_trim.php
Zu durchsuchender String (HTML-kodiert:)
"🛠 äüöödäipusö🛠 öäü"
Zu entfernende Zeichen: (HTML-kodiert:)
"🛠öä ü"
Ergebnis für mb_trim(): "däipus"
Ergebnis für mb_rtrim(): "🛠 äüöödäipus"
Ergebnis für mb_ltrim(): "däipusö🛠 öäü"
Zeitmessung:
mb_trim:
0.26273894309998
263 Millisekunden für 100000 Runden. (3 Mikrosekunden pro Aufruf).
mb_rtrim:
0.15137410163879
151 Millisekunden für 100000 Runden. (2 Mikrosekunden pro Aufruf).
mb_ltrim:
0.16421294212341
164 Millisekunden für 100000 Runden. (2 Mikrosekunden pro Aufruf).
Jetzt lachst Du nicht mehr :-)
Hallo Raketenwilli,
erste naive Lösung, die auf die Array-Konvertierung verzichtet und mb_-Funktionen nutzt:
function rolfb_trim( $string, $toTrim ) {
if (!is_string($string)) return false;
if (!is_string($toTrim) || $toTrim == '') return $string;
$len = mb_strlen($string);
for ($start = 0;
$start < $len && mb_strpos($toTrim, mb_substr($string, $start, 1), 0) !== false;
$start++);
for ($end = $len - 1;
$end > $start && mb_strpos($toTrim, mb_substr($string, $end, 1), 0) !== false;
$end--);
return mb_substr($string, $start, $end-$start+1);
}
Die ist auf meinem PC ca 15% schneller. Lasse ich den input-String auf ein "x" enden, so dass sie keinen end-Trim machen muss, ist sie sogar doppelt so schnell. Verlängere ich den input-String mit str_repeat um 500 Zeichen (in der Mitte), wird sie (mit x am Ende) viermal so schnell. Offenbar ist mb_substr eine Schnecke.
Also habe ich deine und meine Lösung gemixt:
function rolf_trim2( $string, $toTrim ) {
if (!is_string($string)) return false;
if (!is_string($toTrim) || $toTrim == '') return $string;
$arString = mb_str_split( $string );
$len = count($arString);
for ($start = 0;
$start < $len && mb_strpos($toTrim, $arString[$start]) !== false;
$start++);
for ($end = $len-1;
$end > $start && mb_strpos($toTrim, $arString[$end]) !== false;
$end--);
return mb_substr($string, $start, $end-$start+1);
}
Diese Funktion ist konsistent doppelt so schnell wie Deine. Aber die Laufzeit schwankt immer noch proportional zur input-Länge, ich nehme an, der mb_str_split nicht ideal ist. Um das zu lösen, muss man wohl einen UTF-Char Iterator für Strings bauen. Hold my beer...
Rolf
Hallo Rolf,
schmeiß das Bier weg - so ein UTF-8 Iterator in PHP ist viel zu langsam (zumindest, wenn man das Iterator-Interface programmiert).
Damit ist meine trim2 Lösung mein derzeit bestes Angebot.
Rolf
Ja. Sieht so aus, als wenn Du vorn liegst…
Tach!
Vergleiche hierzu auch die mb_*-Funktionen. Für
trim()
kann ich aber keine entsprechende finden.
Siehe User-Kommentar.
Wenn es lediglich um Zeichen im ASCII-Bereich geht, kann trim() problemlos auch mit UTF-8 verwendet werden.
dedlfix.
Hallo,
Vergleiche hierzu auch die mb_*-Funktionen. Für
trim()
kann ich aber keine entsprechende finden.Wenn es lediglich um Zeichen im ASCII-Bereich geht, kann trim() problemlos auch mit UTF-8 verwendet werden.
selbstverständlich - aber dann darf man eben nicht erwarten, dass trim() auch die Whitespace-Varianten der Unicode-Vielfalt eliminiert (Zero Width Space, Half Width Space und sowas).
Möge die Übung gelingen
Martin
Hi,
Vergleiche hierzu auch die mb_*-Funktionen. Für
trim()
kann ich aber keine entsprechende finden.
für die Version mit einem Parameter braucht's auch bei UTF-8-Strings keine mb-Version.
Die damit behandelten Zeichen sind alle unterhalb von 7F, haben also genau 1 Byte. Und Zeichen mit Codes oberhalb von 7F haben in den einzelnen Bytes jeweils das höchste Bit gesetzt, sind also höher als 7f, so daß durch trim($string) auch keine anderen Zeichen zerstört werden.
(bei der Variante mit selbst-angeführten Zeichen ist's was anderes - da funktioniert trim nur richtig, wenn die selbst-angeführten Zeichen alle unterhalb von 7f sind)
cu,
Andreas a/k/a MudGuard
Moin,
...und zwar verstehe ich nicht ganz, wie sich PHP bezüglich Whitespaces verhält.
ganz einfach: Gleichgültig.
<?php $eineVar = " Viele Leerzeichen "; echo $eineVar; ?>
...alle überschüssigen Leerzeichen scheinen in diesem Fall zu verschwinden.
Wie prüfst du das? In der Browseransicht? Dann ist es nicht PHP, sondern der HTML-Parser des Browsers, der die überschüssigen Leerzeichen verschwinden lässt ("komprimiert").
// testing_php_trim.php <?php $content = trim(file_get_contents("php://input")); echo $content; echo "\n"; echo trim($content); ?>
...auch hier scheint ein automatisches trim() zu passieren
Witzbold. Dein PHP-Code lässt erstmal ein trim() auf die Eingabe los, gibt dann das Ergebnis aus, macht dann nochmal ein trim() auf den bereits getrim()-ten String (was an der Stelle nichts mehr zum Abschneiden findet).
Wie behandelt PHP Whitespaces genau und wann ist trim() notwendig?
PHP behandelt Whitespace gar nicht, und trim() ist genau dann notwendig, wenn du Whitespace am Anfang und/oder am Ende eines Strings abschneiden willst.
Möge die Übung gelingen
Martin
Hello,
Wie behandelt PHP Whitespaces genau und wann ist trim() notwendig?
PHP behandelt Whitespace gar nicht, und trim() ist genau dann notwendig, wenn du Whitespace am Anfang und/oder am Ende eines Strings abschneiden willst.
Spannend bleibt aber die Nebenfrage, wie die Browser mit führenden und anhängenden Whitespaces bei der Übertragung der Parameter aus unterschiedlichen Kontexten und mit unterschiedlichen Methoden umgehen?
<input >
-Elementen?Gibt's sonst noch 'was?
Mein Wissen dazu ist veraltet, bzw. verschüttet ;-O
Glück Auf
Tom vom Berg
Moin Tom,
Spannend bleibt aber die Nebenfrage, wie die Browser mit führenden und anhängenden Whitespaces bei der Übertragung der Parameter aus unterschiedlichen Kontexten und mit unterschiedlichen Methoden umgehen?
- Wie war das bei
<input >
-Elementen?
Da ich ja beliebigen Text eintragen kann, ist das auch als value
erlaubt.
- Wie ist es bei Textareas?
Laut https://html.spec.whatwg.org/multipage/rendering.html#the-textarea-element-2 „User agents are expected to apply the 'white-space' CSS property to textarea elements.“ Das ist ja auch sinnvoll, weil ich in eine textarea
beliebigen Text eingeben kann.
Insgesamt einmal demonstriert:
<input type="text" value=" 4 " name="t">
<textarea>1
2
3 (4 Leerzeichen)
4 (1 Tab)
5</textarea>
- Wie ist es bei File-Data [upload]
Da wäre ich sehr verwundert, wenn irgendein Browser den Dateiinhalt manipuliert.
Viele Grüße
Robert
Hello,
ja, genauso wäre es logisch.
Ich erinnere mich aber daran, dass in einem der Fälle (vermutlich eine der Input-Klassen) trotzdem geschnippelt wurde, zumindest bei trailing whitspaces.
Kann allerdings auch sein, dass das eine Browsereigenheitunart war.
Glück Auf
Tom vom Berg
Moin Tom
Ich erinnere mich aber daran, dass in einem der Fälle (vermutlich eine der Input-Klassen) trotzdem geschnippelt wurde, zumindest bei trailing whitspaces.
Kann allerdings auch sein, dass das eine Browser
eigenheitunart war.
guter Punkt, ich habe mal den Cursor ins input
gesetzt um die vier trailing white-space zu zeigen:
Viele Grüße
Robert
Hello,
Ich erinnere mich aber daran, dass in einem der Fälle (vermutlich eine der Input-Klassen) trotzdem geschnippelt wurde, zumindest bei trailing whitspaces.
Kann allerdings auch sein, dass das eine Browser
eigenheitunart war.guter Punkt, ich habe mal den Cursor ins
input
gesetzt um die vier trailing white-space zu zeigen:
Beim aktuellen Firefox (bei mir 97.0.1 64-Bit) wird tatsächlich nix mehr getrimmt.
<?php
$i001 = '';
if (isset($_POST['i001'])) { $i001 = $_POST['i001']; }
$len = strlen($i001);
$mblen = mb_strlen($i001); ## vorher apt install php-mbstring
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Whitespaces</title>
</head>
<body>
<form action="" method="POST" enctype="multipart/form-data">
<input name="i001" type="text" size="20" value="<?=htmlspecialchars($i001); ?>">
<input name="b001" type="submit" value="absenden"><br>
Länge = <?=$len; ?><br>
MB-Länge = <?=$mblen; ?>
</form>
</body>
</html>
Glück Auf
Tom vom Berg
Uff, da hab ich ja eine richtige Lawine losgetreten
Danke alle Teilnehmenden und deren Innen! :D
...stimmt, ich habe file_get_contents
bereits ein trim
zugeschrieben und dafür ein Witzbold kassiert....
...kommt davon, wenn man im Schnellverfahren versucht alle möglichen Optionen durchzuspielen.
Kommen vom Hudeln die Kinder, können meine Nachfahren und deren Innen eine Nation ausrufen.