Back home and almost caught up on email…
One of the many things on my to-do list for Grand Rapids WiFi is to switch from the current system of having separate URLs for the RDF representations of resources to something based on content negotiation.
In order to do that using PHP I needed a couple of functions to extract the relevant headers. A quick web search didn’t turn up any appropriate libraries, so I threw together a couple of functions that should cover everything. For those not familiar with content-negotiation it works by having a user agent (web browser, newsreader, etc.) add an “Accept:” header specifying what types of content it can manage and which it prefers. For example, Firefox might send something like:
Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, image/jpeg, image/gif;q=0.2, */*;q=0.1
The ‘q’ value is a measure of preference, and where none is specified we can assume a value of 1.0. So this browser prefers XML/XHTML over plain HTML, and HTML over plain text, but will accept anything (*/*).
So without more ado, the code:
/*
* To be used with array_map. For a given content-type string
* from an Accept: header this returns an array with the
* content-type name and the preference level associated with
* it.
*
* @author James Stewart <james@jystewart.net>
* @version 0.1
* @license http://creativecommons.org/licenses/by-sa/2.0/ Creative Commons Attribution ShareALike 2.0
* @param string $type
* @return array types with associated weighting
*/
function getValue($type)
{
if (strstr($type, ";q=")) {
$values = explode(";q=", $type);
} else {
$values = array($type, 1.0);
}
return $values;
}
/*
* Given an HTTP Accept: header and an array of options, this function
* returns an array of the overlap, ordered by the level of preference
* sorted in order of descending preference.
*
* @author James Stewart <james@jystewart.net>
* @version 0.1
* @license http://creativecommons.org/licenses/by-sa/2.0/ Creative Commons Attribution ShareALike 2.0
* @param string $header
* @param array $options
*/
function parseAccept($header, $options)
{
/* Explode types into an array */
$types = explode(",", $header);
$types = array_map("getValue", $types);
/* Select only the requested options and sort by preference */
$accepted = array();
$accepted_type = array();
$accepted_val = array();
foreach ($types as $type) {
if (in_array($type[0], $options)) {
array_push($accepted, $type);
array_push($accepted_type, $type[0]);
array_push($accepted_val, $type[1]);
}
}
array_multisort($accepted_val, SORT_DESC, $accepted_type, SORT_ASC, $accepted);
return $accepted;
}
Update (03/09/07): Removed a space in the explode call. See comments for more details.

It appears that the code doesnt work. Amy I doing something wrong?
$options = array(”text/html”);
$prefs = parseAccept($_SERVER["HTTP_ACCEPT"], $options);
var_dump($prefs);
Comment by Ben Davies — 3 April 2006 @ 1:48 pm
Have you checked that the header is definitely not empty?
Is the array returning any content?
If the array is empty, it may be that the browser is simply sending a header of */* meaning it will accept anything in any order. I tend to use it with:
$options = array(’application/xhtml+xml’, ‘text/html’, ‘xml/rdf+xml’, ‘application/rdf+xml’)
$prefs = parseAccept($_SERVER['HTTP_ACCEPT'], $options);
switch ($prefs[0][0])
{
case ‘application/xhtml+xml’:
// xhtml specific handling
break;
case ‘xml/rdf+xml’:
case ‘application/rdf+xml’:
// rdf specific handling
break;
default:
// anything else
break;
}
Comment by James Stewart — 11 April 2006 @ 6:40 am
The Array was returning empty (I’m using FireFox 1.5.0.1 and PHP 5.1.2).
I’ll have another look again tonight and see if I can identify the problem.
Comment by Ben Davies — 11 April 2006 @ 8:02 am
Yup, its working fine now (I left the options array unpopulated) Duh!
Comment by Ben Davies — 15 April 2006 @ 8:32 am
Hi,
Just came back to your site (after a year!) and tried your code again, and noticed a small bug that casued the same symptoms discussed in the previous comments. The bug is this line:
/* Explode types into an array */
$types = explode(”, “, $header);
There is a space after the comma in the delimiter. It should be:
$types = explode(”,”, $header);
That fixes the problem and the code runs perfectly!
Cheers!
Comment by Ben Davies — 31 August 2007 @ 5:36 am