Toon posts:

[php] str_replace / preg_replace encoding probleem

Pagina: 1
Acties:

Verwijderd

Topicstarter
Hi mensen,

ik heb een encoding issue waar ik ondertussen behoorlijk gefrustreerd van raak.

De case is als volgt:
Ik haal op basis van CURL haal ik de volledige content binnen van een externe site. Vervolgens wil ik alle karakters met een accent vervangen door karakters zonder accent en daarna het geheel op het scherm tonen.

Wat heb ik zoal geprobeerd?
Volgens mb_detect_encoding is de binnengehaalde content UTF-8 gecodeerd.

PHP:
1
2
3
4
5
        $search = explode(",",
                "ç,æ,œ,á,é,í,ó,ú,à,è,ì,ò,ù,ä,ë,ï,ö,ü,ÿ,â,ê,î,ô,û,å,ø,Ø,Å,Á,À,Â,Ä,È,É,Ê,Ë,Í,Î,Ï,Ì,Ò,Ó,Ô,Ö,Ú,Ù,Û,Ü,Ÿ,Ç,Æ,Œ");
        $replace = explode(",",
                "c,ae,oe,a,e,i,o,u,a,e,i,o,u,a,e,i,o,u,y,a,e,i,o,u,a,o,O,A,A,A,A,A,E,E,E,E,I,I,I,I,O,O,O,O,U,U,U,U,Y,C,AE,OE");
        $text =  str_replace($search, $replace, $text);


PHP:
1
2
3
4
5
6
7
8
9
10
11
12
$normalizeChars = array(
                'Ŝ'=>'S', 'š'=>'s', 'Ð'=>'Dj','Ž'=>'Z', 'ž'=>'z', 'À'=>'A', 'Á'=>'A', 'Â'=>'A', 'Ã'=>'A', 'Ä'=>'A',
                'Å'=>'A', 'Æ'=>'A', 'Ç'=>'C', 'È'=>'E', 'É'=>'E', 'Ê'=>'E', 'Ë'=>'E', 'Ì'=>'I', 'Í'=>'I', 'Î'=>'I',
                'Ï'=>'I', 'Ñ'=>'N', 'Ò'=>'O', 'Ó'=>'O', 'Ô'=>'O', 'Õ'=>'O', 'Ö'=>'O', 'Ø'=>'O', 'Ù'=>'U', 'Ú'=>'U',
                'Û'=>'U', 'Ü'=>'U', 'Ý'=>'Y', 'Þ'=>'B', 'ß'=>'Ss','à'=>'a', 'á'=>'a', 'â'=>'a', 'ã'=>'a', 'ä'=>'a',
                'å'=>'a', 'æ'=>'a', 'ç'=>'c', 'è'=>'e', 'é'=>'e', 'ê'=>'e', 'ë'=>'e', 'ì'=>'i', 'í'=>'i', 'î'=>'i',
                'ï'=>'i', 'ð'=>'o', 'ñ'=>'n', 'ò'=>'o', 'ó'=>'o', 'ô'=>'o', 'õ'=>'o', 'ö'=>'o', 'ø'=>'o', 'ù'=>'u',
                'ú'=>'u', 'û'=>'u', 'ý'=>'y', 'ý'=>'y', 'þ'=>'b', 'ÿ'=>'y', 'ƒ'=>'f',
                'ă'=>'a', 'î'=>'i', 'â'=>'a', 'ș'=>'s', 'ț'=>'t', 'Ă'=>'A', 'Î'=>'I', 'Â'=>'A', 'Ș'=>'S', 'Ț'=>'T',
        );
        
        echo strtr($text, $normalizeChars);


PHP:
1
2
$text = iconv("UTF-8", "ISO-8859-1//TRANSLIT", $text)
$text = iconv("UTF-8", "ASCII//TRANSLIT", $text)


Vooralsnog geen succes.

Preg_replace en eregi_replace werkt ook niet.

Het php-document is utf-8 gedecodeerd.

Ik word er een beetje zot van 8)7

Iemand nog tips?

  • Tribits
  • Registratie: Augustus 2011
  • Laatst online: 05:47

Tribits

Onkruid vergaat niet

Voor zover ik weet kan PHP niet overweg met Unicode strings in de source code. Zie ook dit artikel op Stack Overflow: Unicode characters in PHP string..

Het komt er dus op neer dat je wat truc werk uit moet halen om Unicode characters in een PHP string te krijgen. Beste (snelste) optie lijkt de JSON encoding/decoding methode, de HTML-Entities methode ben ik zelf wel eens mee aan het experimenteren geweest en werkt ook wel maar kennelijk langzamer.

Je geeft overigens niet aan wat je mb settings zijn in je php.ini maar ik neem aan dat daar het probleem niet zit.

edit: Als ik er nog eens over na denk vermoed ik dat er echter een heel ander probleem optreedt. Je geeft weliswaar aan dat mb_detect_encoding aangeeft dat de pagina UTF8 encoded is maar die functie is eigenlijk alleen bruikbaar als je de strict optie meegeeft. Als de pagina inderdaad UTF8 encoded is (met Byte Order Mark) zou je eerste sourcecode voorbeeld moeten werken. Het lijkt er dus op dat het een html bestand is met html entities of helemaal geen UTF8.

Als de pagina geen BOM bevat zal je aan de hand van de <meta charset> in de pagina moeten bepalen wat er wel in staat en aan de hand daarvan de pagina naar een unicode string converteren. Voor UTF8 met html entities wordt het dan:
PHP:
1
2
        $text = mb_convert_encoding($text , 'UTF-8', 'HTML-ENTITIES');
        $text = str_replace($search, $replace, $text);


en voor bijvoorbeeld ISO-8859-15:

PHP:
1
2
        $text = mb_convert_encoding($text , 'UTF-8', 'ISO-8859-15');
        $text = str_replace($search, $replace, $text);

[ Voor 49% gewijzigd door Tribits op 02-07-2014 07:50 ]

Master of questionable victories and sheer glorious defeats


  • ValHallASW
  • Registratie: Februari 2003
  • Niet online
Verwijderd schreef op woensdag 02 juli 2014 @ 00:35:
PHP:
1
2
$text = iconv("UTF-8", "ISO-8859-1//TRANSLIT", $text)
$text = iconv("UTF-8", "ASCII//TRANSLIT", $text)
code:
1
2
<?php
echo iconv("UTF-8", "ASCII//TRANSLIT", "abcdëfghíç");

(uiteraard met de php-file utf-8 encoded opgeslagen, anders is de string die je aan iconv voert immers geen utf-8...)

levert bij mij
code:
1
abcdefghic


op, wat is wat je zoekt, volgens mij. Wat 'werkt er niet' bij jou?

Verwijderd

Topicstarter
Thanks Tribits,

Ik kan je voor 80% volgen. Ik heb dit document er ook nog maar even bijgepakt: http://kunststube.net/encoding/

Als ik het goed begrijp is het dus onmogelijk om zeker te weten in welke encoding het binnengehaalde document staat?
Dan is het toch ook lastig om te bepalen welke mb_convert_encoding ik moet toepassen?

Je eerste oplossing voor mb_convert_encoding geeft overigens goede resultaten. Echter euro-tekens worden nu weergegeven als a‚¬ en dat is het gedeelte wat ik niet begrijp. Ook niet na het lezen van bovenstaand document. Zou je dat nog willen toelichten?

  • ValHallASW
  • Registratie: Februari 2003
  • Niet online
Verwijderd schreef op woensdag 02 juli 2014 @ 10:15:
Je eerste oplossing voor mb_convert_encoding geeft overigens goede resultaten. Echter euro-tekens worden nu weergegeven als a‚¬ en dat is het gedeelte wat ik niet begrijp.
Dat komt omdat je het document bekijkt alsof het latin-1 is in plaats van utf-8.

"€" wordt in utf-8 opgeslagen met drie bytes: 0xE2 0x82 0xAC. Die komen in latin-1 overeen met respectievelijk â, (undefined) en ¬.

Verwijderd

Topicstarter
Dank ValHallASW,

als ik mijn browser settings omzet naar utf-8 gaat dat ook goed inderdaad.
Ik heb in mijn head <meta charset="UTF-8" /> staan. Ik ging er vanuit dat dit voldoende moet zijn maar helaas. Ik zal verder moeten zoeken om dit dan te forceren. Enige tips?

  • Merethil
  • Registratie: December 2008
  • Nu online
Verwijderd schreef op woensdag 02 juli 2014 @ 13:08:
Dank ValHallASW,

als ik mijn browser settings omzet naar utf-8 gaat dat ook goed inderdaad.
Ik heb in mijn head <meta charset="UTF-8" /> staan. Ik ging er vanuit dat dit voldoende moet zijn maar helaas. Ik zal verder moeten zoeken om dit dan te forceren. Enige tips?
Probeer deze eens ipv alleen de charset:

HTML:
1
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">


Eventueel deze toevoegen
PHP:
1
 header('Content-type: text/html; charset=utf-8');

als je merkt dat de headers LATIN1 eisen.

[ Voor 12% gewijzigd door Merethil op 02-07-2014 13:47 ]


  • DJMaze
  • Registratie: Juni 2002
  • Niet online
Ik snap niet waarom je het wil omzetten.

Echter heb ik wel zoiets gemaakt om search data in latin1 op te slaan zodat er rap gezocht kan worden.

https://code.google.com/p...icode/utf-8/modifiers.inc
En
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Unicode
{
        protected static
                $chars = array(),
                $chars_modified = array();

        # Convert UTF-8 characters to their basic lowercased equivelants
        # examples: the German sharp S becomes ss or Latin L with curl becomes l
        public static function stripModifiers($str)
        {
                if (!self::$chars) { require(__DIR__.'/utf-8/modifiers.inc'); }
                return preg_replace(self::$chars_modified, self::$chars, $str);
        }
}

[ Voor 30% gewijzigd door DJMaze op 02-07-2014 13:59 ]

Maak je niet druk, dat doet de compressor maar


Verwijderd

Topicstarter
DJMaze,

uiteindelijk wil ik het volgende bereiken:

Ik wil via CURL een willekeurige website binnen halen als een contentstring.
Vervolgens wil ik een regel / woord binnen die content kunnen highlighten.
Uiteindelijke echo ik de content naar scherm. Daarbij laat ik in principe de originele meta tags ongemoeid. Deze zou ik er eventueel uit kunnen strippen.

Stel dat ik " Venetië is een toffe bestemming " welke mogelijkerwijs op een geCURLde website staat zou willen voorzien van een <span class="highlight"></span>.
Dan wil ik op basis van een preg_match allerlei variaties op die zin willen markeren zoals:
" Venetië is een toffe bestemming ",
" Venetië, is een toffe bestemming "
" Venetië is een <b>toffe</b> bestemming "

De preg_match die ik daarbij heb werkt zolang ik maar geen accenten hoef te matchen :

PHP:
1
2
3
$regPattern = "Venetië([\s,.!?:;'\"]|<([^>]+)>)*is([\s,.!?:;'\"]|<([^>]+)>)*een([\s,.!?:;'\"]|<([^>]+)>)*toffe([\s,.!?:;'\"]|<([^>]+)>)*bestemming([\s,.!?:;'\"]|<([^>]+)>)*";

$text = preg_replace( '/(?im)' . $regPattern . '/', '<span class="highlight">' .'${0}'.'</span>' , $text);


Mijn volgende oplossing was om dan maar zowel in de $text als de $regPattern alle accenten eruit te strippen. En dat krijg ik niet voor elkaar. En dat was aanleiding voor mijn vraag.

  • MueR
  • Registratie: Januari 2004
  • Laatst online: 13:12

MueR

Admin Devschuur® & Discord

is niet lief

ValHallASW schreef op woensdag 02 juli 2014 @ 10:15:
code:
1
2
<?php
echo iconv("UTF-8", "ASCII//TRANSLIT", "abcdëfghíç");
Dit is de enige goede oplossing. Niet klooien met regexps en dergelijke. De enige kanttekening die ik hierbij moet maken is dat je echt setlocale(LC_ALL, 'nl_NL.UTF8'); (of een andere UTF-8 locale) moet instellen, anders werkt het niet goed. Verder is het natuurlijk wel afhankelijk van de encoding van de website waar je de content af wil halen. Als die hem in ISO sturen, ga je nooit UTF8 characters kunnen vervangen, want die zijn er niet.

[ Voor 18% gewijzigd door MueR op 02-07-2014 16:35 ]

Anyone who gets in between me and my morning coffee should be insecure.


Verwijderd

Topicstarter
Dank voor de input tot dusver.

Ik heb nog even een testcase uitgeschreven die ik zoveel mogelijk heb ontdaan van onnodige code.
Ik heb twee willekeurige sites gepakt met een ë erin.
Op de site van rijksoverheid kan ik het woord met de trema erin markeren na iconv EN mb_convert_encoding.
Op de site van stedentrips lukt me dat vooralsnog niet.


PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//      $replace = 'preg';
        $replace = 'str' ;
        
        $url = 'http://www.stedentrips.nl/stedentrip/venetie';
        $url = 'http://www.rijksoverheid.nl/nieuws/2014/01/21/investeer-slim-met-de-energiebespaarlening.html';
        
        $iconv = true;

        // grab content
        $url_parts = parse_url ( $url );
            
        // initiate CURL
        $ch = curl_init ( $url_parts ['host'] );
            
        // set options
        curl_setopt ( $ch, CURLOPT_URL, $url ) or die ( "Invalid cURL Handle Resouce" );
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );   // just return the data - not print the whole thing.
        curl_setopt ( $ch, CURLOPT_HEADER, false );          // don't return headers
        curl_setopt ( $ch, CURLOPT_FOLLOWLOCATION, true );   // follow redirects
        curl_setopt ( $ch, CURLOPT_CONNECTTIMEOUT, 120 );    // timeout on connect
        curl_setopt ( $ch, CURLOPT_TIMEOUT, 120 );           // timeout on response
        curl_setopt ( $ch, CURLOPT_FOLLOWLOCATION, true );   // follow redirects
        curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, false );
        curl_setopt ( $ch, CURLOPT_MAXREDIRS, 10 );          // stop after 10 redirects
            
        // execute curl
        $response       = curl_exec( $ch );
            
        // close CURL
        curl_close( $ch );
            
        // set response as output variable
       $text = $response;
        
        
        // encode content als het bronbestand geen utf is.
        if (mb_detect_encoding( $text ) != 'UTF-8') {
            $text = utf8_encode( $text );
        }
        
        // onderstaande moet nog geoptimaliseerd worden dat er alleen woorden in de body gemarkeerd worden EN mogen geen attribute van een html element zijn.
        
        // markeer woorden
        // Dit wil ik eigenlijk bereiken. Gewoon een div voor en /div na een pattern plakken
        $prefix = '<span style="background-color:#FFBBDD">';
        $postfix = '</span>';
        
        if ($url == 'http://www.stedentrips.nl/stedentrip/venetie') {
            $pattern = "Venetië";
        }
        elseif ($url == 'http://www.rijksoverheid.nl/nieuws/2014/01/21/investeer-slim-met-de-energiebespaarlening.html') {
            $pattern = "financiële";
        }
        
        
        // deze functie converteert op http://www.stedentrips.nl/stedentrip/venetie alle ë naar "e
        // deze functie converteert op http://www.rijksoverheid.nl/nieuws/2014/01/21/investeer-slim-met-de-energiebespaarlening.html alle eurotekens naar EUR
        if (isset($iconv))   $text = iconv("UTF-8", "ASCII//TRANSLIT", $text);
        
        
        
        
        
        // onderstaande markeert maar converteert alle UTF-8 tekens als Latin-1 weergegeven 
        if (($url == 'http://www.stedentrips.nl/stedentrip/venetie') && ($replace == 'preg')) {
            $text = preg_replace( '/' . $pattern . '/', $prefix .'${0}'.$postfix , $text);
        }
        
        // onderstaande markeert maar converteert alle UTF-8 tekens als Latin-1 weergegeven 
        elseif (($url == 'http://www.stedentrips.nl/stedentrip/venetie') && ($replace == 'str')) {
            $text =  str_replace($pattern, $prefix.$pattern.$postfix, $text);
        }


        
        // onderstaande markeert alleen als ik mb_convert_encoding toepas, helaas is mijn euroteken wel euroteken niet meer intact maar een EUR
        if (($url == 'http://www.rijksoverheid.nl/nieuws/2014/01/21/investeer-slim-met-de-energiebespaarlening.html') && ($replace == 'preg')) {
            $text = mb_convert_encoding($text , 'UTF-8', 'HTML-ENTITIES');
            $text = preg_replace( '/' . $pattern . '/', $prefix .'${0}'.$postfix , $text);
        }
         
        // onderstaande markeert alleen als ik mb_convert_encoding toepas, helaas is mijn euroteken wel euroteken niet meer intact maar een EUR
        elseif (($url == 'http://www.rijksoverheid.nl/nieuws/2014/01/21/investeer-slim-met-de-energiebespaarlening.html') && ($replace == 'str')) {
            $text = mb_convert_encoding($text , 'UTF-8', 'HTML-ENTITIES');
            $text =  str_replace($pattern, $prefix.$pattern.$postfix, $text);
        }
        
        // Charset staat in alle bovenstaande gevallen nog op UTF-8 
        
        // set view items
        echo '<base href="'.$url.'">';
        echo $text;

  • Tribits
  • Registratie: Augustus 2011
  • Laatst online: 05:47

Tribits

Onkruid vergaat niet

Okay, nog even naar gekeken. Allereerst wat betreft de twee pagina's die je hier als test gebruikt: dat zijn beide pagina's met een UTF-8 meta zonder byte order mark. Dat is waarschijnlijk de meest gangbare situatie maar je kan dus ook pagina's met een andere karakterset tegenkomen. Tweakers is daar een voorbeeld van, deze site gebruikt "charset=iso-8859-15". Als je wilt testen of je code goed werkt kan je het beste wat verschillende sites zoeken.

Brengt me (nogmaals) bij het probleem met mb_detect_encoding: die functie detecteert in principe alleen of de string van een unicode byte order mark voorzien is. Daar heb je voor een webpagina niet zoveel aan want daar wil je op basis van de headers of meta bepalen wat de karakterset is. Bovendien heeft mb_detect_encoding het probleem dat het zonder de strict parameter zelf probeert te bepalen wat voor codering een string heeft als er geen byte order mark gevonden wordt. Dat levert onbetrouwbare resultaten op. Probeer maar eens deze pagina op te vragen en dan op beide manieren de encoding te detecteren:
PHP:
1
2
3
4
        $url = 'http://gathering.tweakers.net/forum/list_messages/1596409';
        // knip-knip-knip
        echo "mb_detect_encoding=" . mb_detect_encoding( $text );
        echo "mb_detect_encoding=" . mb_detect_encoding( $text, "auto", true);

Je ziet dan dat de eerste (onterecht) UTF-8 teruggeeft en de tweede helemaal niets, onbruikbaar dus.

De beste manier om de karakterset op te vragen lijkt vooralsnog om die uit de http header te plukken met de volgende code:
PHP:
1
2
3
4
5
6
7
8
9
    /* Get the content type from CURL */
    $content_type = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
 
    /* Get the MIME type and character set */
    preg_match( '@([\w\/]+)(;\s*charset=(\S+))?@i', $content_type, $matches );
    if ( isset( $matches[1] ) )
            $mime = $matches[1];
    if ( isset( $matches[3] ) )
            $charset = $matches[3];


Als je iets anders dan UTF-8 terugkrijgt zal je dat moeten matchen met 1 van de encodings die PHP ondersteunt en mb_convert_encoding aan moeten roepen. Als de header geen encoding bevat kan je altijd nog proberen die uit het HTML document te halen maar ik weet niet of dat de moeite waard is omdat het waarschijnlijk om een beperkt aantal sites zal gaan. Zonder verdere controle op aanwezigheid of geldigheid van de charset ziet dat er dan als volgt uit:

PHP:
1
2
3
        if ($charset != 'UTF-8') { 
            $text=mb_convert_encoding($text, 'UTF-8', $charset);
        }


Dan naar het probleem met het al dan niet vinden van matches: zoals eerder aangegeven kent PHP geen Unicode strings in de broncode. Als je een string opneemt in de broncode zoals je dat hier gedaan hebt wordt dat dus gewoon Windows-1252 of welke karakterset je editor ook gebruikt. Dat valt op twee manieren op te lossen: voor tekst die in een codering met 256 karakters geschreven kan worden door deze te converteren naar Unicode en voor alle andere teksten door de karakters te vervangen door HTML entities en te converteren naar Unicode. Je zoek patronen zien er dan als volgt uit (ik edit zelf onder Windows):

PHP:
1
2
            $pattern = mb_convert_encoding("Venetië", 'UTF-8', 'Windows-1252'); 
            $pattern = mb_convert_encoding("financiële", 'UTF-8', 'Windows-1252');


of

PHP:
1
2
            $pattern = mb_convert_encoding("Veneti&#235;", 'UTF-8', 'HTML-ENTITIES'); 
            $pattern = mb_convert_encoding("financi&#235;le", 'UTF-8', 'HTML-ENTITIES');


Dan het probleem met de verdwijnende eurotekens. Je hebt gelijk dat het in principe genoegs is <meta charset=UTF-8> in de html file te zetten om het een Unicode file te maken (als je het goed doet staat het natuurlijk ook in de http header). Dat probleem met die vreemde tekens werd veroorzaakt door het converteren van HTML entities naar UTF-8. Kennelijk voert die conversie ook een omzetting van alle tekens boven de 127 naar Unicode uit en dat verminkt dus alles wat al Unicode is. Als lapmiddel voer ik nu een eerst een conversie van UTF-8 naar HTML entities uit en dan weer terug. Ziet er een beetje vreemd uit maar lijkt vooralsnog te werken:

PHP:
1
2
        $text = mb_convert_encoding($text , 'HTML-ENTITIES', 'UTF-8'); 
        $text = mb_convert_encoding($text , 'UTF-8', 'HTML-ENTITIES');


edit: HTML-Entities vertalen met html_entity_decode werkt wel als verwacht en kan dus de twee bovenstaande regels vervangen:
PHP:
1
        $text = html_entity_decode ( $text, ENT_QUOTES | ENT_HTML401 , "UTF-8" );


Om het goed te doen zal je alleen wel moeten bepalen of de pagina HTML 4 of 5 is.
/edit

Tenslotte kan die call naar iconv er volgens mij wel uit, dat was meer om alle Unicode naar ASCII om te zetten en dat lijkt me nu niet meer nodig. Voor zover ik kan zien werken trouwens zowel de preg_replace en de str_replace methode verder goed.

Kijk maar hoever je komt. Als je de hele code inclusief aanpassingen wilt hebben moet je het maar even laten weten.

[ Voor 5% gewijzigd door Tribits op 05-07-2014 20:05 ]

Master of questionable victories and sheer glorious defeats

Pagina: 1