XHTML content-type

Bas Ketsman, 23 december 2007 / 26 juli 2011

XHTML mag dan aan een opmars bezig zijn, de interpretatie van XHTML code laat nog steeds te wensen over. Vooral IE blijft – zoals altijd – een grote dwarsligger. In deze blog-post leg ik uit hoe ik hier voor The site to B mee ben omgegaan.

Dit artikel is een gerestaureerde versie van een oud artikel op The site to B.

Het juiste content-type

Iedere pagina die met het HTTP-protocol wordt verzonden bevat naast data ook meta-data (data over data). Een van de mogelijke meta-velden, het content-type veld, bevat een omschrijving van het media-type van de data. Met andere woorden: informatie over de codetaal waarin de data is geschreven waaruit kan worden afgeleid hoe de data moet worden geïnterpreteerd.

Een HTML-bestand hoort bijvoorbeeld te worden geassocieerd met het content-type text/html, terwijl een XHTML-bestand het application/xhtml+xml (XHTML spcifiek) of application/xml (XML in het algemeen) content-type hoort te bevatten.

In een utopische wereld is het simpel. Een XHTML document moet altijd een content-type hebben dat aangeeft dat het om een XHTML document gaat. In het XHTML bestand zelf kan het content-type worden aangegeven met een HTTP-equivalentie header, dit gebeurd door onderstaand element in het head-gedeelte van de XHTML pagina te plaatsen.

<meta http-equiv="content-type" content="application/xhtml+xml;charset=utf-8" />

Belangrijker is echter dat ook het HTTP-content-type zelf consistent wordt ingevuld. In het geval van dynamische inhoud kan hiervoor gebruik worden gemaakt van de een server-side functie zoals header in PHP. Als het om statische bestanden gaat, kunnen we deze header vaak ook afdwingen met behulp van de .xhtml extensie, dus pagina.xhtml en niet pagina.html.

header("Content-Type: application/xhtml+xml; charset=utf-8");

Het probleem

Waar we nu geen rekening mee hebben gehouden is het feit dat niet iedere browser (lees IE) XHTML ondersteund. Wanneer we een XHTML-pagina met het juiste content-type naar IE verzenden, wordt dit document niet herkend en bijgevolg als download aangeboden (zie afbeelding).

Het foutieve content-type

Om browser problemen te voorkomen, wordt er vaak voor geopteerd om XHTML-bestanden te verzenden met een text/html media type en dus eigenlijk te doen alsof het om een HTML bestand gaat. Hoewel het ‘visuele’ probleem zo inderdaad vaak kan worden opgelost, kunnen we hiermee niet echt over een fundamentele oplossing spreken.

Door een XHTML-bestand te verzenden met het text/html content-type, doen we alsof onze pagina in HTML is geschreven. Hoewel XHTML meestal kan worden gezien als een striktere versie van HTML en we hier meestal dus niet echt over een conflict tussen het ‘meta’ content-type en het ‘werkelijke’ content-type kunnen spreken, gaan hiermee wel alle voordelen die XHTML bied verloren. HTML is flexibeler dan XHTML en kan bijgevolg minder efficiënt worden geparst. De mogelijkheid om de XHTML woordenschat uit te breiden wordt onbruikbaar.

Content-negotiation

Een tussenoplossing waarbij we browsers die XHTML ondersteunen voorzien van een XHTML document en browsers die geen XHTML ondersteunen met een HTML document kan worden bekomen via content-negotiation. Omdat HTML en XHTML documenten sterk op elkaar lijken is het vaak niet nodig twee totaal verschillende documenten op te stellen en kunnen we een XHTML document mits kleine wijzigingen in bepaalde gevallen aanbieden als HTML.

Wanneer een browser aan een server een aanvraag voor een bepaalde webpagina doet, wordt zo een aanvraag meestal voorzien van meta-data.

Headers die firefox mee verstuurd tijdens het aanspreken van www.google.com
---
GET / HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; nl; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11
Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, */*;q=0.5
Accept-Language: nl, en-us;q=0.7, en;q=0.3
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1, utf-8;q=0.7, *;q=0.7
Keep-Alive: 300
Connection: keep-alive

Zoals je kan zien wordt hier ook een accept-header, met daarin verschillende media-types die door de betreffende browser worden ondersteund meeverzonden.

Met PHP (of andere server-side talen) kunnen we deze headers opvragen en aan de hand hiervan bepalen onder welk content-type we het document verzenden.

$accept = (isset($_SERVER['HTTP_ACCEPT'])? $_SERVER['HTTP_ACCEPT'] : false);

Met $_SERVER['HTTP_ACCEPT'] worden de media-types die door de browser worden ondersteund opgevraagd. Omdat een browser niet altijd een accept-header meeverzendt, moet we er tevens rekening mee houden dat deze eventueel niet bestaat.

if ($accept) {
    $acceptXHTML = stristr($accept, "application/xhtml+xml");
    ...
}

Hier wordt nagegaan of de accept-header aanwezig is en indien ja, of XHTML wordt ondersteund. stristr is een hoofdletter ongevoelige functie om na te gaan of de tweede string-parameter in de eerste string-parameter voorkomt. stristr geeft de deelstring terug vanaf het eerste voorkomen, of false indien de tweede string niet in de eerste voorkomt [PHP].

if ($acceptXHTML) {
    header("Content-Type: application/xhtml+xml; charset=utf-8");
} else {
    header("Content-Type: text/html; charset=utf-8");
}
    header("Vary: Accept");

Wanneer we zeker zijn dat de browser XHTML ondersteund, wordt het juiste content-type verzonden. In alle andere gevallen zal onze XHTML-code als HTML worden geïnterpreteerd. Om aan te geven dat er bij de interpretatie van de request niet alleen wordt gekeken naar de URI zelf, maar ook naar de inhoud van het Accept-veld, wordt een Vary-header mee verzonden. Dit heeft vooral invloed op de interpretatie van het antwoordbericht door caches.

De volledige code:

$accept = (isset($_SERVER['HTTP_ACCEPT'])? $_SERVER['HTTP_ACCEPT'] : false);
if ($accept) {
    $acceptXHTML = stristr($accept, "application/xhtml+xml");
    if ($acceptXHTML) {
        header("Content-Type: application/xhtml+xml; charset=utf-8");
    } else {
        header("Content-Type: text/html; charset=utf-8");
    }
}

Andere verschillen

In de veronderstelling dat de XHTML code HTML compatibel is, zijn er slechts een paar items waarmee nog rekening moet worden gehouden in het antwoordbericht.

Het is belangrijk dat de inhoud van de content-type meta-tag in de code zelf een weerspiegeling is van de content-type in het HTTP-antwoord bericht. Het is bijgevolg aangewezen ook de inhoud van deze meta-tag te koppelen aan het script dat hierboven werd toegelicht. <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" /> in het geval van XHTML en <meta http-equiv="content-type" content="text/html; charset=utf-8" /> in het geval van HTML.

Een HTML document hoort niet te worden voorafgegaan door <?xml version="1.0"?>, voor een XHTML document mag dit wel worden aangegeven.

Een HTML en XHTML document dient te worden voorafgegaan door een correcte document-type vermelding. Deze zijn vanzelfsprekend verschillend voor HTML en XHTML.

Uitebreiding

De W3C XHTML-validator is één van de gekende gevallen die geen accept-header verzendt, maar waarvan we weten dat het correcte XHTML-content-type wel wordt ondersteund. Voor dit soort gevallen kunnen we de code lichtjes wijzigen. In onderstaande aangepast code wordt de user-agent-header gecontroleerd op het al dan niet aanwezig zijn van de naam “W3C_validator”. Wanneer dit het geval is wordt eveneens het juiste content-type verzonden.

$accept = (isset($_SERVER['HTTP_ACCEPT'])? $_SERVER['HTTP_ACCEPT'] : false);
$userAgent = (isset($_SERVER['HTTP_USER_AGENT'])? $_SERVER['HTTP_USER_AGENT'] : false); 

$acceptXHTML = false;
if ($accept) {
    $acceptXHTML = stristr($accept, "application/xhtml+xml");
}

if (!$acceptXHTML && $userAgent) {
    $acceptXHTML = stristr($userAgent, "W3C_Validator");
}

if ($acceptXHTML) {
    header("Content-Type: application/xhtml+xml; charset=utf-8");
} else {
    header("Content-Type: text/html; charset=utf-8");
}
header("Vary: *");

OPGELET wanneer een browser geen accept-header verstuurd, maar wel XHTML ondersteund, zal de code als HTML worden geïnterpreteerd.

OPGELET PHP-headers moeten steeds vóór de inhoud worden verzonden, anders hebben ze geen effect.

OPGELET Eigenlijk is dit allemaal nergens voor nodig, als iedere browser de W3C richtlijnen correct zou opvolgen...

De code gebruikt in dit artikel is gebaseerd op de code gegeven op [456bereastreet]. Gelijkaardige scripts voor ASP/ASP.NET kunnen hier ook worden gevonden.

Referenties

[PHP]
PHP Documentation Group. PHP: Hypertext Preprocessor. PHP Manual. url: www.php.net/.
[456bereastreet]
Roger Johansson. Content negotation. 456 Berea Street. 2004. url: www.456bereastreet.com/.

Meta-data

Korte omschrijving

Over het content-type application/xhtml+xml en hoe The site to B gebruik maakt van content-negotiation om user-agents die geen XHTML ondersteunen een HTML representatie aan te bieden.

Tags

xhtml, application/xhtml+xml, text/html, application/xml, content-type, headers, PHP, content negotiation, HTTP