Avril 2008 [2]
parse_url pour de vrai Posté dans PHP — 3 commentaires
Pour ceux qui ne la connaissent pas, la fonction parse_url « analyse une URL et retourne un tableau associatif contenant tous les éléments qui y sont présents » (dixit sa documentation). Ce que la documentation dit moins c'est que la fonction s'endort un peu en cours de route en ce qui concerne la requête (les trucs après le point d'interrogation). Ainsi, lorsque l'on utilise la fonction avec l'URL – totalement bidon, mais intéressante – http://www.weirdog.com/blog/?tag=XML&chars=%E9%E9%E0%E0 on se retrouve avec le tableau suivant:
Array
(
[scheme] => http
[host] => www.weirdog.com
[path] => /blog/
[query] => tag=XML&chars=%E9%E9%E0%E0
)
Bon, c'est sympa, mais quid de query ? Ce serait chouette de l'avoir aussi sous forme de tableau, parce que là elle ne ressemble pas à grand chose.
Malheureusement, papa PHP n'a pas pensé à ajouter une petite option pour cela, alors comme d'habitude il va falloir être inventif (ce qui n'est pas mal non plus).
La relève
Je vous propose donc wd_parse_url(), une petite fonction qui fera exactement ça : tout mettre sous forme de tableau. Et comme si de rien n'était, elle décode aussi les arguments de la requête en prenant bien soin d'encoder les caractères dans le jeu de caractère de votre choix (ce que ne fait même pas urldecode() soit dit en passant).
<?php
function wd_parse_url($url, $charset='UTF-8')
{
$url = parse_url($url);
#
# is the query key is defined, we need to create a table
# from its parameters and values
#
if (isset($url['query']))
{
#
# split query parts 'a=1', 'b=2'...
#
$query = $url['query'];
#
# select separator
#
$separator = strpos($query, '&') ? '&' : '&';
#
# transform query parts into a lovely [key => value] array
#
$parts = explode($separator, $query);
$query = array();
foreach ($parts as $part)
{
$av = explode('=', $part);
#
# if the argument's value is an empty string we discard the argument
#
if ($av[1] === '')
{
continue;
}
$value = urldecode($av[1]);
#
# encode the value to the final charset
#
if ($charset)
{
$value = mb_convert_encoding($value, $charset);
}
$query[$av[0]] = $value;
}
$url['query'] = $query;
}
return $url;
}
?>
Résultat
Pour un résultat toujours plus blanc :
Array
(
[scheme] => http
[host] => www.weirdog.com
[path] => /blog/
[query] => Array
(
[tag] => XML
[chars] => ééàà
)
)
Séparateur
Si tu as fais attention au code, ami développeur, tu verras que j'essaie de choisir au mieux le séparateur pour les arguments de la requête. On pourrait se dire que dans une URL belle et valide, le caractère & est échappé en amp;. Le problème c'est que ce n'est vrai que dans un fichier HTML. Lorsque tu lis les URL depuis le tableau associatif $_SERVER, les échappements ce sont échappés, ce qui est bien normal.
Ainsi, pour ne pas te compliquer la vie (ni la mienne), si & est présent il est utilisé comme séparateur, sinon c'est & qui est utilisé.
Si ça te plait pas, tu peux toujours modifier la fonction d'un geste rageur.
Sinon c'est aussi bien.
Pourquoi ne pas utiliser parse_str ?
Ordre de pensée :
- « Ben je savais même pas que ça existait. »
- « Pourquoi ils ont pas appelé ça
parse_query? » - « A ben ça marche même pas bien ! »
Tout comme tant d'autres fonctions, parse_str ne supporte pas l'utf-8.
Un test
- Créez une page HTML avec un encodage en UTF-8
- Utilisez la fonction proposée par naholyr
- Copiez le code suivant dans votre page :
<?php
$a = wd_parse_url($_SERVER['REQUEST_URI']);
echo '<pre>RESULT: ' . print_r($a, true) . '</pre>';
?> - Ajoutez
?test=ééààà l'adresse de votre page
Et vous obtiendrez :
RESULT: Array
(
[path] => /wd/wdpublisher/test.php
[query] => Array
(
[pouic] => ����
)
)
Encore une fois le manque de support de l'utf-8 se fait cruellement sentir. De plus parse_str ajoute de vilains guillemets magiques, même si l'on s'en ai débarrassé plus tôt. Merci pour ta remarque Naholyr, mais je préfère laisser cette fonction de côté.
PHP, XML & Prévisions Météo Posté dans PHP — 1 commentaire
On ne sait plus trop comment s'habiller ces jours-ci. Un jour on se ballade poils au vent, caressé par un soleil complice. Le lendemain on rentre du boulot en vélo, arrosé par une pluie moqueuse. Des conditions climatiques perplexes, idéales pour présenter ma dernière classe : WdWeather.
Présentation
Utilisant les services pratiques et connus de weather.com, WdWeather permet d'obtenir les prévisions météorologiques jusqu'à 10 jours (limite imposée par le service). Les informations sont aussi nombreuses que variées. Ainsi pour chaque jour on obtient, en autre, les informations suivantes :
- Les heures de lever et de coucher du soleil
- Les températures les plus basse et les plus hautes
Et pour chaque période de la journée (Jour / Nuit) :
- Le numéro du pictogramme associé au temps.
- Une description du temps en anglais, par exemple : Mostly Sunny.
- La force et la direction du vent.
- Les précipitations.
- Le taux d'humidité.
Bref, il y a de quoi faire.
Voyons justement comment tout ça marche.
Comment ça marche ?
C'est vraiment très simple. Le principe est le suivant:
- On demande le code de la ville dont on souhaite connaitre les prévisions météo.
- On demande les prévisions météo pour les jours à venir.
- On affiche le résultat.
C'est tellement fastoche que je me suis presque endormi en écrivant les étapes.
Le Code
<?php
class WdWeather
{
var $markup_contents = '([^<]+)';
function getCityCode($search)
{
#
# we create a nice looking URL
#
$search = explode(',', $search);
$search = array_map('urlencode', $search);
$search = implode(',+', $search);
#
# ask the service about the location
#
$xml = file_get_contents
(
'http://xoap.weather.com/search/search?where=' . $search
);
#
# try to get the first result
#
if (!preg_match('#loc id="([^"]+)#', $xml, $matches))
{
return;
}
return $matches[1];
}
function getWeather($city_code, $days)
{
$xml = file_get_contents
(
'http://xoap.weather.com/weather/local/' . $city_code .
'?cc=*&unit=s&dayf=' . $days
);
if (!$xml)
{
return;
}
return $this->parseDays($xml);
}
function parseDays($xml)
{
$days = array();
#
# split xml by days
#
$parts = preg_split('#<day d=[^>]+>#', $xml);
array_shift($parts);
$mc = $this->markup_contents;
foreach ($parts as $xml)
{
$days[] = $this->parseDay($xml);
}
return $days;
}
function parseDay($xml)
{
$mc = $this->markup_contents;
preg_match
(
'#' .
'<hi>' . $mc . '</hi>\s*' .
'<low>' . $mc . '</low>\s*' .
'<sunr>' . $mc . '</sunr>\s*' .
'<suns>' . $mc . '</suns>\s*' .
'<part p="d">(.*)</part>\s*' .
'<part p="n">(.*)</part>' .
'#ms',
$xml, $matches
);
array_shift($matches);
#
# parse day periods (day / night)
#
$matches[4] = $this->parseDayPeriod($matches[4]);
$matches[5] = $this->parseDayPeriod($matches[5]);
#
#
#
return array_combine
(
array('hi', 'low', 'sunr', 'suns', 'day', 'night'), $matches
);
}
function parseDayPeriod($xml)
{
$mc = $this->markup_contents;
preg_match
(
'#' .
'<icon>' . $mc . '</icon>\s*' .
'<t>' . $mc . '</t>\s*' .
'<wind>(.*)</wind>\s*' .
'<bt>' . $mc . '</bt>\s*' .
'<ppcp>' . $mc . '</ppcp>\s*' .
'<hmid>' . $mc . '</hmid>' .
'#ms',
$xml, $matches
);
array_shift($matches);
$matches[2] = $this->parseWind($matches[2]);
return array_combine
(
array('icon', 't', 'wind', 'bt', 'ppcp', 'hmid'), $matches
);
}
function parseWind($xml)
{
$mc = $this->markup_contents;
preg_match
(
'#' .
'<s>' . $mc . '</s>\s*' .
'<gust>' . $mc . '</gust>\s*' .
'<d>' . $mc . '</d>\s*' .
'<t>' . $mc . '</t\s*' .
'#ms',
$xml, $matches
);
array_shift($matches);
return array_combine
(
array('s', 'gust', 'd', 't'), $matches
);
}
static function toCelsius($f)
{
if (!is_numeric($f))
{
return $f;
}
return round(($f-32) * 5 / 9);
}
static function to24H($time)
{
preg_match('#(\d+)\:(\d+)\s+(AM|PM)?#', $time, $matches);
if ($matches[3] == 'PM')
{
$matches[1] += 12;
}
return $matches[1] . ':' . $matches[2];
}
}
?>
L'exemple
Trois lignes d'initialisation :
- On crée l'objet.
- On récupère le code de la ville qui nous intéresse. Ici Toulouse, parce que c'est chez moi.
- On récupère les prévisions météo pour les quatre jours qui viennent.
<?php
require_once 'wdweather.php';
$weather = new WdWeather();
$code = $weather->getCityCode('Toulouse, France');
$days = $weather->getWeather($code, 4);
define('WEATHER_ICONS_URL', 'wdweather/');
?>
On met tout ça dans un tableau
<table id="weather" cellpadding="4" cellspacing="0">
<tr>
Pour chaque jour on affiche les informations suivantes :
- Pictogramme des conditions météo pour le jour et la nuit.
- Heures de lever et de coucher du soleil.
- Température la plus haute, température la plus basse.
<?php
foreach ($days as $day)
{
echo '<td>';
$title = $day['day']['t'];
echo '<img src="' . WEATHER_ICONS_URL . $day['day']['icon'] . '.png"';
echo ' alt="' . $title . '" title="' . $title . '" />';
$title = $day['night']['t'];
echo '<img src="' . WEATHER_ICONS_URL . $day['night']['icon'] . '.png"';
echo ' alt="' . $title . '" title="' . $title . '" />';
echo '<br />';
echo WdWeather::to24H($day['sunr']);
echo ' · ';
echo WdWeather::to24H($day['suns']);
echo '<br />';
echo $day['hi'] == 'N/A' ? '—' : WdWeather::toCelsius($day['hi']) . '°C';
echo ' · ';
echo $day['low'] == 'N/A' ? '—' : WdWeather::toCelsius($day['low']) . '°C';
echo '</td>';
}
?>
Et voilà, c'est fini. On remarquera juste l'utilisation de la méthode WdWeather::to24H() pour transformer la vilaine heure sur 12H en jolie heure sur 24H. On remarquera aussi l'utilisation de la méthode WdWeather::toCelsius()pour transformer les vilains degrés Fahrenheit en jolis degrés Celsius.
</tr>
</table>
Le résultat
Voici le résultat pour ce glorieux jour de soleil. Nous sommes le 19 Avril 2008, il est 12H27 et dans quelques heures le weekend va être tout trempé et morne.
Espérons que ces prévisions là soient toute pourries :-)

Cadeau bonux
Pour ceux qui sont tentés, voici quelques pictogrammes pour agrémenter vos présentations. Vous en trouverez sans doute de bien mieux ailleurs, mais là vous n'avez pas à chercher ;-)