La página del W3C sobre las ventajas de usar XHTML en lugar de HTML dice que si su documento es un XHTML 1.0 puro (sin incluir otros lenguajes de marcas) entonces no notará mucha diferencia. Sin embargo cuantas más herramientas XML estén disponibles, tales como XSLT para transformar documentos, empezará a darse cuenta de las ventajas de XHTML. XForms por ejemplo le permitirá editar documentos XHTML (o cualquier clase de documentos XML) en una forma simple y controlable. Las aplicaciones de la Web Semántica se beneficiarán más de los documentos XHTML. Si su documento es más que XHTML 1.0, por ejemplo incluyendo MathML, XMIL o SVG, entonces las ventajas son inmediatas: sería imposible hacer esas cosas con HTML. Esto es lo que dice el W3C, pero ¿cuáles son las diferencias entre HTML y XHTML?, ¿Porqué los XHTML son servidos como HTML?, ¿Todos los navegadores soportan XHTML?. Y la pregunta final ¿Qué debo aprender: HTML o XHTML?.

Diferencias con HTML 4

La especificación W3C XHTML-1.0 lo define como The Extensible Hyper Text Markup Language (Lenguaje de Marcas de Hipertexto Extensible), agregando el subtítulo A Reformulation of HTML 4 in XML 1.0 (una reformulación de HTML 4 en XML 1.0). En el apartado de diferencias con HTML 4 se exponen algunos ejemplos sobre la buena formación de los elementos:

DiferenciaCorrecto en XHTML 1.0Permitido en HTML 4
Los elementos deben tener etiquetas de cierre.<p>párrafo1</p><p>párrafo2</p><p>párrafo1<p>párrafo2
Los elementos deben estar bien anidados y no superpuestos.<p>párrafo <b>negrita</b></p><p>párrafo <b>negrita</p></b>
Elementos y atributos siempre en minúsculas.<p title="título">párrafo</p><P TITLE="título">párrafo</P>
Los valores de los atributos siempre van entrecomillados.<textarea rows="10" cols="20"...<textarea rows=10 cols=20...
No se permiten atributos minimizados, es decir omitir el valor del atributo cuando este tiene un único valor igual al nombre.<input type="checkbox" checked="checked" /><input type="checkbox" checked />
Elementos vacíos correctamente terminados. Para compatibilidad con documentos XHTML servidos como HTML conviene poner un espacio antes de la barra de terminación.<br /><br>
El navegador (agente de usuario) deberá colapsar espacios en los valores de los atributos.Más de un espacio, tabulaciones o saltos de línea se colapsan en un espacio.No se colapsan espacios.
Secciones CDATA para los elementos de Script.<script><![CDATA[...]]></script><script>...</script>
Exclusiones SGML: En un DTD de HTML-4 pueden excluirse elementos de ser anidados dentro de otros. Por ejemplo
<!ELEMENT A - - (%inline;)* -(A)    -- anchor -->
define el elemento vínculo en la especificación HTML-4.01, excluyendo expresamente de anidar otros elementos vínculo en su interior. Esta característica no es siempre posible en XML y por lo tanto cuando no sea posible definir las exclusiones en el DTD de un XML, debe tenerse en cuenta la lista de elementos con exclusiones para XHTML de la especificación XHTML-1.0.
Los atributos identificadores id deben ser únicos por cada documento. En HTML 4 tenemos el atributo name, que también se permite en XHTML 1.0, pero su diferencia es que puede haber más de uno con el mismo valor en un documento. Para compatibilidad con documentos XHTML que son servidos como HTML, puede incluirse ambos <p id="xx" name="xx">.... Por ejemplo, para recibir los controles de un formulario al ser enviado necesitamos name si el documento XHTML se sirve como HTML.

Documentos XHTML servidos como HTML

En la tabla anterior aparece este término: documento XHTML servido como HTML. Un documento escrito en XHTML debería tener la siguiente estructura (ver mi glosario XHTML+CSS elementos de cabecera para más información):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>XHTML 1.0 como HTML</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        ...
    </head>
    <body>
        ...
    </body>
</html>

Se observa inicialmente el elemento <?xml?> que indica que este documento es un XML-1.0 con codificación UTF-8. Luego el <!DOCTYPE> indica que se trata de un XHTML-1.0 Transitional. En principio parecería que los navegadores deberían observar estos dos elementos iniciales y manejar el contenido en consecuencia. Pero la cosa no es tan simple.

Un agente de usuario (User agent en inglés) es cualquier dispositivo capaz de manejar un documento, como un navegador o un robot de búsquedas. Cuando un servidor (Apache en mi caso) envía un documento remite también unas cabeceras previas para informar al agente de usuario acerca de las características del documento que va a recibir. Veamos algunos ejemplos:

  • html.html, un documento con estructura HTML-4.01 con la extensión html, para servir como HTML.
  • xhtml.html, un documento con estructura XHTML-1.0 pero con extensión html por lo que deberá ser servido como HTML.
  • xhtml.xhtml, un documento también con estructura XHTML-1.0 pero con extensión xhtml, por lo que deberá ser servido como XHTML.

Si intentamos abrir estos documentos con Internet Explorer 8, no tendremos problemas con los dos primeros. Pero con el último este navegador lo tratará como un archivo que no puede abrir, dando la opción de guardar o descargar. ¿Porqué sucede esto?. Intentamos lo mismo con Mozilla Firefox 3.6.13 y sí conseguimos abrir el documento de extensión ".xhtml". La información de Firefox de la página obtenida para los tres ejemplos es la siguiente:

Mozilla Firefox

El servidor detectó la extensión ".html" y aplicó un tipo de documento "text/html", tipo que recibe el navegador y usa para presentarlo.

Mozilla Firefox

También aquí el servidor detectó la extensión ".html" y aplicó el mismo tipo de documento "text/html", tipo que recibe el navegador y usa para presentarlo independientemente de que el contenido del documento este escrito en XHTML.

Mozilla Firefox

Ahora el servidor detectó la extensión ".xhtml" y aplicó un tipo de documento "application/xhtml+xml", de tal forma que el navegador maneja el documento como un verdadero XHTML, es decir, un XML.

Usando mi aplicación Telnet-php para experimentar con peticiones y respuestas a mi servidor localhost, hice una petición para el documento con extensión ".xhtml".

GET /articulos/html-vs-xhtml/ejemplos/xhtml.xhtml HTTP/1.1
Host: localhost
Connection: Close

obteniendo la siguiente respuesta desde el servidor localhost:

HTTP/1.1 200 OK
Date: Sat, 12 Feb 2011 22:38:05 GMT
Server: Apache/2.2.15 (Win32) PHP/5.2.13
Last-Modified: Sat, 12 Feb 2011 22:13:26 GMT
ETag: "8d00000002f282-479-49c1d1fb57354"
Accept-Ranges: bytes
Content-Length: 1145
Connection: close
Content-Type: application/xhtml+xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>XHTML 1.0 como XHTML</title>
    <meta http-equiv="X-UA-Compatible" content="IE=8"/>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <meta name="author" content="Andrés de la Paz" />
    <meta name="copyright" content="© 2011" />
    <link rel="icon" href="/icon.ico" type="image/x-icon" />
    <link rel="shortcut icon" href="/icon.ico" type="image/x-icon"  />
    <style type="text/css">
        code, pre {
            color: maroon;
        }
    </style>
</head>
<body>
    ...

Se observa el tipo de contenido application/xhtml+xml que se muestra en la cabecera HTTP (las previas al documento), mientras que para los documentos de extensiones ".html" el tipo de contenido de la cabecera devuelta por el servidor es Content-Type: text/html. Son estas cabeceras HTTP las que detectan los navegadores para manejar el documento.

¿Todos los navegadores soportan XHTML?

Internet Explorer 8.0 no acepta los documentos XHTML con el tipo application/xhtml+xml o application/xml. En cambio los otros navegadores consultados Firefox 3.6.13, Opera 11.01, Safari 5.0.3 y Chrome 9.0 si aceptan estos tipos. Parece ser que la versión próxima de Internet Explorer 9.0 ya si los manejarán, tal como se ve en la página de Internet Explorer XHTML in IE9.

Una forma de saber si un navegador soporta application/xhtml+xml es usar PHP para analizar las cabeceras enviadas por el navegador en la petición. El ejemplo aceptan-xhtml.php abre un documento escrito como XHTML pero que se sirve con text/html, con application/xhtml+xml o con application/xml según lo seleccionado en un formulario y también que el navegador soporte o no este tipo. Hemos puesto también el modo directo application/xml pues en esencia un XHTML es un XML. Basándonos en esta información recibida podemos en el servidor enviar una cabecera header con el tipo adecuado. Si el tipo recibido desde el formulario no es aceptado por el navegador, entonces se sirve como text/html. El código de este ejemplo no es muy complejo y se expone a continuación:

<?php
$tipo =  "text/html";
$tipo_acepta = "text/html";
if (isset($_GET["tipo"])) $tipo = htmlspecialchars($_GET["tipo"]);
if (($tipo == "text/html") || ($tipo == "application/xhtml+xml") ||
   ($tipo == "application/xml")){
    if ((isset($_SERVER["HTTP_ACCEPT"])) &&
         (stristr($_SERVER["HTTP_ACCEPT"], $tipo))) $tipo_acepta = $tipo;
}
header("Content-type: ".$tipo_acepta);
echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    ...
</head>
<body>
    ...
    <h2>Documento XHTML 1.0 con extension ".php", servido como
    <i style="color: green"><?php echo $tipo_acepta; ?></i></h2>
    <p>Formulario para seleccionar el tipo MIME a servir:</p>
    <form action="" style="border: gray solid 1px">
        "text/html" <input type="radio" name="tipo"
        value="text/html"
        <?php if ($tipo == "text/html")
            echo ' checked="checked" '?>
        />
        "application/xhtml+xml" <input type="radio" name="tipo"
        value="application/xhtml+xml"
        <?php if ($tipo == "application/xhtml+xml")
            echo ' checked="checked" '?>
        />
        "application/xml" <input type="radio" name="tipo"
        value="application/xml"
        <?php if ($tipo == "application/xml")
            echo ' checked="checked" '?>
        /><br />
        <input type="submit" value="enviar" /><br />
        <a href="aceptan-xhtml.php">Reiniciar</a>(Para
        Firefox tras recibir con "application/xml")
    </form>
    <p>Cabeceras enviadas por el navegador al servidor cuando
    solicitó la página actual:</p>
    <ul>
        <li>HTTP_USER_AGENT: <code><?php
        if (isset($_SERVER["HTTP_USER_AGENT"]))
        echo $_SERVER["HTTP_USER_AGENT"]; ?></code></li>
        <li>HTTP_ACCEPT: <code><?php if (isset($_SERVER["HTTP_ACCEPT"]))
        echo $_SERVER["HTTP_ACCEPT"]; ?></code></li>
    </ul>
    ...

Llamando a esta página desde Internet Explorer 8.0 veremos que se sirve como text/html sea cual sea la opción de tipo seleccionada, mientras que para los otros navegadores lo hace respetando la opción seleccionada, es decir, que pueden manejar application/xhtml+xml y application/xml. Aunque hemos de aclarar que cuando envíamos este tipo directo XML, el formulario en Firefox (la versión 3.6.13) deja de funcionar y no vuelve a enviar nada. Por eso hemos dispuesto un vínculo (resaltado en amarillo en este código) que reinicia la página simplemente llamándola sin ningún parámetro en el GET.

Si usamos nuestro Telnet para enviar una petición a esta página aceptan-xhtml.php pero remitiéndole al servidor una cabecera accept: application/xhtml+xml, el servidor entenderá que el agente de usuario (el Telnet) puede manejar XHTML y, por tanto, la página será servida como XHTML. A la dirección hemos de agregar el parámetro application/xhtml+xml pero escapando los caracteres propios de URL:

GET /articulos/html-vs-xhtml/ejemplos/aceptan-xhtml.php?
    tipo=application%2Fxhtml%2Bxml HTTP/1.1
Host: localhost
accept: application/xhtml+xml
Connection: Close

Obtenemos la siguiente respuesta, donde se observa en el resalte verde que el servidor recogió y sirvió con el tipo seleccionado:

HTTP/1.1 200 OK
Date: Sun, 13 Feb 2011 19:34:53 GMT
Server: Apache/2.2.15 (Win32) PHP/5.2.13
X-Powered-By: PHP/5.2.13
Content-Length: 2561
Connection: close
Content-Type: application/xhtml+xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    ...
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    ...
</head>
<body>
    ...
    <h2>Documento XHTML 1.0 con extension ".php", servido como
    <i style="color: green">application/xhtml+xml</i></h2>
    <p>Formulario para seleccionar el tipo MIME a servir:</p>
    <form action="" style="border: gray solid 1px">
        "text/html" <input type="radio" name="tipo"
        value="text/html" />
        "application/xhtml+xml" <input type="radio" name="tipo"
        value="application/xhtml+xml" checked="checked" />
        ...

Por supuesto este Telnet no hace nada con lo recibido, por lo que le da igual el tipo, pero es importante observar que tanto el servidor como el navegador parecen basarse en las cabeceras HTTP (las previas al documento) para servir y manejar el tipo. Ni siquiera tiene en cuenta lo que pueda indicar el propio documento, como la cabecera <meta> o el elemento <?xml?> resaltados en azul (en lo que se refiere a la diferenciación entre HTML y XHTML+XML). Esos elementos también declaran (o puede deducirse de ellos) el tipo MIME del documento, aunque no tiene ninguna relación directa con las cabeceras HTTP. En el siguiente apartado comentaremos algo más sobre esto.

Configuraciones de tipos MIME en Apache y PHP

El servidor determina el tipo a servir principalmente a través de las extensiones de los documentos. Se establece un tipo por defecto que viene configurado en el archivo httpd.conf, que para el servidor Apache de mi localhost es el siguiente:

# DefaultType: the default MIME type the server will use for a document
# if it cannot otherwise determine one, such as from filename extensions.
# If your server contains mostly text or HTML documents, "text/plain" is
# a good value.  If most of your content is binary, such as applications
# or images, you may want to use "application/octet-stream" instead to
# keep browsers from trying to display binary files as though they are
# text.
DefaultType text/plain

El comentario traducido lo explica: DefaultType: El tipo MIME por defecto que el servidor usará para un documento si no puede determinar el tipo de otra forma, tal como de las extensiones. Si tu servidor tiene la mayoría de documentos de texto o HTML, "text/plain" es un buen valor. Si la mayoría son de contenido binario, tal como aplicaciones o imágenes, sería mejor usar "application/octet-stream" para que los navegadores no intenten presentar datos binarios como texto. También encontramos en ese archivo de configuraciones httpd.conf, en la parte donde se carga el módulo de tipos MIME mime_module:

LoadModule mime_module modules/mod_mime.so
...
<IfModule mime_module>
    # TypesConfig points to the file containing the list of mappings from
    # filename extension to MIME-type.
    TypesConfig conf/mime.types
    ...
</IfModule>

El archivo de tipos se encuentra en conf/mime.types. Se trata de una relación de tipos y extensiones de documentos. Por ejemplo, aparecen entre un montón de ellos los siguientes relacionados con lo que estamos viendo:

application/xhtml+xml       xhtml xht
application/xml             xml xsl
text/html                   html htm
text/plain                  txt text conf def list log in

Vemos que el tipo application/xhtml+xml se aplicará para servir documentos con las extensiones xhtml o xht. Para los archivos xml o xsl se usa el tipo application/xml. Las extensiones html o htm se sirven como text/html. El texto plano también tiene unas extensiones y además se aplicará por defecto cuando no se encuentra en la lista de este archivo de tipos.

En la repuesta del Telnet de más arriba resaltamos en azul una cabecera <meta> del documento:

<meta http-equiv="content-type" content="text/html; charset=UTF-8" />

Podemos pensar que el servidor podría usar esta cabecera más el elemento <?xml?> para detectar el tipo sin tener que usar el archivo de tipos MIME para encontrar ahí la extensión del documento. En el archivo de configuración httpd.conf aparece el módulo mime_magic_module para que el servidor determine el tipo MIME según el contenido del documento. En mi localhost venía desactivado con la instalación inicial:

#LoadModule mime_magic_module modules/mod_mime_magic.so
...
# The mod_mime_magic module allows the server to use various hints from the
# contents of the file itself to determine its type.  The MIMEMagicFile
# directive tells the module where the hint definitions are located.
#
#MIMEMagicFile conf/magic 

Analizando la parte del archivo conf/magic relacionada con HTML y XML vemos:

# html:  file(1) magic for HTML (HyperText Markup Language) docs
# from Daniel Quinlan <quinlan@yggdrasil.com>
# and Anna Shergold <anna@inext.co.uk>
0   string      \<!DOCTYPE\ HTML    text/html
0   string      \<!doctype\ html    text/html
0   string      \<HEAD              text/html
0   string      \<head              text/html
0   string      \<TITLE             text/html
0   string      \<title             text/html
0   string      \<html              text/html
0   string      \<HTML              text/html
0   string      \<!--               text/html
0   string      \<h1                text/html
0   string      \<H1                text/html
# XML eXtensible Markup Language, from Linus Walleij <triad@df.lth.se>
0   string      \<?xml              text/xml

Por lo tanto si la configuración MIMEMagicFile estuviera activada y el módulo mime_module fuera cargado, los XHTML serían detectados por su elemento <?xml?> asignándoles un tipo text/xml y no el application/xhtml+xml o el application/xml. En ninguna otra parte de este archivo conf/magic pude encontrar que detectara automáticamente un elemento <meta http-equiv="content-type".... Y aunque pudiera realizarse, confiar en el contenido para determinar el tipo puede ser muy arriesgado. Si un documento no está bien formado o no lleva ese <meta> y al final el servidor no puede determinar el tipo, servirá el documento como texto plano (o lo que por defecto se configure). Puede que sea más seguro basarse en las extensiones. Pero, ¿qué pasa cuando servimos documentos con extensión php?. Cuando se instala PHP después de haber instalado Apache, se agregan los siguientes tipos al archivo mime.types del httpd.conf:

application/x-httpd-php         php
application/x-httpd-php-source  phps

Esto se comprueba porqué estas entradas suelen estar al final del archivo, dado que PHP se instala después de haber instalado el Apache. En este caso es el módulo de PHP instalado en el servidor, que podemos ver en el archivo httpd.conf

#BEGIN PHP INSTALLER EDITS - REMOVE ONLY ON UNINSTALL
PHPIniDir "C:/php/"
LoadModule php5_module "C:/php/php5apache2_2.dll"
#END PHP INSTALLER EDITS - REMOVE ONLY ON UNINSTALL

es el que se encarga de convertir los tipos application/x-httpd-php de los documentos con extensión php a los que se especifiquen en el archivo de configuración php.ini del propio PHP. El de mi localhost tiene esta entrada:

; PHP's built-in default is text/html
; http://php.net/default-mimetype
default_mimetype = "text/html"

Por lo tanto todos los documentos con extensión php serán servidos como HTML con el tipo text/html si no se modifica esta configuración. Pero podemos modificar también el tipo servido usando el envío de cabeceras con header("Content-type: TIPO-MIME), como hicimos en el ejemplo anterior.

¿Servir XHTML o HTML?

Los documentos de este sitio www.wextensible.com son creados y construidos como XHTML, pero con extensión ".html" para que sean servidos como text/html. Principalmente se debe a la falta de los navegadores, sobre todo IE, para soportar la presentación de XHTML, aunque ya vemos que se van incorporando. Pero además hay otra razón muy importante que debemos ponderar. Cuando un navegador recibe un documento con tipo text/html intentará presentar el documento aunque contenga errores. Para ello tratará de arreglar o buscar alguna solución para que se presente el documento. Sin embargo con el tipo application/xhtml+xml esto no funciona así, pues si hay un error el navegador no debe ofrecer el documento, advirtiéndolo con un mensaje al usuario.

Hacemos un ejemplo de un documento escrito en XHTML html-con-errores.php, con el siguiente código:

<?php
$tipo =  "text/html";
$tipo_acepta = "text/html";
if (isset($_GET["tipo"])) $tipo = htmlspecialchars($_GET["tipo"]);
if (($tipo == "text/html") || ($tipo == "application/xhtml+xml")){
    if ((isset($_SERVER["HTTP_ACCEPT"])) &&
         (stristr($_SERVER["HTTP_ACCEPT"], $tipo)))
         $tipo_acepta = $tipo;
}
header("Content-type: ".$tipo_acepta);
echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    ...
</head>
<body>
    ...
    <h2>Documento XHTML 1.0 con errores, con extension ".php", servido como
    <i style="color: green"><?php echo $tipo_acepta; ?></i></h2>
    <p>Formulario para seleccionar el tipo MIME de este documento:</p>
    <form action="" style="border: gray solid 1px">
        Servir "text/html" <input type="radio" name="tipo" value="text/html"
        <?php if ($tipo == "text/html")
            echo ' checked="checked" '?>
        />
        Servir "application/xhtml+xml" <input type="radio" name="tipo"
        value="application/xhtml+xml"
        <?php if ($tipo == "application/xhtml+xml")
            echo ' checked="checked" '?>
        />
        <input type="submit" value="enviar" />
    </form>
    <p>Los elementos dentro de este contenedor de borde rojo a
    continuación están mal formados en XHTML:
    </p>
    <div style="border: red solid 1px;">
        <?php echo "<p style='color: green'>Este párrafo tiene un
        <b>elemento negrita</p> que no está bien anidado</b>"; ?>
    </div>
</body>
</html>

Como en el ejemplo de un apartado anterior, incorporamos un formulario con selección de tipo (ahora no ponemos el application/xml) que permitirán enviar al servidor el tipo para que le sirva el documento con ese MIME. Luego ponemos un conjunto de elementos mal formados en XHTML:

<p style='color: green'>Este párrafo tiene un
<b>elemento negrita</p> que no está bien anidado</b>

Se trata de elementos mal anidados, pues la etiqueta de cierre </p> debería estar donde ahora se sitúa la </b>. Esto lo escribimos con una orden echo para evitar que mi entorno de programación detecte el error.

Si abrimos este documento con Internet Explorer 8.0 veremos que presentará el documento de la mejor manera posible, tratando de solucionar el error, pero no advirtiendo de ello. Como este navegador no soporta el tipo application/xhtml+xml el resultado será el mismo para ambos tipos, algo como esto:

Error en XHTML

Los otros navegadores (Firefox 3.6.1, Opera 11.01, Safari 5.0.3 y Chrome 9.0) también presentan ese trozo así cuando se sirve text/html. Pero con el otro tipo detectarán el error y se detendrá la presentación, presentando mensajes como este de Firefox:

Error XHTML en Firefox

Aunque esta página se haga más pesada con más imágenes, creo conveniente ofrecer las de los otros navegadores para observar el distinto comportamiento ante el error en cada uno. En Opera:

Error XHTML en Opera

Este navegador da la posibilidad de procesar el documento de nuevo como HTML. Si lo hacemos soluciona de una mejor forma el error:

Opera corrige error de XHTML

Safari y Chrome usan lo mismo para procesar el XHTML, pues los mensajes son muy similares por lo que sólo presentamos aquí la imagen del Safari:

Error XHTML en Safari

Estos dos navegadores presentan como HTML, en la misma página que contiene el mensaje, el documento corregido como mejor pueden pero sólo hasta el elemento previo al error. El resto del documento lo omiten.

En definitiva un XHTML servido como XHTML no debería ser presentado si contiene errores. Esto es lo que hace Firefox, pero los demás se atreven a ofrecer alternativas que deberían considerarse no adecuadas. Si escribimos un XHTML y lo ponemos a disposición en un servidor es porque previamente habremos validado su conformidad con XML. Por lo tanto no debe contener errores. El hecho de que un navegador intente solucionarlo va en contra de la propia filosofía de un XML.

Diferencias en la presentación XHTML

Aunque los navegadores están empezando a aceptar XHTML, tenemos que ir con cuidado y observar como ejecutan cada uno la presentación. Por ejemplo, tomando la página de inicio de este sitio index.html y renombrándola a index.xhtml, el servidor la enviará como XHTML. En los navegadores Firefox, Safari y Chrome se presenta prácticamente como la servida en HTML. Pero en Opera se desplazan los marcos verticales laterales montándose encima de la barra de botones:

Opera con XHTML

Supongo que será un error del navegador, pues como dije, en los otros la página sale bien presentada. Si queremos forzar a que el servidor envíe todo como XHTML sin tener que cambiar las extensiones de todos los ".html" a ".xhtml", podemos añadir un tipo en el archivo de configuración del Apache httpd.conf, en la parte que volvemos a reproducir aquí:

LoadModule mime_module modules/mod_mime.so
...
<IfModule mime_module>
    # TypesConfig points to the file containing the list of mappings from
    # filename extension to MIME-type.
    TypesConfig conf/mime.types

    # AddType allows you to add to or override the MIME configuration
    # file specified in TypesConfig for specific file types.
    #
    #AddType application/x-gzip .tgz
    ...
</IfModule>

Observamos que con la directiva AddType podemos agregar o modificar un tipo de los incluidos en el archivo conf/mime.types. Como en ese archivo teníamos:

application/xhtml+xml       xhtml xht
application/xml             xml xsl
text/html                   html htm

entonces nos interesa que las extensiones .html se abran con application/xhtml+xml, para lo cual agregamos un AddType en la directiva de más arriba (no incluimos las extensiones .htm):

<IfModule mime_module>
    # TypesConfig points to the file containing the list of mappings from
    # filename extension to MIME-type.
    TypesConfig conf/mime.types

    # AddType allows you to add to or override the MIME configuration
    # file specified in TypesConfig for specific file types.
    #
    AddType application/xhtml+xml .xhtml .xht .html
    AddType text/html .htm
    ...
</IfModule>

Luego hay que reiniciar el servidor y borrar las cachés de los navegadores, pues en otro caso leen de las que tienen archivadas (servidas como HTML). Menos Explorer, los demás navegadores señalados abren las páginas servidas como XHTML en XHTML. Explorer se comportan de una forma que aún no puedo entender, pues en principio parece que ignora la cabecera de envío y presenta las páginas como si fueran HTML. Aunque arrancado la página de inicio del sitio en algunas ocasiones ofrece el diálogo de descarga, lo que indica que detecta XHTML, y otras veces la presenta sin más. Sin tener en cuenta Explorer, dediqué un rato a ver como se presentaban algunas páginas del sitio. Pude observar algunos errores como:

  • Opera detectó en una página el error class="boton"" en el atributo de un elemento, es decir, las comillas de cierre estaban duplicadas.
  • En otra página Firefox detectó el error //<![CDATA donde faltaba el corchete final, debiendo ser //<![CDATA[.

Estos errores aparentemente insignificantes no son ni siquiera detectados por mi entorno de programación, pero son suficientes para que los navegadores no muestren el documento XHTML. Por lo tanto si decidimos servir XHTML hemos de revisar que los documentos se presentan bien en los principales navegadores.

Si estamos en un alojamiento compartido quizás no tengamos acceso al archivo de configuración del Apache httpd.conf. En ese caso podemos usar archivos .htaccess. Pero para poder insertar estos archivos necesitamos que la directiva AllowOverride tenga un valor distinto de None, el valor que inicialmente viene puesto por defecto. Y esto a su vez debe ser configurado en el httpd.conf:

<Directory ".../Apache server/htdocs">
    ...
    # AllowOverride controls what directives may be placed in .htaccess files.
    # It can be "All", "None", or any combination of the keywords:
    #   Options FileInfo AuthConfig Limit
    #
    #AllowOverride None
    AllowOverride All
    ...
</Directory>

Esta directiva la modificará el administrador del servidor si nosotros no tenemos acceso, seguramente en el archivo extra/httpd-vhosts.conf que es donde se configuran los dominios virtuales (Ver alojamiento compartido en Apache).

Una vez que tengamos la posibilidad de sobreescribir directivas en un archivo .htaccess, simplemente creamos un archivo de texto con ese nombre y ponemos las directivas para sobreescribir los tipos:

AddType application/xhtml+xml .xhtml .xht .html
AddType text/html .htm

Si ubicamos este archivo en la carpeta raíz del sitio, se aplicará a todos los documentos de esa carpeta y todas sus hijas. También podemos limitarlo a una carpeta. En este caso, y esto sí esta puesto en línea en este servidor real, lo he puesto en una carpeta bajo este artículo, carpeta que contiene los mismos tres documentos que vimos en el primer apartado:

Ahora estos documentos se sirven como XHTML, por lo que se acompaña una cabecera Content-type: application/xhtml+xml para todos, incluso el primero que es no es un XHTML. Por lo tanto deberán ser tratados por el navegador como XHTML independientemente de como fueron escritos. Con los dos primeros ejemplos anteriores que tienen extensión ".html", Internet Explorer ignora la cabecera application/xhtml+xml y presenta esas páginas como si fueran HTML. No así con la tercera que tiene extensión ".xhtml" enviándose también como XHTML, que ofrece el diálogo de descarga. Sin embargo los otros exploradores se basan única y exclusivamente en la cabecera enviada, abriéndolos como XHTML, aunque con el primero html.html se encontrarán con un problema como veremos a en el siguiente apartado.

Como simple curiosidad que vuelve a confirmar que los navegadores sólo hacen caso a la cabecera enviada por el servidor y no al contenido del documento, tenemos este ejemplo:

Hice un archivo de texto con el Notepad, lo guardé y luego le cambié la extensión a una ficticia ".estexto". Agregué esta falsa extensión a la directiva de Apache del anterior archivo .htaccess

AddType application/xhtml+xml .xhtml .xht .html .estexto

forzando al servidor para que lo sirva como XHTML. Los navegadores intentan abrirlo como XHTML (a excepción de lo dicho para Explorer 8) detectando el error de documento XML mal formado, de lo cual hablaremos a continuación.

XHTML: Documentos estrictamente conformes

El primer ejemplo anterior html.html es un documento escrito como HTML-4.01 y servido con la cabecera application/xhtml+xml para que el navegador lo abra como un XHTML. Pero así lo abre Firefox:

Firefox error XML
Para este documento html.html, aparte de Explorer que lo presenta como HTML, Opera y Firefox lo presentan como árbol de nodos, mientras que Safari y Chrome presentan sólo el contenido de texto de los nodos, sin el árbol, pero es así como tratan los XML.

Un documento XHTML debe ser reconocido por el navegador en base al elemento <!DOCTYPE> como se expone en el apartado 3.1.1 Strictly Conforming Documents (Documentos Estrictamente Conformes) de la especificación XHTML-1.0. Además debe tener el espacio de nombres xmlns="http://www.w3.org/1999/xhtml", que en este ejemplo no se declara. Por lo tanto el navegador no puede considerarlo un XHTML, ni mucho menos un HTML. En todo caso sólo deberá presentarlo como un XML. Y así lo hacen todos menos Explorer.

Por lo tanto ese ejemplo html.html es un HTML-4.01 servido con la cabecera application/xhtml+xml y presentado por el navegador como un XML. En cuanto al mensaje Este fichero XML no parece tener ninguna información de estilo asociada... que exponen Firefox y Opera, su razón es informar al usuario de que el XML no tiene estilo y se presenta como un árbol de nodos (Safari y Chrome presentan el texto interior de los nodos). Para entender esto es conveniente ver en mi glosario XHTML+CSS el tema sobre estilo CSS para documentos XML, donde desarrollo un ejemplo de un XML sin CSS y otro XML con CSS. Observará que el que está sin CSS se presenta en Firefox como el árbol de nodos de más arriba, con el mismo mensaje de ausencia de información de estilo.

Siguiendo el ejemplo anterior, hemos agregado la declaración de estilo <?xml-stylesheet type="text/css" href="css-html401.css"?> en el inicio de una copia del anterior documento html.html. Como hoja de estilo tomamos la que aconsejan en Appendix D. Default style sheet for HTML 4 de la especificación CSS-2.1, cuyo contenido hemos copiado para crear el nuevo archivo de estilo css-html401.css. De esta forma ahora podemos abrir html-con-css.html con un estilo "similar" al HTML-4.01. Decimos similar pues esa hoja de estilo no puede representar elementos propios de HTML, como <img>, <form> o <script>. Por el mismo motivo tampoco funcionan los elementos <link> que vinculan estilo externo o el propio estilo interno de <style>.

Porqué usar XHTML para escribir los documentos

Después de todo lo anterior es posible pensar que, por ahora, es mejor seguir sirviendo los documentos como HTML. Entonces ¿porqué escribir en XHTML si luego las páginas se van a servir como HTML?. El objetivo final de XHTML es separar completamente la estructura del contenido, es decir, la información de la forma de presentarla. Es lo que se denomina llegar a crear una Web semántica, agregando metadatos (información sobre la información) que describan el contenido, su significado y su relación con otros datos (ver Wikipedia sobre la web semántica). Y aunque XHTML-1.0 es únicamente la reformulación de HTML-4.01 en XML, es realmente XML el primer paso para crear una Web semántica.

Con relación a algunas de las ventajas de XHTML, he de reconocer que no tengo mucha experiencia. Alguna vez he usado XSLT para la transformación de XML, pero no se nada acerca de los XForms o los módulos MathML, SMIL o SVG. En definitiva, me queda mucho por aprender, pero por ahora todas esas ventajas potenciales no me justifican escribir en XHTML en este momento. En XML Related Technologies de W3Schools puede ver esas y otras herramientas y tecnologías relacionadas con XML.

Escribir en XHTML sin errores requiere usar un entorno de programación que vaya indicando los errores a medidas que escribimos el código. Necesitamos saber más acerca de los elementos y tener una base XML. Hay que saber como funciona un DTD e incluso llegar a leerlos y comprenderlos al menos un poco. En cambio para escribir HTML sólo es necesario un editor de texto, pues el navegador se encargará de arreglar los errores. Por ejemplo, un elemento <p> no puede contener un <div> en XHTML-1.0. Pero tampoco HTML-4.01 lo permite, tal como vemos en su DTD en la definición de este elemento <P> en la especificación HTML-4.01:

<!ELEMENT P - O (%inline;)*  -- paragraph -->

Especificando claramente It cannot contain block-level elements (including P itself) (No pueden contener elementos a nivel de bloque, incluso el propio P). Pero con los documentos servidos como HTML esto no tiene mayor importancia: los navegadores lo presentan sin más. Incluso los incluyen dentro del DOM. Esto es así porque HTML no tiene obligación de conformar el documento con su DTD. Por lo tanto con HTML las cosas aparentan más fáciles.

Además se suponía que XHTML sustituiría a HTML. Esta era la perspectiva inicial del W3C, aunque al final han tenido que ceder. Ahora ya viene HTML-5 manteniendo la idea inicial de que el navegador puede permitir errores de sintaxis y presentar el documento lo mejor que pueda. Aunque también habrá una XHTML del HTML-5 y, por otro lado, también se trabaja en un nuevo XTHML-2.0

¿Aún hay razones para seguir escribiendo en XHTML?. Como ya dije, no soy un experto en XML, pero puedo aportar una razón que nace de mi corta experiencia con XHTML. Y es que escribir en XHTML obliga como dije a conocer XML, lo que puede dar una perspectiva diferente de la estructura de un documento. Así una página es algo más que información presentada en un monitor. Es también un árbol de nodos que puede recorrerse con métodos y propiedades del DOM. Vea el ejemplo anidados.php que expone como puede afectar un mal anidamiento de elementos en la selección de los mismos mediante DOM en Internet Explorer 8.

Si observa ese ejemplo verá que XHTML y un entorno de programación adecuado nos hace escribir "correctamente", no adquiriendo hábitos que se apoyan en que sea el navegador el que corriga los errores. Aunque luego los XHTML se sirvan como HTML, serán documentos más resistentes, mejor estructurados y potencialmente preparados para hacer otras cosas con ellos. Por eso creo que conviene aprender XML+XHTML, pues ya lleva implícito HTML. Hacerlo al revés supone mayor esfuerzo y puede que nos perdamos algunas ventajas futuras. E incluso Internet Explorer 9 aceptará los XHTML.