Desarrollo de aplicaciones web
Jeisson Hidalgo-Céspedes
Versión 0.2.8. 2011-Dic-13

Tabla de contenidos

Introducción a la tecnología web

De todas las aplicaciones que se han construido sobre Internet, la World Wide Web (WWW o simplemente web) ha sido la más popular, tanto que, muchas personas cuando escuchan el término "Internet" realmente imaginan la web. La web puede definirse como un sistema de documentos de hipertexto interrelacionados entre sí y accesibles a través de Internet. Se dice que son documentos de hipertexto o hipermedios, por su capacidad de contener diferentes medios de comunicación: texto, imágenes, sonido, vídeo, enlaces, etc.

La popularidad de la web puede adjudicarse a su facilidad de uso y por ser el más exitoso de los sistemas distribuidos en la actualidad. Los documentos se almacenan en computadoras distintas, con sistemas operativos diversos, generados de fuentes heterogéneas, pero esto es transparente para el usuario. De hecho, para el usuario todos los documentos del mundo parecen ser parte de un único sistema, lo cual es fundamento de lo que se define como un sistema distribuido.

Historia

La web fue conceptualizada en un artículo de 1989 de Tim Berners-Lee, quien se convertiría en uno de sus grandes líderes. A finales de 1990, Berners-Lee desarrolló el Protocolo de transferencia de hipertexto (HTTP, HyperText Transfer Protocol), el primer servidor web llamado CERN-httpd; el primer navegador llamado WorldWideWeb, que también era un editor web; el lenguaje de marcado de hipertexto (HTML, HyperText Markup Language); y las primeras páginas web que hablaban sobre el proyecto mismo.

La aparición pública de la web fue en 1991, cuando Berners-Lee describió el proyecto en un grupo de noticias (Usenet newsgroup). Había nacido un mecanismo eficiente que permite a cualquiera ser autor, compartir material de interés al mundo, y hacer referencia al existente a través de hiper-enlaces.

La difusión de la web fue lenta. Inicialmente fue adoptada por universidades y grupos científicos. Los documentos web eran muy sencillos y la mayoría de navegadores corrían en modo texto, unos pocos eran gráficos como el escrito por Berners-Lee en una NeXT. Desde 1992 muchos navegadores se construyeron, pero el más influyente de todos fue Mosaic.

En 1993 el National Center for Supercomputing Applications (NCSA) de la University of Illinois en Urbana-Champaign (UIUC), introdujo el navegador gráfico Mosaic, como un proyecto de investigación. NCSA licitaba su código fuente a otras compañías para que crearan sus propios navegadores, incluso comerciales. Tras de graduarse, el líder del proyecto Mosaic, Marc Andreessen, fundaría la compañía Netscape en 1994, para comercializar su navegador Netscape Navigator, el cual corría transparentemente entre diversos sistemas operativos. Era gratis para uso no comercial, por lo que se convirtió en el navegador más usado del mundo.

En 1994 Berners-Lee funda el World Wide Web Consortium (W3C) en el Instituto Tecnológico de Massachusetts (MIT, Massachusetts Institute of Technology), apoyado por varias instituciones y empresas con el fin de crear estándares y recomendaciones para mejorar la calidad de la web.

La primera guerra de navegadores

En 1995 Microsoft incluye Internet Explorer 1.0 en Windows 95, basado en código fuente de NCSA Mosaic. La versión 2.0 fue gratuita incluso para uso comercial. Lentamente Internet Explorer va tomando popularidad y mercado de Netscape, lo que generaría la primera guerra de navegadores.

Los ataques entre estos dos navegadores consistían en la inclusión de funcionalidades novedosas para atraer tanto usuarios como autores de sitios web. Por ejemplo, Netscape desarrolló JavaScript a finales de 1995, que un año más tarde sería imitado por el JScript de Microsoft, en la versión 3 de Internet Explorer. En esa versión particular, Microsoft incluyó una parcial implementación de las hojas de estilo en cascada (CSS, Cascading Style Sheets) sugeridas por el W3C pero aún no estandarizadas.

En 1997 Netscape fusionó su Netscape Navigator con otro conjunto de programas para correo electrónico, composición web, calendario, etc. Al suite se le llamó Netscape Communicator, nombre que serviría para confusiones. Microsoft integró Internet Explorer 4 con Windows y desalentó desde el sistema operativo el uso de cualquier otro navegador. Netscape no compitió contra esto, ni tenía sentido. Desde 1998 el 80% del mercado que tenía Netscape pasó a sumar el 96% que tenía Internet Explorer 5 en el 2002. La guerra de los navegadores había finalizado y también el rápido tren de innovaciones; tan evidente que Microsoft no volvería a hacer cambios significativos en su navegador desde el 2001 al 2006.

A inicios de 1998 Netscape libera el código fuente de Netscape Communicator 4.0 en el proyecto Mozilla. La comunidad descartaría luego dicho código debido a su complejidad, poca modularización, al gran volumen de pulgas y otros inconvenientes; y empezó la elaboración de un nuevo motor de navegador (web browser engine) hecho desde cero, al cual se le llamó, Gecko, y sería el motor de "rendering" de Firefox y el nuevo Netscape Communicator, que finalmente sería descontinuado a inicios del 2008.

El proceso de estandarización

La guerra de navegadores (aproximadamente de 1995 a 1998) también tuvo consecuencias muy negativas. Ambos, Netscape Navigator e Internet Explorer, ofrecían características novedosas incompatibles entre sí, es decir, dialectos de HTML distintos que provocaron que los autores de millones de páginas tuvieran que escoger por uno o el otro. Era común ver imágenes indicando que el sitio se veía mejor con un navegador particular, incluso hasta de una versión específica.

Mientras tanto el proceso de estandarización avanzaba lentamente. En junio de 1993 el Consorcio Web (W3C) propone varios borradores de estandarización y en noviembre de 1995, la Internet Engineering Task Force (IETF) aprueba el HTML 2.0 que luego se convertiría en estándar internacional en 1997. El W3C propone HTML 3.0 en abril de 1995 pero la IETF no realiza ninguna acción. Los navegadores tomarán luego ideas de estas iniciativas en proceso y las implementarán a su manera para atraer usuarios.

A inicios de 1997 la IETF traslada la responsabilidad de estandarización al W3C, quien publicaría recomendaciones con una eficiencia mayor. Se publica el HTML 3.2 adoptando etiquetas y atributos de marcado visual de Netscape (como <b> y <font>). A final del mismo año, el W3C presenta el trabajo de estandarización más notorio hasta la fecha, conocido como HTML 4.0. Un esfuerzo que considera las extensiones derivadas de la guerra de navegadores y las raíces del HTML. En ella, las etiquetas de marcado visual serían desaprobadas ("deprecated") en favor de CSS; pero su uso se había difundido tanto que el W3C decidió generar tres variaciones del HTML 4.0:

  • HTML 4.0 Strict. Prohíbe elementos "deprecated".
  • HTML 4.0 Transitional. Permite elementos "deprecated".
  • HTML 4.0 Frameset. Permite construir páginas basadas en "frames", con los elementos <frameset> y <frame>.

Dos años después, a finales de 1999, se le haría una ligera modificación al estándar HTML 4.01, cuya variación estricta (HTML 4.01 Strict) sería adoptado como estándar internacional (ISO/EIC 15445:2000), suplantando la versión 2.0 de 1997. Tras ello, el trabajo de estandarización dejaría de lado al HTML para concentrarse en el XHTML.

En febrero de 1998 el W3C publicó el estándar XML (Extensible Markup Language). En enero de 2000 se reformuló HTML 4.01 como una aplicación XML en lo que conoce como XHTML 1.0. Una actualización XHTML 1.1 se emitió como recomendación en mayo de 2001 que permite modularizar el HTML para necesidades específicas, las más sobresalientes han sido XHTML-Basic que incluye el conjunto mínimo de características que cualquier navegador debe soportar, incluso de dispositivos móviles y XHTML-MP (mobile profile) con controles aptos para dispositivos móviles.

Entre agosto de 2002 y julio de 2006, el W3C trabajó en XHTML 2.0 que sólo alcanzó el nivel de notas y no de recomendación. En el 2008 el W3C consideraría como base del futuro (X)HTML[1] 5.0, un borrador llamado (X)HTML5 desarrollado por un grupo de interesados, compuesto principalmente por fabricantes de navegadores alternativos (Mozilla Foundation, Opera Software y Apple), ajenos al W3C, que se autodenominaron WHATWG (Web Hypertext Application Technology Working Group). A diferencia de XHTML 2.0, (X)HTML5 estaría más centrado en el desarrollo de aplicaciones web y no en la especificación de documentos.

La segunda guerra de navegadores

Los navegadores en guerra tuvieron equipos de desarrollo enfocados en agregar funcionalidad no estandarizada tan rápido como fuese posible, con poco control de calidad. Esto llevó a que ambos navegadores, Netscape Navigator e Internet Explorer no se apegaran a los estándares, fuesen "pulgosos" e inseguros. Pese de hacerlo público, el código fuente de Netscape fue descartado por la Fundación Mozilla. Por su parte, Microsoft tras ganar la guerra, mantuvo su navegador en el letargo por varios años.

Un navegador no mencionado es Opera. Inició en 1994 como un proyecto de investigación de la compañía noruega Telenor y disponible al público desde 1996 en forma comercial o gratuito con publicidad; hasta la versión 8.5 en setiembre de 2005, en que se distribuye sin costo ni publicidad alguna. Se caracterizó por ser el primer navegador en apegarse a los estándares, influir activamente en ellos, y en la inclusión de características amigables para el usuario, por ejemplo, el uso de "tabs", "mouse gestures", velocidad, seguridad y correr en dispositivos móviles.

La Fundación Mozilla siguió estas características en su navegador gratuito Firefox. Con el objetivo de crear nuevos estándares web que serían sometidos al W3C para aprobación, la Fundación Mozilla y Opera Software fundaron en el 2004 el grupo de trabajo WHATWG (Web Hypertext Application Technology Working Group), al cual se uniría Apple posteriormente.

Las primeras versiones del navegador de Mozilla aparecieron a finales del 2002, con nombres que después serían cambiados por conflictos con otras compañías, hasta quedar de Firefox, el cual se liberó en noviembre de 2004. Desde el 2003 Mozilla Firefox empezó lentamente a atraer usuarios de Internet Explorer, en especial por sus problemas y la falta de iniciativa de Microsoft por corregirlos.

Microsoft reaccionó hasta octubre de 2006 con Internet Explorer 7, imitando características de Opera y Firefox. Esto no logró detener la creciente tasa de usuarios que seguían cambiando a Firefox. Por su parte, una semana después, Mozilla liberó Firefox 2.0 con mejoras de usabilidad y seguridad, lo que pondría en evidencia la segunda guerra de navegadores. Esta vez luchando por proveer mayor facilidad de uso y apego a los estándares.

En el 2003 Microsoft anunció que descontinuaría Internet Explorer for Mac. Apple inició el trabajo de crear un navegador que lo reemplazara, ya que era su navegador por defecto, y a partir del motor KHtml usado en Konkeror de KDE generó WebKit y el navegador Safari que apareció en el mismo año.

En el 2008 Google libera el navegador Chrome basado en WebKit. La presencia de estos nuevos navegadores siguen reduciendo el número de usuarios de Internet Explorer [2]. Microsoft reacciona con la versión 8 en marzo de 2009 imitando funcionalidades e incrementando el apego a los estándares. Esta guerra se mantiene hasta el presente, donde cada fabricante libera versiones con frecuencia siguiendo la misma estrategia.

Aunque no es un estándar aún, la segunda guerra ha llevado a la mayoría de navegadores a implementar (X)HTML 5.0 o al menos las secciones más estables de sus borradores.

Ejercicio. Represente los eventos que considere más relevantes de la historia del web en una línea de tiempo.

Ejercicio. Si usted implementara un navegador web que se apegue fiel y únicamente a los estándares ¿Sería útil para que sus usuarios naveguen libremente en la web? Explique.

Ejercicio. ¿Por qué hay tanto interés en crear aplicaciones web en lugar de aplicaciones de escritorio? Compare las ventajas y desventajas de cada una.

Notas

[1]
En este documento se usarán los términos HTML y XHTML para referirse a cada notación por separado, o (X)HTML cuando algo aplique a ambas por igual.
[2]
De acuerdo a la estadísticas del W3C en http://www.w3schools.com/browsers/browsers_stats.asp

Arquitectura web

Un sitio web es un mecanismo de comunicación digital donde uno o más visitantes pueden obtener o proveer información de interés, y está basado en la arquitectura web, un modelo cliente-servidor cuya forma más simple se aprecia en la fig_arqweb y se explica a continuación en forma muy general.

Arquitectura web simple
Arquitectura web simple

El autor que quiera publicar un sitio web, construye un conjunto de páginas web en notación (X)HTML junto con imágenes, estilos y otros medios, y las almacena en una computadora que está siempre conectada a Internet, a la cual se le llama el servidor. Un programa especial, llamado servidor web, el cual tiene acceso de lectura a dichas páginas, estará siempre en esta computadora escuchando por un puerto TCP (Transmission Control Protocol), normalmente el 80 u 8080.

Un lector que quiera visitar el sitio debe conocer la dirección del servidor, es decir su número IP, ya sea introduciéndolo directamente o a través del servicio de nombres de dominio (DNS, Domain Name Service) y el puerto donde el servidor web está escuchando. El lector carga un programa especial en su computadora llamado navegador web (web browser) e ingresa en él la dirección del servidor y el puerto, la cual respeta una notación estándar conocida como localizador uniforme de recursos (URL). El navegador intentará establecer una conexión TCP con el servidor al puerto indicado o al 80 si se omite. El servidor web aceptará la conexión. A partir de este momento el cliente y el servidor pueden comunicarse libremente, pero para que ambos puedan entenderse se necesita un idioma común: el protocolo de transferencia de hipertexto (HTTP, HyperText Transfer Protocol).

El protocolo HTTP establece que el cliente, también conocido como user agent, siempre hace solicitudes de recursos para mostrarlos al usuario, y el servidor responde a ellas, no el recíproco. Las solicitudes del cliente y las respuestas del servidor están codificadas como se verá luego.

En lo que resta de esta sección se explicarán los conceptos que componen la arquitectura web con más detalle y en la siguiente sección, el proceso típico en que un autor construye su sitio web y lo hace público para que los visitantes interaccionen con él.

El servidor web

Un servidor web es un programa cuya ejecución es persistente, a veces llamado servicio o demonio, en un equipo conectado a Internet, esperando conexiones de clientes usualmente por el puerto 80. Una vez establecida una conexión, el cliente solicita repetitivamente recursos al servidor mediante el protocolo HTTP y éste responde a cada una de ellas. El término servidor web también se usa para hacer referencia al hardware que corre este programa, pero en este documento se usará sólo para referirse al software.

Cualquiera puede implementar un servidor web escribiendo un programa que espere conexiones en algún puerto TCP y hable por él HTTP. Sin embargo, para la mayoría de situaciones, es apremiante utilizar un servidor web existente. La servidores_web lista los más populares en la actualidad.

Nombre Fabricante Licencia Detalles
Apache HTTP Server Apache Libre (Apache License) Rico en características y extensiones. Corre en la mayoría de sistemas operativos. Sirve aproximadamente más del 60% de las páginas web del mundo.
Internet Information Services (IIS) Microsoft Propietario / Comercial Sólo se ejecuta en Windows Server. Sirve aproximadamente 20% de las páginas web del mundo
nginx Igor Sysoev Libre (BSD) Nació como una alternativa de Apache caracterizada por el alto rendimiento y bajo consumo de recursos. Corre en la mayoría de sistemas operativos. Sirve aproximadamente 8% de las páginas web del mundo.
Google Web Server (GWS) Google Uso interno Sirve aproximadamente 5% de las páginas web del mundo.
lighttpd lighttpd Libre (BSD) Diseñado para ambientes de muy alto rendimiento donde la velocidad es un factor crítico. Es limitado en cuanto a funcionalidad. Sirve aproximadamente un 1% de las páginas web del mundo.
Servidores web más populares en la actualidad de acuerdo a la encuesta Netcraft.

El cliente web o navegador web

Un navegador web, cliente web, o agente usuario ("user agent"), es un software que permite obtener, presentar, recorrer e interaccionar con recursos disponibles en la web. De los componentes de la arquitectura web, es el más conocido por los usuarios, ya que interaccionan directamente con él.

Un navegador web es una pieza de software compleja, por lo que usualmente está dividida en al menos dos módulos: el motor y la interfaz. El motor del navegador web ("web browser engine" o "rendering engine"), se encarga de analizar contenido ((X)HTML, imágenes, etc.), aplicarle estilos, modificar lo anterior con programas de JavaScript y presentar los resultados en la pantalla u otro medio. Por su parte, la interfaz del navegador web provee una barra de direcciones, marcadores, botones de navegación y otros controles que usan al motor del navegador web internamente para facilitar al usuario su interacción.

La separación entre el motor y la interfaz tiene una importante ventaja. El motor se distribuye en forma de biblioteca y permite que cualquier otra aplicación pueda usarlo. De esta forma, clientes de correo, aplicaciones de ayuda o cualquier software que usted quiera programar, puede manipular documentos web. La browser_engines muestra los motores de "rendereo" web más populares en la actualidad y los navegadores que los usan.

Nombre Fabricante Licencia Navegadores
Gecko Mozilla Project Libre (L/GPL) Firefox
WebKit Apple, KDE, Google, ... Libre (LGPL/BSD) Chrome, Safari
Trident Microsoft Propietario Internet Explorer
Presto Opera Software Propietario Opera
Motores de navegador web más populares en la actualidad.

El localizador uniforme de recursos URL

Un servidor web provee recursos en los que puede estar interesado un cliente. Todo recurso transferible entre el servidor y el cliente, debe estar identificado de forma única en el mundo mediante un localizador uniforme de recurso (URL, Uniform Resource Locator), que es una dirección junto con alguna información adicional para acceder al recurso. La sintaxis de un URL es

esquema://username:password@servidor:puerto/ruta?query_string#id_fragmento

Ejercicio. Identifique y señale en los siguientes ejemplos, cada una de las partes del URL. Nota: algunos son ficticios.

http://www.amazon.com/
http://www.w3.org/TR/html401/struct/tables.html#edef-CAPTION
http://acme.co.cr:8080/index.php
https://24.168.39.221/cgi-bin/book?id=4596098&action=remove
ftp://msoto:w33n8rf1@down.antivirus.net/current/setup.exe

Las partes de un URL son las siguientes:

  1. Esquema. También llamado Protocolo. Indica el propósito y la sintaxis del resto del URL. Entre muchos se pueden citar: http, https, ftp y mailto. Por ejemplo, al procesar el URL http://www.amazon.com/, un navegador hará un petición HTTP al servidor www.amazon.com en el puerto 80. Al procesar el URL mailto:chema@suizacentroamericana.cr, lanzará un cliente de correo con un mensaje nuevo dirigido a chema@suizacentroamericana.cr.
  2. Servidor. Es la dirección IP o el nombre de dominio si tiene registrado uno en el servicio de nombres de dominio (DNS, Domain Name Service). Permite identificar al servidor que provee el recurso, por ejemplo drupal.org. Dado a que DNS no es sensitivo a mayúsculas ni minúsculas, da lo mismo acceder a DRUPAL.org, por ejemplo.
  3. Puerto. Indica el puerto que será usado en la conexión TCP. Por ejemplo, http://myserver.com:10000/ hará que el navegador establezca una conexión HTTP en el puerto 10000, probablemente para administración del servidor. Si se omite el puerto en el URL se asumirá el por defecto para el esquema usado. Por ejemplo, para http es 80, para https es 443 y para ftp es 21. Una lista de protocolos y sus puertos por defectos puede a conveniencia consultarse en la Wikipedia.
  4. Ruta. Contiene la ruta para encontrar el recurso. Puede ser relativa al sistema de archivos del servidor o un "alias" que ayude a especificar el recurso, por ejemplo, http://mibibl.net/revistas/acm.png. Es muy probable que sea sensitiva a mayúsculas si el sistema de archivos del servidor lo es también.
  5. Parámetros. Si el recurso es generado por una aplicación en el servidor web, a esta se le pueden enviar parámetros en forma de parejas parametro=valor y separadas por ampersands (&) si son varias parejas, por ejemplo, https://mibibl.net/revista.php?id=23091&action=devolucion&user=chema. A esta lista de parámetros se le suele llamar "query string".
  6. Identificador de fragmento. Es un nombre que sirve para identificar una parte (fragmento) o un punto particular de un documento web. Cuando se especifica un identificador de fragmento en un URL, provoca que el navegador cargue el documento y automáticamente se desplace ("scroll") hasta el fragmento en cuestión. Por ejemplo, http://misitio.co.cr/docs/tesis.html#Cap03.
  7. Usuario y contraseña. Si para acceder al recurso se necesita que el visitante se autentique, algunos protocolos permiten indicar las credenciales en el URL mismo. Es poco común y una práctica no recomendada incluir contraseñas, ya que el URL normalmente es público y carece de seguridad. Ejemplo, ftp://mmortadela@sjmuni.go.cr/vaca/mu.pdf.

El protocolo de transferencia de hipertexto HTTP

La arquitectura web establece que un servidor web provee al mundo recursos identificados de forma única con URLs; un cliente web o navegador web interesado solicita estos recursos y el servidor responde con ellos. Las convenciones usadas en el trasiego de estos recursos entre el cliente y servidor las estalece el protocolo de transferencia de hipertexto (HTTP, Hypertext Transfer Protocol).

Cuando el cliente web necesita un recurso, emite un mensaje de solicitud HTTP (HTTP request) al servidor. El servidor web carga el recurso desde el disco, una base de datos, un programa o cualquier otra fuente, y responde con una respuesta HTTP (HTTP response).

Una sesión HTTP (HTTP session), es una secuencia de solicitudes-respuestas entre el navegador y el servidor web. En la versión HTTP/1.0 de 1996 se establece una sesión por cada transferencia. Es decir, se inicia la sesión, el cliente solicita un recurso, el servidor responde y se cierra la sesión inmediatamente. Para transferir un nuevo recurso se debe iniciar otra sesión HTTP. En la versión HTTP/1.1 de 1999 la sesión permite un número arbitrario de transferencias lo que agiliza la comunicación entre ambas partes.

El mensaje HTTP

En la http_message_format se puede ver que la estructura de los mensajes de solicitud y respuesta HTTP es la misma, pero varía el contenido de los primeros dos campos.

Estructura de un mensaje HTTP
Estructura de un mensaje HTTP

La http_message_examples muestra dos ejemplos de mensajes HTTP. Los cambios de línea son importantes por lo que se muestran en rojo. En las siguientes secciones se explica cada uno de los campos que conforman los mensajes HTTP.

Ejemplo de dos mensajes HTTP
Ejemplo de dos mensajes HTTP

Los navegadores sólo muestran el contenido del cuerpo de los mensajes al usuario, ocultando los dos primeros campos de los mensajes usados en la negociación con el servidor web. La extensión HTTP Headers para el navegador Chrome, y Live HTTP Headers para Firefox, permiten al usuario visualizar y estudiar los campos ocultos de los mensajes que se intercambiaron entre el cliente y el servidor durante la carga de una página.

Request line

En la primera línea del mensaje de solicitud HTTP, llamada Request line, el cliente web indica al servidor el recurso de interés (un URL) y la acción que desea el servidor tome con dicho recurso. La sintaxis del Request line es:

METHOD URL HTTP_version

El protocolo HTTP define 9 acciones que un cliente web puede solicitar al servidor, reciben el nombre de Request methods y se listan en la http_request_methods. El protocolo HTTP indica que todo servidor web debe almenos implementar GET y HEAD, e idealmente OPTIONS.

Método Descripción
GET Solicita una copia completa del recurso cuya identificación esta dada por el URL que le sigue. A través de directivas en el encabezado (el campo que sigue en el mensaje) se pueden hacer solicitudes condicionales (Conditional GET), por ejemplo, obtener una copia del recurso sólo si ha cambiado a partir de una fecha, o una solicitud parcial (Partial GET), por ejemplo, obtener sólo un segmento del recurso. Estos detalles se explican en el RFC-2616.
HEAD Es idéntico al método GET excepto en que el servidor web no debe retornar un Message body en el mensaje de respuesta HTTP. Esto permite al cliente obtener metadatos de un recurso, como para saber si ha cambiado en el servidor u otros fines.
OPTIONS Retorna los métodos HTTP que el servidor soporta para un recurso dado (con un URL) o en general por el servidor web mismo (con un '*' en lugar de un URL).
POST Envía datos, normalmente ingresados en un formulario web, en el campo Message body del mensaje de solicitud HTTP, al recurso identificado por el URL, normalmente un programa en el servidor que procesará o almacenará dichos datos.
PUT "Sube" (upload) un recurso identificiado por el URL cuyo contenido es enviado en el Message body al servidor, el cual reemplaza el recurso anterior si existe. Se diferencia de POST en la semántica del URL. En POST el URL especifica una aplicación que recibe los datos y los procesa; mientras que en PUT el URL es la identificación del recurso mismo.
PATCH Sirve para aplicarle modificaciones parciales a un recurso.
DELETE Solicita al servidor eliminar el recurso identificado por el URL. Normalmente está deshabilitado por defecto en la mayoría de servidores web.
TRACE Permite rastrear servidores intermedios que procesan la solicitud HTTP. Esto es útil para estudiar el comportamiento de servidores proxy en especial si implementan algún caché.
CONNECT Transforma la solicitud en una conexión a un túnel TCP/IP para facilitar la comunicación segura (HTTPS) a través de un proxy HTTP no encriptado.
HTTP Request Methods.

Request header

El campo de encabezado de la solicitud HTTP (HTTP Request Header) permite al cliente proveer información adicional sobre la petición o sobre el cliente mismo al servidor. En este campo se escriben parejas Atributo=Valor. El estándar HTTP define unos 19 atributos de los cuales sólo uno es obligatorio: Host. Los atributos se pueden extender mientras los agentes (el servidor y el navegador) estén de acuerdo en ellos. La request_header_fields muestra algunos de estos atributos.

Campo Descripción
Accept: Permite al cliente especificar los tipos de medios (MIME types) que son aceptables como respuesta. Ej.: Accept: text/plain; text/html; application/html+xml. Un asterisco indica que todas las categorías o todos los medios son aceptables (*/* se asume si no se especifica un atributo Accept). Un párámetro q=valor indica la calidad aceptable, donde valor es un real entre 0.0 y 1.0; útil especialmente en vídeo o ciertos formatos. Si el servidor no puede responder con un formato aceptable, debe generar un mensaje con el status code 406 NOT ACCEPTABLE.
Host: Permite especificar el servidor y el puerto que tiene el recurso solicitado, ya que la primera línea (Request line) no incluye estos valores y se necesitan para desambiguar en caso de servidores proxy u otros intermediarios. Es el único atributo obligatorio en HTTP/1.1. Ej.: Host: www.miservidor.com:8080.
Referer: Permite especificar el URL del recurso del cual se obtuvo el URL solicitado al servidor. Debería ser Referrer:, pero en el estándar aparece como Referer:.
User Agent: Contiene información sobre el navegador o agente de usuario que solicita el recurso. Consta de varias parejas módulo/valor separadas por espacios y en orden de importancia. Todos los agentes de usuario deben proveer este atributo. Ej.: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:5.0.1) Gecko/20100101 Firefox/5.0.1.
Campos estándar en el HTTP Request header.

Emtpy line

Es un cambio de línea compuesto nada más de dos caracteres: retorno de carro y avance de línea (CRLF, carriage-return y line-feed). En notación C se escribe como "\r\n". Aunque algunos servidores web aceptan un LF simple ("\n"). Esta línea no debe tener ningún otro espacio en blanco.

Message body

Este es el único campo opcional del mensaje HTTP y su contenido varía dependiendo de la naturaleza del mensaje. También es el único que puede transportar información binaria (todos los demás son de texto).

Status line

En la línea Status line del mensaje de respuesta HTTP, el servidor indica al cliente web, un código y una explicación textual como resultado de procesar la solicitud previamente hecha. La sintaxis del Status line es:

HTTP_version status_code reason_phrase

El status_code es un número y el reason_phrase es un texto corto escogido por el servidor web y comprensible para seres humanos; el navegador no necesita analizarlo. El status_code es entero de tres dígitos, donde el primer dígito indica una de las cinco clases de respuesta definidas por el estándar HTTP:

  1. Información. La solicitud fue recibida y está siendo procesada.
  2. Éxito. La solicitud fue recibida, entendida, aceptada y procesada exitosamente.
  3. Redirección. Se necesitan más acciones del cliente para completar la solicitud.
  4. Error del cliente. La solicitud es incorrecta por error de sintaxis o no se puede realizar.
  5. Error del servidor. El servidor falló al procesar una solicitud aparentemente válida.

Por ejemplo, el error 404 NOT FOUND indica que el cliente especificó un URL de un recurso que no existe en el servidor. El RFC 2616 de HTTP/1.1 define 41 status_codes, algunos de los más comunes son los siguientes:

  • 200 OK. La solicitud HTTP fue exitosa. Por ejemplo, si la solicitud fue un GET, la respuesta contendrá el recurso solicitado en el campo Message body; si la solicitud fue un POST, los datos enviados por el cliente fueron procesados exitosamente.
  • 204 NOT CONTENT. El servidor procesó la solicitud exitosamente, pero no es necesario generar un recurso como respuesta y por ende el campo Message body está vacío.
  • 206 PARTIAL CONTENT. El servidor responde con un fragmento del recurso solicitado, tal como fue pedido por el cliente web. Esto es de utilidad para continuar descargando un recurso incompleto o permitir varias descargas simultáneas en partes diferentes del mismo recurso. Esta funcionalidad es ampliamente explotada por los administradores de descargas (download managers).
  • 301 MOVED PERMANENTLY. El recurso ha sido movido a otro URL y el cliente debe obtener el nuevo recurso con una nueva solicitud.
  • 302 FOUND. No debería usarse. Normalmente se quiere decir 303.
  • 303 SEE OTHER. El recurso está disponible en otro URL, el cual el cliente debe obtener con una solicitud nueva. Puede usarse para evitar sobrecargar un servidor web o redireccionar algún recurso.
  • 400 BAD REQUEST. La sintaxis de la solicitud es incorrecta.
  • 401 UNAUTHORIZED. El cliente intenta acceder a un recurso que está protegido y no ha provisto credenciales o las ha fallado. La respuesta incluye un reto al usuario, el cual debe proveer un nombre de usuario y contraseña.
  • 403 FORBIDDEN. La solicitud es válida pero el servidor se niega a completarla, por ejemplo, tras varios intentos el usuario falla el proceso de autenticación.
  • 404 NOT FOUND. El recurso no se encuentra pero podría estarlo en el futuro.
  • 410 GONE. El recurso no se encuentra y nunca más lo hará. Es útil para avisar a los motores de búsqueda que eliminen el recurso de sus índices.
  • 500 INTERNAL SERVER ERROR. Mensaje genérico, para cuando no hay uno 5XX más específico.
  • 501 NOT IMPLEMENTED. El request method o alguna característica es válida ante el estándar, pero el servidor web no lo implementa.
  • 503 SERVICE UNAVAILABLE. El servidor web no está disponible por sobrecarga o en mantenimiento. Normalmente una condición temporal.

Para revisar la lista completa de códigos de estado HTTP con los que puede responder un servidor, consúltese el RFC 2616.

Response header

El campo encabezado de respuesta HTTP (HTTP Response Header) permite al servidor enviar información adicional sobre la respuesta al agente de usuario. Utilizan la misma notación que los del campo encabezado de solicitud HTTP y también se pueden extender mientras los agentes estén de acuerdo en ellos. Algunos se explican en la siguiente tabla.

Campo Descripción
ETag: Contracción de "Entity Tag". Es una cadena que permite identificar el estado del recurso, de tal forma que si se hace una modificación del recurso en el servidor, su ETag variará. Esto permite al cliente saber si su copia en el caché está actualizada o ha variado en el servidor. Ej.: Etag: "239876f-4b8c-429fe67474a80".
Location: Permite al servidor indicarle al agente de usuario que el recurso solicitado ha sido movido a un nuevo URL. El agente usuario debe entonces solicitar el nuevo recurso, lo que se conoce como una "redirección".
Server: Contiene información sobre el servidor web que genera la respuesta. Consta de varias parejas módulo/valor en orden de importancia para identificar al servidor. Ej.: Server: Apache/2.2.9 (Debian) PHP/5.2.17-0.dotdeb.0.
WWW-Authenticate: Solicita al usuario autenticarse para acceder a un recurso en una respuesta 401 UNAUTHORIZED. Es seguido por el número de intentos permitidos. Ej.: WWW-Authenticate: 3.
Campos estándar en el HTTP Response header.

Ejemplo de una sesión HTTP

Supóngase que el sitio web ubicado en www.ejemplo.com está en construcción y tiene sólo dos recursos, un index.html y una imagen img/ucr.png. El contenido del recurso index.html se encuentra en el some_index_page.

<html>
   <head><title>Desarrollo de aplicaciones web</title></head>
   <body>
      <img src="img/ucr.png" alt="Escudo UCR" />
      <img src="img/ecci.png" alt="Escudo ECCI" />
      <h1>Presentación</h1>
      <p>Este curso introduce el estudiante en el desarrollo[...]</p>
   </body>
</html>
Contenido de una página web hipotética

Un visitante accede con su navegador a www.ejemplo.com. La interacción entre el navegador web y el servidor web se aprecia en el diagrama de secuencia de la http_session_example_img.

Diagrama de secuencia de una sesión HTTP
Diagrama de secuencia de una sesión HTTP

Al escribir la dirección http://www.ejemplo.com/ el navegador contacta al "default gateway" que tenga configurado el sistema operativo de la máquina local y le pide que le resuelva la dirección IP del dominio www.ejemplo.com. Al obtener la dirección IP, el navegador establece una conexión TCP en el puerto 80 con www.ejemplo.com y éste acepta. A partir de este momento se ha establecido una sesión HTTP; el navegador y el servidor web pueden intercambiar mensajes HTTP.

El navegador solicita el recurso que el usuario indicó en el URL, en este caso es "/". Ensambla un mensaje HTTP Request con el método GET, escribe algunos campos de encabezado (HTTP Request Header fields) y un cambio de línea. Lo envía al servidor.

El servidor recibe la solicitud HTTP y de acuerdo a su configuración local, obtiene que el recurso "/" equivale al archivo index.html. Ensambla una respuesta HTTP (HTTP Response message) con el status code 200 indicando que el recurso fue encontrado y la solicitud se procesó exitosamente. Agrega unos campos de encabezado de respuesta (HTTP Response Header fields) y anexa el contenido del recurso index.html literalmente en el campo Message body. Lo envía al cliente.

El navegador recibe el mensaje de respuesta HTTP y viendo que la solicitud fue exitosa, extrae el recurso index.html guiado por los campos de encabezado HTTP. Almacena el recurso en su caché e inicia el análisis (parsing) y despliegue (rendering) del recurso en la ventana del usuario. Al analizar la línea 4 del index.html, el navegador se percata de que necesita otro recurso para presentarlo en la página, y ensambla otra solicitud al servidor, de la misma forma que hizo con index.html, pero esta vez por img/ucr.png.

El servidor web recibe la nueva solicitud y la procesa de la misma forma que hizo previamente con index.html. El recurso también existe. La única diferencia es que img/ucr.png es un recurso binario, de ahí la importancia de los campos del encabezado que ayudarán al navegador a procesar el recurso adecuadamente. El servidor envía el mensaje de respuesta.

El navegador recibe el mensaje y al ver que es exitoso, agrega el recurso en su caché y guiado por los campos del encabezado del mensaje, interpreta el contenido binario como una imagen PNG y la coloca en su lugar dentro de la ventana. Al analizar la línea 5 del documento index.html (some_index_page), se percata de que necesita también el recurso img/ecci.png. Ensambla una nueva solicitud GET y la envía al servidor como hizo anteriormente.

El servidor web recibe la solicitud y trata de localizar el recurso. Al notar que no se encuentra en su sistema de archivos, construye una respuesta HTTP con status code 404 Not Found, indicando que el agente del usuario solicitó un recurso erróneo. En el cuerpo del mensaje agrega un texto más explicativo y en los campos del encabezado HTTP detalles de cómo interpretar este texto. Lo envía al cliente.

El navegador recibe el mensaje 404 Not Found. Al no obtener un recurso para desplegar (render), opta por dibujar un rectángulo genérico indicando la ausencia y rellena con el texto alternativo que se encuentra en el documento index.html. Continúa de esta forma analizando y desplegando el recurso index.html hasta alcanzar su final.

Ejercicio. Instale dos servidores web distintos en su máquina local. Haga a uno de ellos responder en el puerto 80 y otro en el 8080. Pruebe que funcionan adecuadamente con un navegador. ¿Qué pasa si intenta hacer que los dos atiendan el mismo puerto?

Ejercicio. ¿Cómo hace un servidor web para encontrar en el sistema de archivos local un recurso solicitado a través de un URL? Por ejemplo, si intenta acceder a http://localhost/bio/foto.jpg ¿a qué archivo de su sistema de archivos, exista o no, intenta acceder el servidor web? ¿A qué recurso intenta acceder el servidor web cuando se le solicita simplemente http://localhost/?.

Ejercicio. Inspeccione la comunicación con un servidor web externo o el servidor web local usando telnet. Pruebe obtener diferentes tipos de recursos o enviar diferentes tipos de peticiones (métodos HTTP).

Ejercicio. Un usuario tiene cargada una página web y presiona el botón de refrescar. ¿Cómo debería el navegador web implementar esta operación? ¿Existe algún mecanismo eficiente para reducir el tráfico de red?

Proceso de construcción del sitio web

Al igual que cualquier otro software, la construcción de un sitio web sigue un proceso de desarrollo, con las fases conocidas: análisis para saber qué necesita el cliente; diseño del sitio; implementación de las aplicaciones web; pruebas (testing) de las aplicaciones web; mantenimiento del sitio; etc.

Se debe tener muy claro que el equipo de desarrollo de un sitio web, o simplemente el equipo web, es una entidad multidisciplinaria y no sólo uno o más informáticos. Este equipo puede estar conformado por los siguientes profesionales.

Diseño del sitio web

La fase de análisis pretende conocer el problema, es decir, obtener el propósito del sitio web, las necesidades que resuelve, los visitantes que lo usarán, etc.

La fase de diseño pretende elaborar un modelo de sitio web que solucione las necesidades halladas en la fase de análisis. En esta etapa se decide qué tecnologías usar (páginas estáticas, un administrador de contenido (CMS), programación PHP, bases de datos orientadas a documentos, etc.); la arquitectura de los componentes que intervendrán; la apariencia del sitio (los estilos, si se usará una metáfora, etc.), las convenciones (los nombres de los archivos, reglas de escritura del código fuente, etc.).

Sin un buen diseño el programador se sentará frente a la computadora sin saber cómo empezar el sitio web, ni qué secciones tendrá, ni cuáles de ellas tendrán páginas estáticas o serán programadas, etc. La importancia de la fase de diseño web es tal que libros completos atienden el tema, como "Web Style Guide" de Patrick J. Lynch y Sarah Horton, cuyo texto completo se encuentra disponible en línea gratuitamente.

Alojamiento web

Para publicar su sitio web el autor debe, adquirir una o más computadoras; configurarlas como servidores web, bases de datos u otros servicios; contratar suficiente ancho de banda; velar porque el servicio no se interrumpa pese a fallos eléctricos con UPS (Uninterruptible Power Supply) y generadores eléctricos, o fallos en el proveedor de servicios de Internet (ISP, Internet Service Provider) con redundancia de conexiones; fallos de hardware (como malfuncionamiento de un disco duro) y otra serie de consideraciones para que el sitio esté disponible al menos el 99% del tiempo.

Muchos autores prefieren relegar estas responsabilidades a una compañía que se especialice en brindar este tipo de servicio, al cual se le conoce como alojamiento web (web hosting). La oferta de proveedores de alojamiento web es considerable, y varía en precios desde lo gratuito hasta lo costoso.

Las compañías de alojamiento web proveen varios tipos de alojamiento. Un listado de tipos de alojamiento web puede verse en http://en.wikipedia.org/wiki/Web_hosting_service, de los cuales sobresalen los siguientes.

  1. Servidor compartido (shared hosting). Es la variante más común y la más económica. Su sitio web comparte el mismo servidor web con otros sitios que estén alojados en el mismo servidor físico. Usted no tiene derechos de administración, ya que un cambio afectaría otros sitios.
  2. Servidor dedicado virtual o servidor privado virtual (VPS, Virtual Private Host). Usted alquila una máquina virtual la cual es de su uso exclusivo (privado) y por ende tiene permisos de administración sobre ella, por lo que puede reiniciarla o instalar paquetes. Varias máquinas virtuales comparten el mismo servidor físico y por ende compiten por sus recursos.
  3. Servidor dedicado (dedicated hosting). Un servidor físico es para su uso exclusivo, no compartido con nadie.
  4. Alojamiento en la nube (cloud hosting). Su sitio web no estará en un único servidor, sino distribuido en varios de ellos. Esto tiene grandes ventajas, como la posibilidad de responder a una inmensa cantidad de visitantes simultáneamente, la facilidad de expansión y la capacidad de continuar activo pese a que un servidor físico esté caído.

El autor puede beneficiarse de los servicios de alojamiento web gratuitos. El sitio Free Web Hosting permite listarlos, compararlos y buscar de acuerdo a necesidades específicas. Antes de decidirse por uno, el autor debería revisar los términos de servicio y otros detalles para evitar sorpresas.

Registro de dominio

La mayoría de sitios web se conocen por un nombre de dominio como www.ejemplo.com. Cualquier persona puede registrar un nombre de dominio, pero siempre tiene un costo económico. En la actualidad es cercano a los 10 dólares anuales.

Cuando se registra un nombre de dominio, éste se convierte en pertenencia del autor y no de la compañía con que se hizo el trámite. El autor puede entonces asociar el dominio con cualquier servidor web, sea propio, de un servicio de alojamiento gratuito, o uno comercial. Sin embargo, la mayoría de planes de alojamiento web comercial, incluyen el costo de registro y renovación del nombre de dominio.

Construcción del contenido

Una vez que se ha determinado la necesidad de un sitio web, se ha diseñado una solución, se tiene alojamiento gratuito o no, y opcionalmente asociado a un nombre de dominio; el autor estará interesado en escribir el contenido del sitio web.

Quizá el método más rápido y eficiente de construir un sitio web sea a través de páginas estáticas. Es decir, un conjunto de documentos (X)HTML escritos manualmente o con ayuda de algún software de diseño web, además de otros recursos como hojas de estilo, imágenes y programas en JavaScript. Esto puede ser conveniente para sitios web sencillos y pequeños.

Cuando un sitio web se torna extenso con cientos o miles de documentos web, intervenido por muchos usuarios que aportan información, o que debe solventar necesidades especificas que sólo con programación puede atendenderse; se debe convertir en un sitio web dinámico. En tal caso el autor debe escoger algún medio de programación en el lado del servidor, como CGI o algún lenguaje de "scripting" como PHP.

Promoción del sitio web

Una vez que el sitio web está publicado, el autor estará interesado en que la audiencia interaccione con él. Es difícil que entre millones de sitios web en el mundo un visitante encuentre el de uno. No obstante, algunas estrategias pueden ayudar.

El principal medio en la actualidad para encontrar algo de interés en el web es a través de un buscador, como Google o Bing. Normalmente estos buscadores proveen algún mecanismo para solicitarles que consideren incluir un dominio en sus índices.

Aunque el sitio web sea incluido en los índices de los buscadores, es muy probable que no aparezca entre los primeros resultados de una consulta. Esto se puede mejorar incrementando la "popularidad" del sitio. Los buscadores miden qué tan popular es un recurso en proporción a la cantidad de enlaces que encuentran hacia dicho recurso en otros dominios. Por eso, enlazar el sitio web en diarios, noticias, listas de correo u otros medios puede ayudar.

Una alternativa más es invertir en publicidad local o internacional. Incluso se puede costear un lugar privilegiado en los resultados de los buscadores más populares.

Ejercicio. Elabore una lista de potenciales sitios web de su interés o del interés de otras personas a las que quiere ayudar. Ordénelos por algún criterio, como urgencia o la pasión que siente por ellos. Escoja uno para construir siguiendo el conocimiento que obtendrá en los próximos capítulos.

Ejercicio. Registre un sitio web en un servicio de alojamiento web gratuito para el sitio que escogió en el ejercicio anterior.

El lenguaje de marcado extensible XML

El lenguaje de marcado extensible (XML, eXtensible Markup Language), es un lenguaje estándar para representar información de interés en forma de documentos digitales, que se caracteriza por permitir marcado definido a conveniencia del autor. Con propósitos de sencillez, XML es un subconjunto del SGML (Standard Generalized Markup Language), un lenguaje estándar (ISO 8879:1986) nacido en IBM en los años 60.

Conceptos

Los seres humanos tienen diversas necesidades de información, y cuando ésta se debe almacenar en la computadora, se suele hacer en forma de bases de datos o documentos digitales. Las bases de datos restringen en alguna medida la representación de información del mundo real a una estructura formalmente definida que puede ser eficientemente manipulada por el computador. Los documentos son menos estrictos por lo que pueden representar casi cualquier tipo de información del mundo real, pero tal libertad limita el poder de manipulación del computador. El XML es un lenguaje formal que busca las ventajas de ambos: la representación flexible de información en forma de documentos que pueden ser manipulados aprovechando el poder del computador.

Charles Goldfarb, el principal autor del SGML, indica que XML sirve para la representación digital de documentos. Representar digitalmente un documento implica transformarlo en algún tipo de código legible por la computadora para que ésta sea capaz de almacenarlo, procesarlo, buscarlo, transmitirlo, mostrarlo e imprimirlo [Gold99]. La estrategia consiste en separar los datos del documento de su presentación y además, describir la estructura lógica y física que sigue el documento.

La presentación de un documento es la percepción que tiene el ser humano de él, que típicamente es impresa en papel o en una pantalla [Gold99]. La estructura del documento describe la distribución y orden de los componentes del documento. Los componentes varían dependiendo del tipo de documento. Por ejemplo, un libro suele tener como componentes: portada, índice, partes, capítulos, secciones, párrafos, oraciones, palabras, títulos, notas, etc. En terminología XML estos componentes reciben el nombre de elementos. El ej_doc_xml muestra un ejemplo de un posible libro en notación XML.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book SYSTEM "book.dtd">

<book name="AplicacionesWeb" version="1.0">
   <title>Aplicaciones web</title>
   <authors>
      <author name="Jeisson" percent="100">
         <fullname>Jeisson Hidalgo-Céspedes</fullname>
         <email>jeissonh@gmail.com</email>
      </author>
   </authors>

   <chapter name="Preliminar" type="prelims">
      <section name="Prologo">
         <title>Prólogo</title>
         <p>Este documento contiene un resumen de temas que servirán de
            apoyo a los estudiantes del curso CI-2413 impartido...</p>
      </section>
      <section name="Agradecimientos" type="acknowledgements" author="Jeisson">
         <title>Agradecimientos</title>
         <p>Quiero agradecer en primer lugar, a usted, que con sus ojos
            da vida a estas inanimadas palabras pintadas en...</p>
      </section>
   </chapter>

   <chapter name="Introduccion">
      <title>Introducción a la tecnología web</title>
      <p>De todas las aplicaciones que se han construido sobre Internet...</p>
      <p>Su popularidad puede deberse a su &quot;facilidad de uso&quot; y...</p>

      <section name="HistoriaWeb">
         <title>Historia</title>
         <p>La web fue conceptualizada en un artículo de 1989 Tim Berners-Lee</p>
      </section>
   </chapter>
</book>
Ejemplo de un libro hipotético en XML.

Otro concepto presente en la estructura de un documento XML es el de entidad. Una entidad representa cualquier trozo de texto, desde un carácter, un párrafo, un archivo o todo un libro. Las entidades tienen nombre el cual puede utilizarse en cualquier parte del documento al insertar una referencia de entidad, la cual es sustituida por la entidad misma cuando el documento es procesado. Las entidades funcionan de forma similar a las macros de los procesadores de palabras [Gold99]. Por ejemplo la referencia a la entidad &lt; en un documento XML se reemplaza por el símbolo "<".

Las entidades permiten separar un todo en partes físicas que facilitan la manejabilidad o ciertos procesos, pero también se pueden manipular como un todo cuando sea necesario. Por ejemplo, es tedioso imprimir un manual HTML que está segmentado en cientos de archivos .html unidos débilmente por enlaces (links). En XML, la información de cada archivo .html se escribiría como una entidad y en el archivo que represente el todo, contendrá referencias a las entidades anteriores, el cual puede ser impreso con facilidad.

Las entidades pueden representar información en otra notación que no es XML, por ejemplo, imágenes, archivos de audio, archivos de vídeo, texto puro, HTML, etc. Estas entidades reciben el nombre de entidades no analizables sintácticamente.

Se dice que las entidades describen la estructura física y los elementos la estructura lógica de los documentos XML. La estructura lógica (elementos) y física (entidades) se representa dentro del documento XML agregando el marcado, el cual se delimita de los datos de carácter encerrando la descripción de los elementos dentro de paréntesis angulares (“<” y “>”), que se conocen como etiquetas y las referencias a entidades entre el signo “&” y el punto y coma (“;”). Es decir, el texto encerrado dentro de estos cuatro caracteres se conoce como marcado, lo restante como datos de carácter. La combinación del marcado más los datos de carácter forman el texto XML [Gold99].

Ejercicio. Utilizando resaltadores, distinga el marcado de los datos de carácter en el listado ej_doc_xml.

Es sabido que las cartas, tesis, guiones y las guías telefónicas son documentos que tienen una estructura muy diferente. Es decir, poseen elementos distintos, cada uno con su propia distribución y orden. Se dice que son tipos de documentos distintos. En XML cada tipo de documento se define en una notación formal que plasma su estructura llamada definición de tipo de documento (DTD, Document Type Definition). A modo de ejemplo, la línea 2 del listado ej_doc_xml declara que el tipo de documento es un libro.

Se puede pensar en un DTD como una clase en programación orientada a objetos, mientras que un documento XML que sea de ese tipo de DTD es como un objeto que instancia esa clase. El “objeto” documento XML debe cumplir a cabalidad con la estructura descrita en su DTD, cuando esto ocurre, se dice que el documento es de tipo válido o simplemente válido, de lo contrario, se dice que el documento es de tipo no válido o simplemente no válido (pero no se usa el término "inválido") [Gold99].

Un documento XML puede no tener un tipo de documento definido, es decir, carece o no cumple con un DTD, por lo que es un documento no válido pero puede respetar la sintaxis XML, en tal caso se dice que sólo es un documento bien formado (well formed). Los documentos bien formados pero no válidos (no cumplen un DTD) suelen utilizarse para documentos pequeños que deben escribirse de forma rápida. Los documentos válidos siempre están bien formados y son necesarios cuando son muy extensos o deben procesarse por algún sistema computacional [Gold99].

Sintaxis general de XML

Sintácticamente, un documento XML es una secuencia, flujo o cadena de caracteres usualmente en formato Unicode. Dentro de esa secuencia, los caracteres encerrados entre paréntesis angulares (“<” y “>”) y las referencias de entidades (“&” y “;”) son el marcado, los demás son los datos de carácter.

Dentro del marcado suelen definirse identificadores como nombres de elementos, entidades u otros. Por ejemplo, la línea 3 del listado ej_doc_xml incluye tres identificadores: book, name y version. Los identificadores deben iniciar con una letra y pueden estar seguidos de cero o más letras o los caracteres punto, guión o dígitos. Los identificadores en XML son sensitivos a mayúsculas y minúsculas (case sensitive), esto implica, por ejemplo que book, Book y boOK se tomen como identificadores distintos. El autor puede emplear cualquier identificador válido, excepto aquellos que inician con la cadena “xml” en cualquiera de sus combinaciones de mayúsculas y minúsculas, ya que están reservados para propósitos de estandarización [Gold99].

Varios espacios en blanco, tabuladores y cambios de línea se ignoran a menos que sean cadenas literales, las cuales se escriben entre comillas dobles (") o simples (') o una combinación de ambas, pero con la restricción de que la comilla que cierra la cadena debe ser del mismo tipo de la que abre. Esto implica que un software que procese el listado ej_doc_xml, deberá ignorar los cambios de línea y espacios en blanco que rodean al párrafo en las líneas 16 y 17.

Los documentos XML constan de dos partes: encabezado (head) y cuerpo (body). El encabezado de un documento XML recibe el nombre de prólogo de documento y almacena información que describe al cuerpo del documento, como la versión de XML, el tipo de documento (DTD) al que pertenece, la codificación y otros. El cuerpo del documento XML recibe el nombre de instancia de documento, que contiene los datos reales del documento.

Los elementos se pueden anidar, formando un árbol. Los elementos dentro de otros se llaman elementos hijos y a los contenedores, elementos padres. Sólo puede existir un único elemento raíz, también llamado elemento documento, que contiene a todos los demás [Marc00]. En el listado ej_doc_xml el elemento con identificador book es elemento raíz.

Los elementos se describen utilizando etiquetas. Una etiqueta es el marcado entre un par de signos “menor que” y “mayor que”. Un elemento es la combinación de una etiqueta de inicio (que puede tener atributos), un contenido opcional y una etiqueta de cierre, es decir

<identificador_etiqueta_inicio atributos="valor">
contenido
</identificador_etiqueta_cierre>

Un elemento tiene dos etiquetas, una de inicio y otra de cierre (o fin). La etiqueta de inicio se compone de un carácter menor que (<), un identificador_etiqueta_inicio que es un simple identificador y debe respetar las restricciones de los identificadores; seguido por cero o más parejas atributo="valor"; y un carácter de mayor que (>).

La etiqueta de cierre o etiqueta de fin consta de los caracteres menor que (<) y un slash (/), luego un identificador_etiqueta_cierre que obligatoriamente debe ser el mismo identificador que el de la etiqueta de inicio del elemento y un carácter de mayor que (>). La etiqueta de cierre nunca tiene atributos.

El contenido del elemento puede ser nulo o texto XML, es decir, datos de carácter, otros elementos (y por ende etiquetas) o ambos. Si un elemento no tiene contenido se dice que es un elemento vacío, y se puede escribir en cualquiera de las dos siguientes formas:

<id_elemento atributos="valor"></id_elemento>
<id_elemento atributos="valor"/>

La segunda de las formas anteriores consta de una sola etiqueta llamada etiqueta vacía, la cual finaliza en un slash (/) indicando que el elemento ha terminado. Es poco común encontrar etiquetas vacías sin atributos.

Si un elemento tiene contenido (no es vacío) y se omite alguna de las etiquetas (la de inicio o la de fin) el procesador XML deberá alertar de que el documento no está bien formado.

Debe quedar claro que las etiquetas no son elementos. Las etiquetas inician con un '<' y terminan con un '>', lo demás no son etiquetas [Gold99]. Los elementos tienen etiquetas y contenido. Los elementos se escriben en el documento XML y son instancias de los tipos de elementos, los cuales se declaran en la definición del tipo de documento (DTD).

Los elementos pueden tener atributos que son “una forma de incorporar características o propiedades a los elementos de un documento” [Gold99, 353]. Un atributo se compone de un identificador, un signo de igual (=) y un valor entre comillas (dobles, simples o una combinación de ambas).

El prólogo de documento

El prólogo de un documento suele incluir la declaración de XML y la declaración del tipo de documento, en ese orden; además de otras características como comentarios e instrucciones de procesamiento. Todas son opcionales, pero se aconseja maximizar la cantidad de información en el prólogo ya que ayuda al procesamiento posterior del documento [Gold99]. El prólogo termina cuando abre la etiqueta de inicio del elemento raíz, es decir, donde inicia la instancia de documento.

La declaración XML

La declaración XML indica que el documento usa notación XML e indica la codificación del mismo. Tiene la forma mínima <?xml version="1.0"?> y no posee una etiqueta de cierre. Tiene tres atributos: la versión, la codificación y el documento autónomo:

  • La declaración de versión con el atributo version, indica la versión de XML utilizada en el documento. Normalmente "1.0".
  • La declaración de codificación con el atributo encoding, indica la codificación de caracteres del documento. Aunque los procesadores XML tienden a reconocer automáticamente la codificación, es mejor indicarlo explícitamente, por ejemplo, "UTF-8", "UTF-16" o "iso-8859-1" [Gold99]. Lo importante es que este atributo refleje realmente el tipo de codificación que usó para generar el documento.
  • La declaración de documento autónomo con el atributo standalone no se suele utilizar con frecuencia ni tampoco se recomienda su uso. Su comprensión es compleja y se explica ampliamente por Goldfarb [Gold99].

A modo de ejemplo, la declaración XML en su forma más amplia tiene la siguiente forma:

<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>

La declaración del tipo de documento

El otro elemento que se suele incluir en el prólogo es la declaración de tipo de documento, el cual indica el tipo de documento (expresado por un DTD) que se está utilizando en el documento. Por ejemplo, la segunda línea del listado ej_doc_xml indica que ese documento es de tipo "book":

<!DOCTYPE book SYSTEM "book.dtd">

La declaración DOCTYPE indica el tipo de documento y además instruye al procesador de XML dónde encontrar la definición del tipo de documento (DTD), el cual puede estar escrito en el mismo documento XML o puede encontrarse en una entidad externa (un archivo en disco o red) o una combinación de ambas. Lo más común es que se encuentre en un recurso externo, como se hizo en la segunda línea del listado ej_doc_xml, la cual instruye al procesador XML que el tipo de documento está en el archivo "book.dtd" en la misma ubicación que el documento XML. La palabra SYSTEM indica al sistema que busque el recurso especificado en el URI (Universal Resource Identifier) que le continúa. Más adelante se estudiará la nomenclatura de un DTD.

El identificador del elemento que continúa inmediatamente después de <!DOCTYPE, por ejemplo, book en el listado ej_doc_xml, indica al procesador XML a partir de qué elemento del DTD se debe incluir, es decir, sólo se incluye el subárbol cuya raíz es precisamente ese elemento.

Marcado misceláneo

Además de los elementos y entidades definidos por el autor, a veces es necesario incluir cierto tipo de información que no se puede lograr con estas estructuras. Por esto el autor puede agregar, además, entidades predefinidas, secciones CDATA y los comentarios.

Las entidades predefinidas "son marcas que representan caracteres que de otro modo se interpretarían con un significado especial" [Gold99, 358], como los caracteres menor que (<) y ampersand (&). Existen cinco entidades predefinidas mostradas en la xml_predefined_entities. "Cuando el procesador XML analiza el documento, reemplaza las referencias de entidad por los caracteres reales", en lugar de interpretarlos como caracteres de marcado [Gold99, 359].

Referencia de entidad Valor
&amp; &
&lt; <
&gt; >
&apos; '
&quot; "
Entidades predefinidas en XML.

Aunque el uso de entidades predefinidas parece sencillo, hace que el documento XML se vuelva difícil de leer para el autor, en especial cuando se usan repetidamente, como ocurre al escribir segmentos de código fuente; por eso se crearon las secciones CDATA; las cuales indican al procesador XML que no interprete una parte del texto aunque contenga marcado, es decir, que lo trate como datos de carácter (character data, de ahí la contracción CDATA). La sintaxis de una sección de datos de carácter es

<![CDATA[ contenido ]]>

En el contenido puede aparecer cualquier cadena de texto, excepto la que cierra la sección CDATA (]]>) llamada CDEnd. La línea 29 del listado ej_doc_xml usa dos entidades preestablecidas. Este mismo elemento pudo haberse escrito con una sección CDATA como

<p><![CDATA[
   Su popularidad puede deberse a su "facilidad de uso" y...
]]></p>

Los comentarios son cadenas de caracteres que no son parte de los datos de carácter, es decir, es parte del marcado pero que debe ser ignorado por el procesador XML. Permiten al autor hacer anotaciones que le ayuden a comprender o recordar partes del documento. El texto del comentario se introduce entre los caracteres inicio de comentario (<!--) y final de comentario (-->). Por ejemplo

<!-- Esto es un comentario -->

Todo el texto dentro del inicio y fin de comentario será ignorado, incluyendo los caracteres de marcado (menor que, ampersand, comillas, etc.), excepto la secuencia de cierre del comentario (-->).

Semántica de los elementos XML

Cuando se escribe un documento XML, el autor escoge los identificadores de los elementos, los atributos y la estructura a su gusto y la plasma en un DTD. Cuando una persona observa el marcado de un documento XML y se pregunta “¿qué significa esto?” o bien “¿qué aspecto tendrá después?”, estará haciendo preguntas sobre su semántica. Por ejemplo, si se encuentra una etiqueta <T> esta podría indicar que se trata de un título, una tabla o alguna otra cosa [Gold99].

La computadora no puede inferir la semántica de un documento XML dado, sino que es responsabilidad del autor de la gramática describir esta semántica para que otros autores puedan comprenderlo. Esta descripción se puede realizar por correo electrónico, un documento PDF (Portable Document Format), un libro, o cualquier otro medio de comunicación [Gold99].

Como señala Goldfarb, "lo que le interesa a la computadora es el aspecto que se supone debe tener un elemento cuando está formateado, la forma en que debe actuar si es interactivo o qué hacer después de extraer los datos. Estas cuestiones se encargan de especificarlas las hojas de estilo y los programas informáticos" [Gold99, 350].

La definición del tipo de documento (DTD)

La definición de tipo de documento (DTD, Document Type Definition) es "el conjunto de normas XML para la representación de documentos de un determinado tipo" [Gold99, 49]. En pocas palabras, el DTD describe la gramática del documento [W3C00], es decir, los tipos de elementos, sus atributos, entidades, algunas restricciones y cómo se relacionan entre ellos. Esta sección explica la sintaxis que XML utiliza para representar estos conceptos y sus relaciones en un DTD.

Un documento XML que respete cabalmente las reglas establecidas en su DTD se dice que es válido. A modo de ejemplo, el listado xml_dtd_book muestra el DTD referido en el libro del listado ej_doc_xml llamado book.dtd.

<!ENTITY % SectionType
   "forework|preface|acknowledgements|dedication|contents">

<!ELEMENT book (title, authors, front-cover?, chapter+, back-cover?)>
<!ATTLIST book
   name ID #REQUIRED
   version CDATA #REQUIRED>

<!ELEMENT title (#PCDATA)>

<!ELEMENT authors (author+)>

<!ELEMENT author (fullname, email?)>
<!ATTLIST author
   name ID #REQUIRED
   percent CDATA #IMPLIED>

<!ELEMENT fullname (#PCDATA)>

<!ELEMENT email (#PCDATA)>

<!ELEMENT front-cover EMPTY>
<!ATTLIST front-cover
   img CDATA #REQUIRED>

<!ELEMENT chapter (title?, (p|section)+)>
<!ATTLIST chapter
   name ID #REQUIRED
   type (prelims|contents) "contents">

<!ELEMENT p (#PCDATA|em|strong|code)*>

<!ELEMENT em (#PCDATA)>
<!ELEMENT strong (#PCDATA)>
<!ELEMENT code (#PCDATA)>

<!ELEMENT section (title?, (p|section)+)>
<!ATTLIST section
   name ID #REQUIRED
   type (%SectionType;) "contents"
   author IDREF #IMPLIED>

<!ELEMENT back-cover EMPTY>
<!ATTLIST back-cover
   img CDATA #REQUIRED>

Declaraciones de tipo de elemento

Entre otras cosas, en el DTD se escriben las declaraciones de tipo de elemento que son restricciones que deben respetar todos los elementos en un documento XML válido. Estas declaraciones tienen el siguiente formato

<!ELEMENT IdElemento EspecContenido>

El <!ELEMENT inicia la declaración de un tipo de elemento. El IdElemento es el identificador que aparece en sus etiquetas. Debe ser único en el DTD y respetar las restricciones de los identificadores expuestas en la sección Conceptos XML. La EspecContenido se refiere a la especificación de contenido, que indica qué objetos pueden figurar en el contenido del elemento, hay cuatro posibilidades [Gold99]:

Especificación de contenido de elementos XML.
Tipo Descripción
EMPTY Impide que el elemento tenga contenido y por tanto sus etiquetas son vacías. Ejemplo: front-cover en la línea 22 en el DTD del listado xml_dtd_book.
ANY Puede contener cualquier elemento o carácter, lo cual no se recomienda ya que no es estructurado. Pueden ser útiles cuando se está escribiendo el DTD para lograr que los documentos sean válidos mientras se está refinando dicho DTD.
element-content Sólo puede contener los subelementos listados dentro de paréntesis en la especificación de contenido. Por ejemplo, la línea 26 del listado xml_dtd_book indica que los elementos chapter sólo pueden tener un título opcional y varios párrafos o secciones.
mixed-content Permite al elemento contener subelementos, datos de carácter (#PCDATA) o ambos, como ocurre con el elemento de párrafo p en la línea 31 del listado xml_dtd_book.

Cuando un elemento tiene subelementos (o elementos hijos) en el contenido (últimos dos casos de la lista anterior), es necesario especificar un modelo de contenido (content model), que establece el orden y número de apariciones de los subelementos.

El orden de los elementos hijos se establece con los caracteres coma, barra vertical (|) y paréntesis. Una secuencia de subelementos separados por coma indica que tales subelementos deben aparecer y en el mismo orden. La barra vertical indica que debe aparecer sólo uno de los dos subelementos que están en sus extremos. Los paréntesis agrupan partículas de contenido que son tratadas como unidades por los operadores anteriores, lo que permite combinarlos.

El número de apariciones de los subelementos se especifica con los indicadores de frecuencia que se escriben al final del subelemento, sin espacios en blanco y son tres. El signo de interrogación (?) indica que el elemento hijo es opcional, es decir, puede o no puede aparecer. El asterisco (*) indica que el subelemento puede aparecer 0 ó más veces (opcional y repetible). El signo de más (+) indica que el subelemento puede debe aparecer 1 o más veces (necesario y repetible) [Gold99].

Declaraciones de la lista de atributos

Los atributos “son una forma de incorporar características o propiedades a los elementos de un documento” [Gold99, 353]. Un elemento puede tener una lista de atributos, la cual se declara en el DTD en la sección conocida como declaración de lista de atributos, usualmente después de la declaración del tipo de elemento. Tiene la siguiente sintaxis

<!ATTLIST IdElemento IdAtt TipoAtt DefaultValue>

El <!ATTLIST indica que se va a declarar la lista de atributos del elemento identificado por IdElemento. IdAtt indica el nombre del atributo; debe ser un identificador válido y no es obligatorio que sea único en el documento. El tipo del atributo TipoAtt puede ser alguno de los siguientes

Tipos de atributos en un DTD.
Tipo Descripción
CDATA Datos de carácter. Permite casi cualquier cadena de caracteres.
NMTOKEN Name token. Sólo permite letras, números y algunos caracteres especiales, como los que se utilizan para declarar identificadores, pero no obliga a que sea único en el documento.
NMTOKENS Una lista de name tokens.
(a|b|c|d) Enumerados. La lista de valores permitidos se escribe dentro de paréntesis y separados por barras verticales (|), por ejemplo el atributo type en la línea 29 del listado xml_dtd_book.
ID Identificador. Declara que el atributo es un identificador único, es decir, su valor debe cumplir con las restricciones de los identificadores y en el documento XML no puede haber dos o más atributos con el mismo valor.
IDREF Referencia a un identificador. Declara que el atributo tiene como valor una referencia a un identificador existente. Su valor puede repetirse muchas veces en el documento XML pero debe respetar las restricciones de los identificadores.
ENTITY Referencia a una entidad (o atributos de entidad). El valor del atributo debe ser el nombre de una entidad existente en el documento XML.

El DefaultValue en la declaración de la lista de atributos indica si el atributo se puede omitir o no, y el valor por defecto en caso de que se omita. Puede tomar tres variantes:

Valores por defecto de atributos en un DTD.
Tipo Descripción
#REQUIRED Indica que el atributo es requerido y no se puede omitir, por tanto no tiene un valor por defecto. Son ejemplos name y version de book en el listado xml_dtd_book.
value Un valor que está en el dominio de valores permitido por el tipo de atributo. Indica que ese será el valor que el procesador XML tome si el autor no especifica uno en el documento. Por ejemplo, según la línea 29 del listado xml_dtd_book, si el autor no especifica el tipo del capítulo, el procesador XML asumirá que se trata de un capítulo de contenido y no uno preliminar.
#IMPLIED Permite al autor omitir el valor del atributo pero sin forzar a escribir un valor por defecto determinado. El procesador XML asignará algún valor que considere adecuado o lo ignora.

Elementos versus atributos

No es trivial decidir qué aspectos del mundo real deben modelarse como elementos y cuáles como atributos. Tampoco existe un diseño mejor que otro. El autor debe escoger alguna convención que sea coherente y escribir un DTD para obligar a todos los documentos del mismo tipo a cumplir con el diseño escogido. Aunque no hay ninguna regla para decidir qué debe modelarse como un elemento o un atributo, Goldfarb sugiere las siguientes ideas.

Los atributos no pueden contener “subatributos” ni elementos, sino que son pequeños trozos de texto sin estructura o listas de condiciones. Por tanto, si algo del mundo real contiene otras partes que también deben modelarse, debe hacerse con elementos y no con atributos. Los atributos se usan para añadir poca información, sencilla y sin estructura [Gold99].

Los elementos deben respetar el orden y el número de ocurrencias descritos en el DTD, mientras que los atributos pueden aparecer en cualquier orden y no pueden repetirse. Por esto, las “cosas” que deben repetirse o que respetan cierto orden deben modelarse como elementos y no como atributos [Gold99].

Si las ideas anteriores no son criterio suficiente, puede usarse la siguiente heurística. Los elementos se utilizan para representar partes de los objetos, o datos que son parte del contenido principal, y deben aparecer en todas las impresiones o interpretaciones del documento. Los atributos se utilizan para representar propiedades de los objetos, es decir, información sobre los datos reales (metadatos) y que no necesariamente debe imprimirse [Gold99].

Entidades XML

Las entidades “permiten al documento dividirse en varios objetos de almacenamiento y son herramientas importantes para volver a utilizar y mantener el texto... Una entidad es como una abreviatura y se utiliza como una forma corta de algunos textos. La abreviatura se llama nombre de la entidad y la forma larga contenido de la entidad. El contenido puede ser tan corto como un carácter o tan largo como un capítulo” [Gold99, 389].

Las entidades se utilizan con referencias de entidades, cuya sintaxis consiste en escribir el nombre de la entidad entre los caracteres ampersand (&) y punto y coma (;). Cuando el procesador XML encuentra una referencia de entidad, la reemplaza por el contenido de la misma, proceso conocido como inclusión. Las entidades se crean con declaraciones de entidad, con una de las dos siguientes notaciones, donde los corchetes indican opcional:

<!ENTITY [%] IdEntidad "contenido">
<!ENTITY [%] IdEntidad SYSTEM url [NDATA formato]>

Hay tres criterios para clasificar entidades y por tanto, 8 combinaciones de las cuales 5 son válidas. Los criterios son

  • Analizables o no analizables
  • Internas o externas
  • Generales o parámetro

Las entidades cuyo contenido es texto XML que debe ser analizado por el procesador XML reciben el nombre de entidades analizables (parsed entities). Si el contenido de la entidad es código ajeno a XML (como imágenes o texto puro) que no debe analizarse, se denomina entidades no analizables. Las entidades no analizables terminan en NDATA seguido por un indicador del formato, como ocurre con cedula en el listado xml_entity_examples. Las entidades que no se declaran con NDATA son analizables.

Las entidades externas son aquellas cuyo contenido está en un recurso fuera de la declaración de la entidad, y por tanto debe proveerse un URL hacia ese recurso precedido por la palabra SYSTEM o PUBLIC, como ocurre con cap01 y cedula en el listado xml_entity_examples. Mientras que el contenido de las entidades internas está escrito en la misma declaración de la entidad entre comillas, como ocurre con adn y frase en el listado xml_entity_examples.

Reciben el nombre de entidades generales aquellas que se pueden referenciar en cualquier lugar del documento XML, como ocurre con &adn; del listado xml_entity_examples y las entidades predefinidas, como &amp; que son declaradas por el procesador XML. Las entidades que sólo se pueden referenciar dentro del DTD se conocen como entidades parámetro. Es decir, son de uso exclusivo del DTD y no se pueden referenciar desde el documento XML. Se declaran anteponiendo un % y se referencian usando la notación %IdEntidad; en lugar de &IdEntidad; como ocurre con %SectionType; en las líneas 1 y 40 del listado xml_dtd_book.

<!ENTITY adn "ácido desoxirribonucleico">
<!ENTITY frase "si <emp>no</emp> tienes una solución sos parte de lo que criticas">
<!ENTITY cap01 SYSTEM "http://www.misitio.com/libro/cap01.xml">
<!ENTITY cedula SYSTEM "cedula.gif" NDATA GIF>
<!ENTITY % SectionType "forework|preface|acknowledgements|dedication|contents">

Los identificadores externos “hacen referencia a información que se encuentra fuera de la entidad en la que tienen lugar” [Gold99, p404]. Existen dos tipos, los identificadores de sistemas (SYSTEM) y los identificadores públicos (PUBLIC).

Los identificadores de sistemas utilizan la palabra clave SYSTEM y se refieren a un objeto por su localización utilizando un URI (Universal Resource Identifier). Pueden ser direcciones absolutas o relativas a la ubicación de la entidad documento que contiene el identificador. La siguiente declaración referencia el recurso "http://www.serv.com/dir/cap02.xml":

<!ELEMENT cap02 SYSTEM "http://www.serv.com/dir/">

Los identificadores públicos utilizan nombres declarados públicamente para referirse a la información. Estos nombres deben ser únicos en el mundo. Tienen cuatro partes separadas por dos slash (//). La primera parte es un carácter de suma (+) si la organización que publica el recurso está registrada en el ISO, sino es un carácter de menos (-), lo cual ocurre con mayor frecuencia. La segunda parte es el dueño del recurso. La tercera parte es la descripción del recurso, que permite espacios en blanco. La última parte es el lenguaje en que está escrito el recurso [Marc00]. Los dos siguientes ejemplos de declaraciones de tipo de documento emplean identificadores públicos. El primero de ellos es ficticio.

<!DOCTYPE book
   PUBLIC "-//ECCI-UCR//DTD book//EN"
   "http://www.ecci.ucr.ac.cr/dtd/book.dtd">

<!DOCTYPE html
   PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

Espacios de nombres (namespaces)

La familia de tecnologías XML

Las secciones anteriores discutían sobre el núcleo de la especificación del XML 1.0, llamado XML Core, El W3C y otras organizaciones continuaron extendiendo la especificación original, definiendo estándares que en conjunto se conocen como la familia de tecnologías XML [Arci02], cuyo estudio sobrepasa los límites de este documento. En los siguientes títulos se citan algunas de las más importantes tecnologías agrupadas en categorías.

Modelaje y validación avanzada

Informalmente, un esquema es un documento que describe otro documento. Por ejemplo, un DTD X es un esquema que describe la estructura de los documentos que son de tipo X. Los DTD son relativamente simples, ampliamente utilizados, flexibles y extensibles, pero tan pronto como se utilizan, se hace claro sus inconvenientes: no permiten asociaciones y restricciones complejas entre los datos, no poseen tipos de datos y no utilizan notación XML, entre muchos otros. Por esto, el grupo de trabajo en XML dio origen a la notación llamada XML Schema, que permite al autor incorporar más restricciones sobre los documentos XML [Walm02].

Además del XML Schema, se han desarrollado otros lenguajes con alto grado de éxito pero que no son oficialmente apoyados por el W3C. Ejemplos son, Schematron y RELAX NG (Regular Language description for XML) [Arci02].

Interfaces de programación

Cuando un sistema planea utilizar tecnología XML, debe implementar un módulo que se encarga de leer o escribir documentos XML. Este módulo no es de fácil desarrollo, más si se quiere que soporte la especificación formal del XML y tecnologías estándar como las descritas en los párrafos anteriores. Además, si cada sistema que requiere XML en el mundo implementara ese módulo, habría trabajo repetitivo considerable y costoso.

El módulo que se encarga de procesar archivos XML debe aislar a la aplicación del usuario de los detalles técnicos del XML. Este módulo comúnmente se denomina XML parser (analizador XML) y existen muchos gratuitos y de código abierto. Si cada uno tuviera su propia interfaz, obligaría al desarrollador a revisar la documentación del parser que vaya a emplear y, si posteriormente quisiera cambiar de parser, tendría que estudiar el nuevo parser y reescribir su programa para adaptarlo. Por esto, un acuerdo mutuo entre programadores de XML ha dado origen a estándares de interfaz entre la aplicación y el parser. Hay dos API (Application Programming Interface) estándar: SAX y DOM.

El Simple API for XML (SAX) especifica los objetos y sus funciones, tanto los que debe implementar el parser como la aplicación, utilizando el modelo de eventos, el cual funciona de la siguiente forma. El parser recorre el documento XML carácter por carácter, identificando elementos, atributos, datos de carácter, referencias de entidad, etc. Cada vez que el parser identifica uno de esos componentes léxicos, actúa como si fuese un evento y realiza un llamado a la aplicación para que lo atienda, pasándole como parámetros los lexemas identificados. SAX es un enfoque bastante rudimentario pero es muy eficiente.

El segundo API estándar es el Document Object Model (DOM) que se basa en el modelo de documento, esto es, el parser recorre el documento XML construyendo una estructura jerárquica compuesta de nodos, los cuales pueden ser elementos, atributos o datos. Cuando se ha terminado de analizar el documento XML, el árbol de objetos nodo estará construido y se pasará como parámetro a la aplicación, la cual podrá utilizarlo para sus propios fines [Gars02].

El lenguaje extensible de hojas de estilo (XSL)

Un documento XML sólo sirve para representar datos y su estructura. El estándar XML define una familia de lenguajes llamada lenguaje extensible de hojas de estilo (XSL, Extensible Stylesheet Language), que permiten a los autores dar formato a los documentos XML o transformarlos en otros documentos como (X)HTML o PDF (Portable Document Format). XSL se compone de los siguientes tres lenguajes.

  1. XSLT. Es un lenguaje que permite transformar un documento XML en otro documento de texto, como (X)HTML.
  2. XSL-FO. Es un lenguaje para especificar el formateo visual de un documento XML.
  3. XPath. Un lenguaje de consultas para seleccionar partes (nodos) de un documento XML. Es usado principalmente por XSLT.

Transformaciones XSL (XSLT)

Para transformar un documento XML a otro documento de texto, como (X)HTML, CSV (Comma-Separated Values file), texto puro u otro tipo de documento XML; el autor puede escribir una hoja de estilos, que es un conjunto de instrucciones o reglas en notación XSLT (Extensible Stylesheet Language Transformations) que indican cómo se debe transformar cada pieza de información (nodo) presente en el documento XML. Un software llamado Procesador XSLT (XLST Processor) toma estos dos documentos por entrada, aplica las reglas de la hoja de estilos al documento XML y el resultado es el documento transformado. Este proceso se ilustra en la figura siguiente.

Proceso de transformación XSLT

En general el proceso de transformación es como sigue. El procesador XSLT analiza el documento XML y genera un árbol de nodos conocido como DOM (Document Object Model). Luego procesa la hoja de estilos. Cada regla definida en ella afecta a uno o varios nodos del documento. Una regla consta de dos partes: una consulta (query) que sirve para identificar a cuáles nodos debe aplicarse, y una operación que debe efectuarse en esos nodos. La consulta puede asemejarse a otros lenguajes de consulta como SQL (Structured Query Language), pero emplea una notación especial llamada XPath, mencionada anteriormente. La operación consta de texto literal que aparecerá en la salida o de trozos del elemento al que se le está aplicando la regla. La concatenación de todas las salidas constituye el documento transformado que desea el autor.

La forma de invocar al procesador XSLT varía dependiendo de la implementación. Las hay en forma de ejecutables en línea de comandos, como bilbiotecas compartidas (.so, .dll), incorporados en un navegador o una mezcla de ellas. Por ejemplo, libxslt del proyecto GNOME puede usarse como una biblioteca compartida desde cualquier programa, o en línea de comandos como se muestra en el siguiente ejemplo:

xsltproc libro_web.xsl libro.xml > libro.html

La mayoría de navegadores web actuales implementan un procesador XSLT internamente, sin embargo no hay forma de indicarles en un URL la ubicación del documento XML y la hoja de estilos simultáneamente. Pero si el autor indica en el prólogo del documento XML cuál hoja de estilos quiere que se aplique, el navegador hará la trasformación y tratará de desplegar (rendering) el resultado directamente. Un documento XML declara su hoja de estilos con la instrucción de procesamiento xml-stylesheet, por ejemplo:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book SYSTEM "book.dtd">
<?xml-stylesheet href="book_web.xsl" type="text/xsl"?>

<book name="MiLibro" version="1.0">
   <!-- ... -->
</book>

Formateo de objetos (XSL-FO)

Ejercicio. Recuerde uno o varios softwares que haya programado -o quiera programar- donde se requería escribir o leer archivos en un formato propio y no usó XML. Represente la misma información con XML: escriba unos archivos de ejemplo y un DTD. Compare las ventajas del formato propio contra el formato XML. ¿Cuál de los dos prefiere?

Ejercicio. Haga una comparación de las ventajas y desventajas de utilizar XML para representar algún tipo de información, contra otros documentos de texto y bases de datos.

Ejercicio. Investigue la diferencia entre un conjunto de caracteres (character set) y la codificación del texto (text enconding). ¿Cómo esto afecta a los documentos XML?

Ejercicio. Represente en un documento XML la base de datos Universidad cuyo estado se encuentra en las siguientes tablas y su estructura en el siguiente modelo entidad-relación.

Una base de datos Universidad
Estudiante
Carné Nombre Promedio
a45891 Eliécer Montero 7.14
a98765 Yanet Arias 8.96
b10976 Anais Solano 8.30
Profesor
Cédula Nombre
109830876 Edgar Casasola
208431187 Maureen Murillo
135900821 Francisco Arroyo
110101101 Jeisson Hidalgo
Curso
Sigla Nombre Créditos
CI-1101 Programación I 4
CI-1201 Programación II 4
CI-1320 Redes de Computadoras I 5
CI-2413 Desarrollo de aplicaciones web 4
Requisito
Curso Requisito
CI-1201 CI-1101
CI-2413 CI-1201
CI-2413 CI-1320
Grupo
Curso Numero Anno Semestre Horario Aula Profesor
CI-1101 04 2011 1 KV13-15 301IF 109830876
CI-1101 08 2011 1 KV07-09 304IF 208431187
CI-1320 01 2011 1 KV17-19 303IF 135900821
CI-2413 01 2011 1 KV15-17 305IF 110101101
Matricula
Estudiante Curso Grupo
a45891 CI-1101 04
a45891 CI-1320 01
a98765 CI-1320 01
b10976 CI-2413 01

Ejercicio. Escriba una definición de tipo de documento para representar la base de datos Universidad. Asegúrese de que el documento que escribió en el ejercio anterior sea válido ante este DTD.

Ejercicio. Si no lo hizo, trate de desnormalizar las relaciones Requisito y Matricula en su documento XML. Es decir, estas relaciones no deben aparecer como una lista de elementos homogénea, sino como hijos de otros elementos. Actualice el DTD y asegúrese de que el nuevo documento XML sea también válido. ¿Se puede también desnormalizar la entidad débil Grupo?

El lenguaje de marcado de hipertexto (X)HTML

El lenguaje HTML nació como una aplicación SGML en 1991 y su mayor volumen de estandarización fue en la versión 4.0 de 1997. En 1998 el W3C publicó una simplificación de SGML llamada XML. En el 2000 el W3C publicó XHTML 1.0 como una adaptación de HTML sobre XML y actualizó HTML a 4.01 con el fin de que fuesen cercanamente compatibles. Después de ello, parece que el W3C ha centrado su atención en XHTML restando trabajo a HTML. El desarrollador web se puede estar preguntando cuál de estos dos lenguajes escoger para un sitio en que va a trabajar. Necesitará entender las diferencias para fundamentar su decisión.

HTML es bastante liberal, mientras XHTML es inherentemente estricto. El listado <a> muestra un trozo de código HTML y el listado <b> el mismo contenido en XHTML. Las principales diferencias entre estos dos lenguajes son las siguientes.

  1. Los identificadores en HTML no son sensitivos a mayúsculas ni minúsculas; mientras que en XHTML lo son y los definidos por la recomendación usan minúsculas consistentemente.
  2. HTML permite dejar elementos sin cerrar como p y li, otros nunca se cierran como br e img; mientras que en XHTML todo elemento debe cerrarse obligatoriamente.
  3. HTML permite minimizar atributos (attribute minimization), esto es, omitir las comillas alrededor de los valores de atributos que sólo contienen letras, números o los caracteres guión (-), punto (.), guión bajo (_) o dos puntos (:). XHTML exige que todo valor de atributo debe estar encerrado entre comillas.
  4. HTML permite que algunos atributos no tengan valor como noshade en <hr width="50%" noshade>, la presencia del atributo es suficiente para generar su efecto. XHTML exige que todo atributo tenga un valor entre comillas, y para estos casos, el Consorcio Web tomó la convención de repetir el nombre del atributo en su valor, ejemplo: <hr width="50%" noshade="noshade"> [Cast06].

Es evidente que escribir HTML es más relajante que XHTML, si ambos producen el mismo efecto en el navegador ¿por qué molestarse con XHTML? HTML obliga al navegador a programar excepciones y reglas circunstanciales que dilatan el procesamiento y propician a la ambigüedad. XHTML obliga a una sintaxis restringida que facilita al navegador y otros software XML a interpretar código fácil y eficientemente. Además XML abre un conjunto de funcionalidades y tecnologías que no están disponibles para HTML, como la posibilidad de incrustar otras especificaciones como SVG y MathML en los documentos XHTML.

Si una persona piensa escribir algunas páginas manualmente, quizá HTML sea la mejor alternativa. Si planea que esas páginas formen parte de un sitio web grande, sean almacenadas en bases de datos o procesadas por algún tipo de software, es mejor que elija XHTML. En general se cumple que código XHTML válido es también código HTML válido, no el recíproco. Por eso este capítulo conferirá énfasis a XHTML y las diferencias importantes con HTML se resaltarán en su contexto.

Estructura global

Un documento web es muy similar a cualquier otro documento que una persona podría escribir en un procesador de palabras, y por ende tiene títulos, párrafos, tablas, imágenes, enlaces, estilos y otros elementos familiares. La principal diferencia es que un documento web debe seguir estrictamente la sintaxis de (X)HTML. Al hacerlo, cualquier software (X)HTML podrá manipular el documento, sea un navegador, un programa de diseño, un motor de bases de datos y hasta el mismo procesador de palabras mencionado anteriormente. Dado que el navegador es el más común, se usará en este texto por claridad en lugar de software (X)HTML.

Un documento web se compone de cuatro partes: la declaración del tipo de documento, el elemento documento, el encabezado y el cuerpo. El listado html_outline muestra un ejemplo de documento web con estas cuatro partes. Los comentarios utilizan la misma notación de XML y pueden aparecer en casi cualquier lugar del documento.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>Título del documento</title>
</head>

<body>
   <!-- Cuerpo del documento -->
</body>
</html>

La declaración del tipo de documento

La declaración del tipo de documento, expresada con la etiqueta <!DOCTYPE ...>, informa al navegador el tipo de documento (HTML o XHTML) y la versión usada en el resto del documento. Con esto el navegador sabrá cómo interpretar la sintaxis y contra cual especificación validar su código. La tabla html_doctypes muestra los tipos de documentos y sus versiones actualmente en uso. [Para una lista completa que incluye tipos de documentos para XHTML-Basic y XHTML Mobile Profile, consulte http://en.wikipedia.org/wiki/DOCTYPE].

Tipos de documento (X)HTML de acuerdo a su versión y variación
Language Ver. Variation Document type
HTML 4.01 strict <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
transitional <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
frameset <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"http://www.w3.org/TR/html4/frameset.dtd">
XHTML 1.0 strict <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
transitional <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
frameset <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
1.1 -- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
(X)HTML 5.0 -- <!DOCTYPE html>

Aunque en HTML es opcional, siempre es conveniente incluir el tipo de documento; y a menos de que se esté usando (X)HTML5 es difícil memorizarlos. Por eso los autores históricamente han tenido una fuente de la cual copiar y pegar los tipos de documentos en sus creaciones, o confiar en un programa de diseño web que haga el trabajo automáticamente.

Si se omite la declaración del tipo de documento, la mayoría de navegadores entran en "quirks mode", un modo de compatibilidad para poder desplegar páginas web obsoletas que contienen errores o "quirks", con resultados dependientes del navegador. Si la declaración del tipo de documento está presente, el navegador entra en modo estándar, y si el documento es válido, el comportamiento debería ser homogéneo entre navegadores.

El W3C ofrece varias herramientas gratuitas para asegurar la calidad (QA, Quality Assurance) de un sitio web. Entre ellas el autor puede someter cualquier documento al W3C's Unified Validator (Unicorn) y recibir retroalimentación de si está bien formado, se conforma al tipo de documento que tiene declarado, no tiene enlaces rotos y los estilos que usa son válidos.

Dado que XHTML es una aplicación XML, todo documento XHTML debe iniciar con la especificación XML como se ve en el código de abajo. El W3C recomienda esta práctica, sin embargo, algunas versiones antiguas de navegadores pueden confundirse y entrar erróneamente en "quirks mode", por lo que muchos autores omiten la especificación XML en sus documentos XHTML.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
...
</html>

Al omitir la declaración XML se pierde la oportunidad de informar sobre la codificación del documento. Como un rodeo, los autores lo hacen en el encabezado (X)HTML con un elemento de metainformación, como se hizo en la línea 6 del listado html_outline.

El elemento documento

El elemento documento o elemento raíz de un documento (X)HTML es html. Normalmente se escribe sin atributos, lo cual es válido tanto en HTML como XHTML. Su único atributo exclusivo en XHTML es el espacio de nombres xmlns, el cual sirve para indicar que sus identificadores pertenecen al espacio de nombres de XHTML y así evitar que colisionen con otras especificaciones. Los demás atributos son los comunes para casi todos los elementos XHTML, mostrados en el cuadro [tab:AtributosComunesXHTML], a excepción de class. El elemento raíz html sólo admite dos elementos hijos: un encabezado (head) y el cuerpo del documento (body).

Atributos comunes para la mayoría de elementos (X)HTML
Atributo Descripción
id Un nombre que identifica de forma única a un elemento, de tal forma que se le puede hacer referencia posteriomente, en especial en hojas de estilo y programas de JavaScript.
class Sirve para agrupar varios elementos bajo un identificador común. A todos los elementos de una misma clase se les puede aplicar estilos o acceder desde un programa de JavaScript. Por su parte, un elemento puede pertenecer a varias clases, las cuales se separan por espacios en blanco en el valor de este atributo.
xml:lang Indica el idioma en el que se encuentra el contenido del elemento. Su valor es un código de idioma en el estándar [http://www.loc.gov/standards/iso639-2/php/code_list.php||ISO-639]. Sólo está disponible en XHTML. Tiene prioridad sobre lang.
lang Igual que xml:lang. Está disponible tanto en HTML como XHTML.
dir La dirección del texto que se encuentra en el contenido del elemento. Puede tomar tomar los valores ltr para izquierda a derecha y rlt para derecha a izquierda.

Encabezado del documento

El encabezado del documento (X)HTML contiene información sobre el documento mismo pero que no es considerado parte del contenido, a lo que frecuentemente se le llama "metadatos" y son útiles para los navegadores o motores de búsqueda. Los elementos que pueden aparecer en el encabezado son: el título del documento, metadatos, hojas de estilo, programas de JavaScript y otros objetos. En esta sección se estudiarán los dos primeros.

El título del documento se escribe como un texto simple en el contenido del elemento title y es obligatorio en XHTML. No es parte del contenido del documento porque las personas lo usarán antes de tener acceso a él. Por eso, es uno de los textos más importantes y siempre debe escogerse con cuidado, tratando de que sea corto y descriptivo. Aparece en la barra de título del navegador y en los resultados de los motores de búsqueda como Google. Los lectores lo verán en el historial de su navegador y con suerte en sus favoritos.

Los metadatos son detalles sobre el contenido del documento como el autor, palabras clave, codificación del texto, etc. Se escriben en forma de parejas atributo-valor con el elemento vacío meta. El nombre del metadato se escribe en el atributo name y el valor del metadato en el atributo content. Si el nombre del atributo es el mismo que una de las directivas del encabezado HTTP se puede escribir en el atributo http-equiv en lugar de name. Cuando un documento web es solicitado, el servidor web puede revisar el encabezado por estos metadatos http-equiv y agregar las respectivas directivas en el encabezado del mensaje de respuesta HTTP (HTTP Response header) que precede al documento.

La recomendación (X)HTML no define un conjunto de valores válidos para ninguno de los atributos de meta, sino que permite libremente construir perfiles (profiles) de metadatos. El listado xhtml_header_example muestra un ejemplo de encabezado con título y metadatos comunes. Para más detalles, revísese la especificación HTML 4.01. En versiones más recientes de (X)HTML, el W3C ha adoptado una notación más elaborada para la especificación de metadatos llamada Marco de descripción de recursos (RDF, Resource Description Framework).

<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <meta name="author" content="Jeisson Hidalgo-Céspedes" />
   <meta name="copyright" content="&copy; 2011 ECCI-UCR" />
   <meta name="keywords" content="desarrollo web, curso, evaluación" />
   <title>Carta al estudiante</title>
</head>

Cuerpo del documento

El cuerpo del documento contiene el contenido del documento. Esto es lo que ve o escucha el usuario una vez que se ha cargado. El contenido del elemento body puede ser texto puro en HTML, pero normalmente es una mezcla de los elementos que se explican en el resto de secciones de este capítulo.

Elementos de texto

En esta sección se presentarán los elementos más comunes para estructurar el texto del documento web: encabezados de secciones (títulos), párrafos, texto preformateado, texto estructurado y listas de elementos.

Encabezados de secciones

Los autores suelen dividir sus creaciones en secciones temáticas. En (X)HTML no hay un elemento para especificar una sección temática completa, pero sí para delimitar el título o encabezado de cada sección. (X)HTML define 6 niveles de encabezados en orden descendente de importancia h1, h2, ..., h6. El navegador u otro software podría usar los encabezados, por ejemplo, para construir una tabla de contenidos automáticamente.

El formato de cada encabezado es dependiente del navegador. Es usual que empleen tamaños más grandes y mayor peso para que sean más vistosos. En general esto ocurre con todos los elementos visuales de (X)HTML y es bueno que el navegador escoja su formato por defecto. De esta forma, el autor se concentra en escribir el contenido del documento y el navegador escoge el formato ideal para un medio específico, por ejemplo, una limitada pantalla de un dispositivo móvil. Sin embargo, (X)HTML permite al autor controlar el formato utilizando hojas de estilo como se estudiará en el capítulo siguiente.

Es habitual que la primera actividad que realice un autor sea definir la estructura temática de su documento antes de empezar a escribir el contenido de cada sección. El listado xhtml_section_headings muestra los encabezados de una carta al estudiante de un curso de programación web. Un abuso de indentación se hizo para resaltar la jerarquía de niveles de los encabezados de sección.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es">
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>Carta al estudiante</title>
</head>

<body>
   <h1>Carta al estudiante</h1>
      <h2>Descripción</h2>
      <h2>Objetivos</h2>
         <h3>Objetivo general</h3>
         <h3>Objetivos específicos</h3>
      <h2>Contenido</h2>
      <h2>Metodología</h2>
      <h2>Evaluación</h2>
      <h2>Bibliografía</h2>
</body>
</html>

Párrafos

Un párrafo en (X)HTML se escribe con el elemento p como se aprecia en el listado xhtml_paragraphs_example. Aunque en HTML es opcional cerrarlo, es recomendable siempre hacerlo. A través de hojas de estilo se puede controlar el espaciado entre párrafos, la sangría y otros formatos. No escriba párrafos vacíos <p></p> para tratar de dejar espacio en blanco en el documento, la especificación (X)HTML recomienda al navegador ignorar estos elementos.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es">
<head>
	<title>Programación en C++</title>
</head>

<body>
<p>
	Universidad de Costa Rica<br />
	Escuela de Ciencias de la Computación e Informática<br />
	CI1201 - Programación II
</p>
<hr>

<h1>Introducción a la programación</h1>
<p>La computadora es un artefacto electro-matemático que puede ejecutar una secuencia[...]</p>

<h2>Hola mundo en C++</h2>
<p>
	Desde el libro de Kernighan y Ritchie se ha seguido la tradición del primer programa a
	mostrar sea uno simple: escribir la cadena "Hola mundo" en la pantalla o algún otro
	medio. La forma de decir "Hola mundo en C++" se aprecia en el siguiente código:
</p>
<pre>
#include &lt;iostream&gt;

int main()
{
    std::cout &lt;&lt; "Hola mundo" &lt;&lt; std::endl;
}
</pre>
</body>
</html>
Párrafos y texto preformateado en (X)HTML. Correr este ejemplo.

A veces se quiere insertar un cambio de línea sin iniciar otro párrafo, por ejemplo, en un poema los versos se separan por cambios de línea y las estrofas por párrafos. Un cambio de línea se representa con el elemento vacío <br />, y se aconseja dejar el espacio en blanco antes de la barra inclinada que cierra la etiqueta, con el fin de evitar problemas con navegadores viejos que no implementan XHTML.

Cuando la longitud de un párrafo excede el ancho de la ventana, el navegador inserta cambios de línea automáticos (word wrap). Cuando se quiere evitar que el navegador por esta característica separe dos palabras, estas deben separarse por un espacio no divisible (not breaking space) obtenible por la referencia de entidad &nbsp;.

El hecho de que (X)HTML ignore los cambios de línea que usted escribe, no es práctico para escribir código fuente u otros tipos de escritos donde el espaciado en blanco es relevante. Para esas situaciones (X)HTML provee el elemento de texto preformateado pre, el cual instruye al navegador mantener los espacios en blanco, usar una fuente monoespaciada y desactivar el ajuste automático de línea (word wrap). La especificación recomienda reemplazar tabuladores por 8 espacios en blanco, lo cual muchas veces resulta molesto, por eso, es recomendable utilizar espacios en blanco en lugar de tabuladores en las secciones pre.

El navegador intentará interpretar el marcado (X)HTML que escriba dentro de un elemento pre, por esto debe al menos reemplazar los tres caracteres especiales (<, > y &) con sus referencias de entidad respectivas (&lt;, &gt; y &amp;), o escribir el texto dentro de una sección CDATA si su documento es XHTML [Desdichadamente la mayoría de navegadores actuales ignoran las secciones CDATA].

Un elemento hr (hard return) crea una línea horizontal que sirve para separar párrafos tratando de hacer una separación más visible entre ellos, por ejemplo, para separar los cambios temáticos entre estrofas de un poema. Es un elemento vacío y su apariencia debe ser estrictamente controlada con estilos.

Texto estructurado

(X)HTML define los elementos de frase (phrase elements) para agregar información a fragmentos de texto. Son elementos que afectan no a un párrafo sino sólo a algunas palabras de un párrafo, por ejemplo, denotan énfasis en una palabra, un trozo de código, una abreviatura, etc. La siguiente tabla lista los elementos de frase definidos en (X)HTML 4.01

Elementos de frase en (X)HTML
Elemento Descripción
abbr Indica una abreviatura, ej.: WWW, HTTP, etc., PhD. Son siglas o palabras reducidas.
acronym Indica un acrónimo, ej.: sonar, codec, JSON. Son abreviaturas que se leen como si fueran una palabra normal.
cite Indica una cita o referencia a otras fuentes. No soportado en la mayoría de navegadores.
code Indica un fragmento de código informático.
dfn Indica un término que se está definiendo.
em Indica énfasis.
kbd Indica que el texto debe ser ingresado o tecleado por el usuario.
samp Indica salida de programas informáticos.
strong Indica mayor énfasis.
var Indica una variable de un programa informático.

Los dos elementos de frase de uso más común son em y strong. Normalmente los navegadores los despliegan en itálicas y negritas respectivamente, pero se puede alterar con hojas de estilo. Los demás elementos de frase están orientados a documentos técnicos. El listado xhtml_phrase_elements_example muestra el uso de alguno de estos elementos.

<p>
   Se dice que <em>las entidades</em> describen la <strong>estructura física</strong>
   y <em>los elementos</em> la <strong>estructura lógica</strong> de los documentos
   <abbr title="Lenguaje de marcado extensible (eXtensible Markup Language)">XML</abbr>.
   La estructura lógica (elementos) y física (entidades) se representa dentro del
   documento <abbr title="Lenguaje de marcado extensible (eXtensible Markup Language)">
   XML</abbr> agregando <strong>el marcado</strong>, el cual
   se delimita de <em>los datos de carácter</em> encerrando la descripción de los
   elementos dentro de paréntesis angulares ("<code>&lt;</code>" y "<code>&gt;</code>"),
   que se conocen como <dfn>etiquetas</dfn> y <em>las referencias a entidades</em> entre
   el signo "<code>&amp;</code>" y el punto y coma ("<code>;</code>"). Es decir, el
   texto encerrado dentro de estos cuatro caracteres se conoce como <dfn>marcado</dfn>,
   lo restante como <dfn>datos de carácter</dfn>. La combinación del <em>marcado</em>
   más los <em>datos de carácter</em> son el <dfn>texto XML</dfn> [Gold99].
</p>
Elementos de frase en (X)HTML. Correr este ejemplo.

Todos los elementos de frase tienen un atributo title, en el cual se puede escribir información adicional que aparece cuando hay cierta interacción con el elemento, normalmente un "tooltip" cuando el puntero del ratón pasa sobre ellos. Esto es útil para expandir el significado de las abreviaturas y acrónimos sin tener que incluir estas definiciones explícitamente en el texto cada vez que se quiere usar la abreviatura. Nótese que esto se hizo en el listado xhtml_phrase_elements_example para la abreviatura "XML" que aparece dos veces, lo que genera redundancia de código. Teóricamente esta redundancia se puede evitar en XHTML definiendo entidades generales en el parámetro interno del tipo de documento, sin embargo, los navegadores actuales las ignoran, por lo que la mayoría de autores prefieren hacer algún tipo de programación del lado del servidor antes de despachar la página web.

Los elementos sub y sup sirven para declarar subíndices y superíndices respectivamente. Aunque tienen utilidad matemática y en otras notaciones científicas, realmente se mantienen en la recomendación para otros textos. Ejemplos:

Subíndices y superíndices en (X)HTML
Texto Código (X)HTML
El 1ro de la clase El 1<sup>ro</sup> de la clase
May 3rd, 2011 May 3<sup>rd</sup>, 2011
C6H12O6 C<sub>6</sub>H<sub>12</sub>O<sub>6</sub>

Listas

(X)HTML permite definir listas de ítemes. Las hay en tres tipos: listas ordenadas, listas no ordenadas, y listas de definiciones. Las tres se pueden anidar. El siguiente listado muestra un ejemplo de los tres tipos de listas y su anidamiento:

<h1>Puesto: Desarrollador web</h1>
<h2>Requisitos:</h2>
<ul>
   <li>Conocimiento de estándares web</li>
   <li>Programación en PHP o JSP</li>
   <li>DBMS libres: PosgreSQL, MySQL o SQLite</li>
   <li>Afiliado al colegio respectivo</li>
</ul>

<h2>Procedimiento de reclutación</h2>
<ol>
   <li>Llenar la fórmula RE-TI-C-2348 a mano</li>
   <li>Presentarla en las oficinas centrales</li>
   <li>Si es seleccionado:
      <ol>
         <li>Presentarse a realizar el examen RE-TI-E-23</li>
         <li>Presentarse a realizar el examen RE-LG-E-02</li>
         <li>Si es seleccionado: presentarse a trabajar</li>
      </ol>
   </li>
</ol>

<h2>Definiciones</h2>
<dl>
   <dt>DBMS</dt><dd>Database Management System</dd>
   <dt>RE-TI-C-2348</dt><dd>Fórmula de cualidades para TI</dd>
   <dt>RE-TI-E-23</dt><dd>Examen de tecnologías web</dd>
   <dt>RE-LG-E-02</dt><dd>Examen básico de inglés</dd>
</dl>
Listas de elementos en (X)HTML. Correr este ejemplo.

Las listas ordenadas se escriben con el elemento ol (ordered list). Sirven para indicar que los ítemes en la lista siguen cierto orden, como en una serie de pasos a ejecutar o cuando se quiere simplemente enumerar ítemes. Cada ítem de la lista se escribe dentro del elemento li (list item). Los navegadores les anteponen números arábigos por defecto, pero con estilos estos se pueden cambiar por números romanos, letras u otras formas.

Las listas no ordenadas se escriben con el elemento ul (unordered list). Sirven para indicar una lista de ítemes que no siguen un orden inherente. Visualmente se anteceden con una viñeta circular que puede cambiarse con estilos en rectángulos u otras figuras.

Las listas de definiciones se escriben con el elemento dl (definition list). Sirven para escribir glosarios u otros tipos de estructuras similares. A diferencia de los tipos de listas anteriores, los ítemes de las listas de definiciones constan de parejas término-definición. El término se escribe con el elemento dt (definition term) y su definición con el término dd (definition description).

Imágenes

La característica más notable del hipertexto es el soporte de varios medios, entre los que se incluyen las imágenes, las cuales se almacenan en recursos (archivos) separados al documento web y se referencian desde éste. Para incluir una imagen se utiliza el elemento vacío img, cuyo atributo src debe tener el URL que identifica el archivo con la imagen, y puede ser absoluto o relativo al documento. La sintaxis más básica del elemento img es como sigue.

<img src="imagen.url" alt="descripción de la imagen" />

(X)HTML requiere un texto alternativo que será desplegado cuando la imagen no es cargada por alguna razón, como imagen inexistente o porque el usuario ha deshabilitado las imágenes en su navegador. Algunos navegadores muestran este texto como un "tooltip" cuando el puntero del ratón pasa sobre ellas, pero esa función es realmente responsabilidad del atributo title, el mismo que se usa en los elementos de frase estudiados en la sección de texto estructurado. Si la imagen se va a usar como una decoración y no se quieren "tooltips", asigne un valor vacío en estos atributos, de la forma <img alt="" title="" ... />. La apariencia del texto alternativo se puede ajustar con hojas de estilo.

Tamaños de imagen

Cuando el navegador está cargando un documento web y encuentra una etiqueta <img src="imagen.url" alt="algún texto" /> con esos atributos, solicita al servidor web que le envíe una copia de la imagen. Mientras ésta llega, el navegador sigue cargando (rendering) el resto del documento (X)HTML. Es muy probable que termine de mostrar el documento antes de que la imagen llegue y que el visitante empiece a leer el documento. Cuando finalmente la imagen es recibida, el navegador la insertará en el lugar donde encontró el elemento img correspondiente, desplazando el texto que se encuentre debajo. Esto es molesto para el lector, en especial si el documento tiene un considerable número de imágenes o son de gran tamaño o ambas.

El problema anterior se soluciona especificando las dimensiones de la imagen en el documento (X)HTML, de tal forma que el navegador pueda reservar espacio para la imagen antes de que esta sea recibida. Simplemente especifique las dimensiones en pixeles con los atributos width y hight.

<img src="imagen.url" alt="texto alternativo" width="320" height="280" />

Si el valor indicado en los atributos ancho o alto de img no coincide con el tamaño real de la imagen, el navegador la escalará. También se puede especificar en ellos un porcentaje de la ventana, algo como width="75%". Normalmente las aplicaciones de diseño web se encargan de asignar los valores reales de la imagen en forma automática. El ancho y alto de una o varias imágenes y otros detalles se pueden controlar con estilos, lo cual es muy útil para un conjunto grande de imágenes que comparten las mismas dimensiones.

Formatos de imágenes

Aunque existen muchos formatos de imágenes, sólo unos pocos se pueden utilizar en el web. La tabla Formatos de imagen muestra una comparación de los formatos más soportados entre los navegadores actuales. Es tentador publicar imágenes de alta calidad, pero su tamaño será considerable y tardarán en cargarse en proporción inversa al ancho de banda del visitante. Si una o muchas imágenes hacen lento el cargado de una página web, es una invitación al lector para abandonarla. El autor debe hacer un balance entre la calidad de la imagen y su tamaño en bytes. Por esto es importante que conozca la diferencia entre los formatos disponibles y cómo ajustar este balance.

Formatos de imagen soportados por navegadores
Formato Tipo Colores Transparencia Pérdida Utilidad
PNG Escalar 256 / 16M No Ilustraciones
JPEG Escalar 16M No Fotografías
GIF Escalar 256 No Animaciones sencillas
SVG Vectorial 16M No Ilustraciones

Cuando un autor quiere representar alguna pieza de información en forma gráfica, debe decidir si utilizar una fotografía, una ilustración o una animación. Esta decisión ayuda en gran medida a delimitar el formato a escoger.

Una fotografía tiene millones de colores o grises. Se obtienen por cámaras digitales o escáneres. Ambos dispositivos no proveen información de transparencia. El formato más recomendable para fotografías es el JPEG, creado en 1992 por el Joint Photographic Experts Group. Es un algoritmo de pérdida de calidad que trata de descartar detalles que el ojo humano no percibe con el fin de ahorrar espacio. El formato PNG (Portable Network Graphics) también puede almacenar fotografías, pero sin pérdida de calidad, lo cual es poco recomendable para el web ya que su tamaño compromete la velocidad de carga del documento.

Una ilustración es una imagen, normalmente en dos dimensiones, que contiene texto o lo complementa. Suelen contener diagramas o dibujos hechos en un programa de cómputo, en contraposición a una cámara o escáner. La cantidad de colores de una ilustración suele se reducida. El formato PNG o SVG (Scalar Vector Graphics) son aptos para ilustraciones, la diferencia entre ambos es el tipo de imagen: escalar o vectorial respectivamente.

Una imagen escalar es un mapa de bits, es decir, una matriz de tamaño definido donde cada celda o pixel almacena un color codificado en forma numérica. "Redimensionar" a menor tamaño una imagen escalar provoca que ésta se deforme en especial si no se mantiene su proporción. Si se agranda una imagen escalar provocará que los pixeles se propaguen a las celdas adyacentes, generando zonas de colores poco agradables a la vista humana.

Una imagen vectorial está compuesta por figuras geométricas como puntos, líneas, curvas o polígonos. Sus atributos como posición y tamaño son los que se almacenan en el archivo. "Redimensionar" una imagen vectorial a cualquier tamaño significa reajustar las posiciones de las figuras geométricas y volverlas a pintar, lo que provoca que la imagen siempre se vea agradable a la vista humana.

La explicación anterior facilita la decisión hacia SVG como formato de elección para ilustraciones, sin embargo hay que tener en cuenta que, aunque SVG es un estándar abrigado por el W3C, su implementación en los navegadores actuales es parcial y quizá ausente en aquellos de dispositivos móviles, debido a que su despliegue requiere de mayor poder de cómputo que un PNG. Algunos sitios web como Wikipedia usan SVG, pero si el navegador no soporta este formato, el servidor web envía a cambio un PNG autogenerado.

Si el autor encuentra que una animación es el mejor medio de comunicar algo, puede usar una imagen GIF (Graphics Interchange Format), que permite animaciones escalares muy sencillas y limitadas a 256 colores. Si se requiere animaciones vectoriales, el autor podría considerar HTML5 o Adobe Flash. Si se requiere animaciones de más de 256 colores, se trata de un video y estos pueden incrustarse con objetos como se estudiará en la sección Objetos multimedia.

Indiferentemente de si se utilizan imágenes escalares o vectoriales, el autor debe conseguir reducir el tamaño tanto como se pueda sin que la imagen se vea desagradable al lector. El autor necesita que el editor de imágenes lo apoye en esta labor. Normalmente estos programas proveen una opción que permite variar el tamaño en bytes de la imagen u otros parámetros y ver el efecto en tiempo real en la visualización de la misma. Es importante que el autor guarde además la imagen original en un formato con mayor calidad o sin pérdida alguna.

Existe considerable cantidad de editores de imágenes disponibles, desde los renombrados paquetes de Adobe: Photoshop para imágenes escalares e Illustrator para imágenes vectoriales. Como opciones libres a estos programas se puede citar Gimp e Inkscape respectivamente.

Icono de favoritos

Se ha convertido en una práctica común proveer un pequeño icono que ayuda al visitante a identificar la página web. A este icono se le suele llamar icono de favoritos ó simplemente favicon (contracción de favorites icon), porque el navegador lo almacena junto a los favoritos, y lo despliega en la barra de direcciones o en las pestañas. Usualmente el mismo icono se utiliza en todas las páginas de un sitio web, lo que le da identidad al sitio entero y por ende también se le llama icono de sitio web (web site icon).

El favicon es un archivo PNG, o uno con extensión .ico que puede contener imágenes de varios tamaños. Normalmente son de 16x16 pixeles y utilizan fondo transparente. El documento web debe indicar el favicon en su encabezado (head) utilizando el elemento vacío link e indicar el tipo de imagen y su URL, algo como:

<head>
   ...
   <link rel="icon" type="image/png" href="path/to/favicon.png" />
</head>

Véase http://en.wikipedia.org/wiki/Favicon para otros tipos de imágenes y enlaces a usar como favicons

Tablas

(X)HTML permite organizar datos en forma tabular con el elemento table. Una tabla consta de filas declaradas con el elemento tr (table record). Cada fila se compone de celdas td (table data). El siguiente ejemplo muestra una tabla sencilla. Una tabla consta de tres partes: un encabezado de tabla opcional, el cuerpo de la tabla y el pie de tabla opcional. Cada una de esas partes consta de filas.

Formularios

Objetos multimedia

Elementos genéricos

Ejercicio. Cambie la dirección del texto de derecha a izquierda en uno o varios elementos. Pruebe en algunos navegadores su efecto. ¿Es lo mismo que usar el estilo de justificación derecha del texto?

Ejercicio. Las especificaciones de HTML 4.01 y XHTML 1.0 son la documentación oficial y quizá la más completa del estándar, se acceden en el sitio web del W3C. Una es más extensa que la otra ¿Cómo esto le afecta a usted en su rol de desarrollador?

Ejercicio. Investigue los elementos del e ins en la especificación HTML 4.01, haga un ejemplo y describa cuál es su utilidad.

Ejercicio. Investigue en la recomendación sobre el elemento address y genere hipótesis de a qué se debe su poco uso.

Ejercicio. Los elementos blockquote y q sirven para citar ideas de otro autor. Investigue en la especificación su diferencia. ¿Debe el autor proveer comillas o estos elementos las agregan automáticamente? ¿Alguna de ellas permite agregar varios párrafos <p> en su contenido? ¿En qué afecta indicar el idioma de la cita? ¿Cuáles de estos elementos se pueden anidar? ¿Qué abuso han históricamente cometido los autores con el elemento blockquote

Ejercicio. ¿Qué pasa si dentro de un elemento pre coloca código (X)HTML que usted quiere mostrar como ejemplo literal?
  1. El navegador no trata de interpretarlo y lo despliega apropiadamente.
  2. El navegador lo interpreta como código (X)HTML y el usuario puede ver el código original adecuadamente.
  3. El navegador lo interpreta como código (X)HTML y el usuario no puede ver el código original sino el resultado de interpretarlo.
  4. El navegador interpreta sólo unas etiquetas e ignora otras dentro del contenido de <pre>.
Ejercicio. ¿Cuál de las siguientes afirmaciones es falsa?
  1. El elemento br siempre debe cerrarse en XHTML de la forma <br/> ó <br></br>.
  2. El elemento br nunca debe cerrarse en HTML.
  3. Para permitir que navegadores viejos puedan mostrar código XHTML, algunos autores cierran el elemento br antecediendo un espacio en blanco, de la forma <br />. Esto es válido en XHTML pero no en HTML, sin embargo, los navegadores ignoran lo que no entienden y esto es lo que hacen los navegadores viejos cuando ven esta barra inclinada.
  4. El estándar HTML 4.01 se modificó para que <br /> sea una construcción completamente válida.
  5. Lo anterior también se cumple para los elementos hr, img y meta.

Ejercicio. Descargue el DTD de un documento (X)HTML en que esté trabajando y estudie su contenido.

Ejercicio. La recomendación HTML 4.01 da indicio de que el elemento body tiene varios atributos de la forma onevent como onload y onclick. Si el W3C ha sido insistente de que el estilo y el comportamiento no deben especificarse en el documento sino en un recurso externo, ¿por qué no han sido "deprecated" estos atributos? Investigue.

Ejercicio. En su nuevo trabajo usted está mejorando un sistema existente que genera varios documentos HTML automáticamente. Uno de ellos es bastante extenso; se comporta diferente entre navegadores; a veces carga por completo y otras sólo un trozo del documento aparece, incluso en el mismo navegador. La información en el documento es importante y el cliente está bastante disconforme. Hojeando rápidamente el código generado usted detecta los siguientes problemas. ¿Cuál de ellos puede estar ocasionando este comportamiento impredecible?
  1. Dos elementos pre están anidados.
  2. Algunas etiquetas <li> están cerradas y otras no.
  3. La indentación es inconsistente.
  4. No especifica el tipo de documento, ni el encabezado HTML.

Ejercicio. Una persona está escribiendo una página web personal en [ToDo: http://...], pero está teniendo serios problemas y le pide ayuda a usted para hacerla funcionar. Corrija dicha página y hágala válida. Por ahora no se preocupe del formato, en el siguiente capítulo regresará a ello.

Ejercicio. El documento [ToDo] es HTML válido. Conviértalo a XHTML estricto válido.

Ejercicio. Localice una página web de su agrado y sométala al validador del W3C. Si no se conforma al estándar, estudie qué le falta para hacerlo.

Ejercicio. Haga estadísticas. Someta al validador del W3C al menos diez páginas web que frecuenta (sugerencia: las que tiene registradas en sus favoritos). ¿Cuántas de ellas son válidas? ¿Qué tipos de documentos (DTD) son los menos y más usados? Compare sus resultados con otras personas que hayan realizado el experimento.

Ejercicio. Escriba un documento XHTML válido, incluya la declaración XML y pruebe su comportamiento en varios navegadores "viejos" como Internet Explorer 6.0. ¿Qué pasa si además usa la extensión .xhtml?

Ejercicio. Suponga que está escribiendo un manual de usuario en (X)HTML ¿Qué formato de imagen utilizaría para un "pantallazo" (screenshot) del programa en cuestión? Justifique la respuesta.

Hojas de estilo en cascada CSS

Un documento (X)HTML sólo debe tener contenido, es decir, los datos que el autor quiere publicar y su estructura. La apariencia o presentación de un documento web debe especificarse en un archivo externo llamado hoja de estilos que indica cómo debe formatearse la información en la pantalla, en papel u otro medio de salida.

La separación del contenido y la presentación tiene grandes ventajas. Una hoja de estilos puede reutilizarse en todas las páginas de su sitio web, lo que les da uniformidad, facilita el mantenimiento y ahorra ancho de banda. Una misma o varias páginas web pueden ajustarse a diferentes necesidades simplemente cambiando la hoja de estilos. Así el autor puede tener un juego de hojas de estilo en su sitio web; una para cada necesidad: un diseño avanzado para el navegador, grandes contrastes para personas con necesidades especiales, estilos amigables para impresión en papel (printer friendly), etc. De esta forma, una misma página web puede verse en formas distintas sin cambiar la información que contiene.

Generalidades de las hojas de estilo

Una hoja de estilos es un archivo de texto que contiene reglas de formateo de elementos utilizando un lenguaje especial conocido como hojas de estilo en cascada (CSS, Cascading Style Sheets), el cual es estándar y definido por el W3C. Por ejemplo, la siguiente regla hace que todos los títulos de un documento web sean de color verde:

h1, h2, h3, h4, h5, h6 { color: green; }

En general una regla CSS tiene la siguiente sintaxis:

selector { declaration }

El selector determina cuáles elementos serán afectados por los estilos definidos en la declaración, y puede ser uno o varios elementos separados por comas. La declaración es una lista de parejas propiedad: valor que serán aplicadas a los elementos listados en el selector. Dichas parejas se separan por el símbolo punto y coma (;). Es opcional terminar en punto y coma la última pareja. Con esto la sintaxis se expande a:

element1, element2, ..., elementN
{
   property1: value1;
   property2: value2;
   ...
   propertyN: valueN;
}

Es muy conveniente que escriba comentarios explicando la intención de cada regla, o al menos de aquellas no triviales. Los comentarios utilizan la misma notación del lenguaje de programación C:

/* Las definiciones deben estar en negritas y no en itálicas como ocurre en ciertos
navegadores. Globalmente se usa verde para definiciones y títulos; azul para enlaces. */
dfn
{
   font-weight: bold;
   font-style: normal;
   color: #004444;
}

Ubicación de las reglas de estilo

La recomendación (X)HTML permite declarar reglas de estilo en cinco lugares:

  1. En el elemento mismo, con el atributo style.
  2. En el encabezado del documento web, con el elemento style.
  3. En un archivo .css externo el cual se liga al documento con el elemento link.
  4. En una hoja de estilos externa provista por el usuario.
  5. En la implementación del navegador web.

De los cinco lugares para especificar estilos en la lista anterior, el Consorcio Web (W3C) desalienta el uso los dos primeros, ya que el documento web debe almacenar contenido únicamente, no estilos; y los dos últimos están fuera del control del autor. Por eso, se aconseja al autor declarar las reglas de estilo siempre en una o varias hojas de estilos .css externas al documento web.

El listado css_locations muestra cómo declarar estilos en los tres lugares donde puede el autor. Supóngase que la línea 17 representa un archivo de texto con extensión .css que contiene sólo una regla y se adjunta al documento web con el elemento link en el encabezado del mismo (línea 4). Si dos o más hojas de estilo se incluyen en el encabezado, se procesarán en el mismo orden en que se incluyeron.

<!-- file.html -->
<html>
   <head>
      <link rel="stylesheet" type="text/css" media="screen" href="path/to/styles1.css"/>
      <link rel="stylesheet" type="text/css" media="print" href="path/to/styles2.css"/>
      <style type="text/css">
         p { color: red }
      </style>
      ...
   </head>
   <body>
      <p style="color: blue">Lorem ipsum ad his scripta blandit partiendo...</p>
   </body>
</html>

<!-- styles1.css -->
p { color: green }

El atributo media del elemento link indica si las reglas de estilo aplican a un medio específico, como la pantalla (screen), la impresora (print), programas de lectura sintetizada (aural), computadoras de mano (handheld), televisores (tv) y otros. En el ejemplo anterior, un navegador cargará la hoja styles1.css y los aplicará al documento web para ser visualizado en la pantalla. Si el usuario decide imprimir el documento, el navegador cargará styles2.css y los aplicará en la copia que enviará a la impresora. El atributo media es opcional, y si no se especifica en un elemento, se asume que esa hoja de estilos aplicará a todos los medios, lo que equivale a darle el valor media="all". El atributo media puede contener varios valores separados por comas, de esta forma, una hoja de estilos puede aplicarse a varios medios simultáneamente.

El segundo lugar donde se pueden escribir reglas de estilo es en uno o varios elementos style dentro del encabezado (head) del documento (X)HTML, como se aprecia en las líneas 6 a 8 del listado css_locations. Nótese que el encabezado head admite combinar hojas de estilo externas y elementos style en cualquier orden. La especificación CSS indica que el navegador debe respetar este orden, de tal forma que las reglas de estilo que se encuentran en un archivo .css, tienen el mismo efecto que si se escribiesen directamente en un elemento style del encabezado.

El tercer lugar donde se puede especificar reglas de estilo es en el atributo style del elemento mismo, como se hizo en la línea 12 del listado css_locations. En este caso, todas las propiedades aplicarán únicamente a dicho elemento por lo que no es necesario especificar un selector, ni agrupar las propiedades dentro de llaves { }.

El principio de cascada

En vista de que hay cinco lugares opcionales donde se especifican estilos, puede ocurrir que para una propiedad no haya ninguna regla, o bien hayan varias. El navegador necesita un algoritmo que especifique cómo actuar en estos casos, al cual se le llama el principio de cascada y consta de otros tres principios.

Si dos o más reglas compiten para darle estilo a una misma propiedad, recibirá mayor prioridad aquella que tenga mayor especificidad en su selector. A esto se le llama principio de especificidad.

Si dos reglas compitiendo por la misma propiedad tienen igual especificidad, se tomará la que aparezca de último, es decir, tendrá más peso aquella que se encuentre más cerca del elemento. A esto se le llama el principio de ubicación.

Cuando por el contrario, no se especifican reglas para una propiedad, el navegador debe seguir un principio más, el principio de herencia, el cual indica que ante la ausencia de una regla de estilo, el navegador debe aplicar el estilo del elemento padre. No todos las propiedades son heredables por defecto. Por ejemplo, la fuente lo es pero no el margen. De esta forma, al asignar la fuente al elemento body, todos los párrafos la heredan, lo cual es deseable; pero si se asigna un margen grande al body para mantener una separación visual con los bordes de la ventana del navegador, sería indeseable si este borde se aplicara entre párrafo y párrafo.

El uso de los tres principios: ubicación, especificidad y herencia para determinar cuál regla debe aplicar a una propiedad de estilo de un elemento se conoce como principio de cascada. Los navegadores implementan este principio dándole puntos o pesos a cada regla de estilo siguiendo un cálculo propuesto en la especificación CSS, que cualitativamente se puede resumir en lo siguiente:

Ante la ausencia de una regla, el estilo se hereda del elemento padre si la propiedad es heredable, si no, se usa el estilo por defecto del navegador. Si dos o más reglas compiten por darle estilo a una misma propiedad, se tomará aquella con mayor especificidad de su selector, independientemente de dónde esté ubicada. Con dos reglas de igual especificidad, ganará la que aparezca de último.

A modo de ejemplo, el listado css_location_doc muestra un documento web que tiene dos párrafos y estilos definidos en tres lugares: una hoja de estilos externa (cascading1.css) enlazada en la línea 4; un elemento style en el encabezado (líneas 5 a 7), y un atributo style en el segundo párrafo (línea 18). Se estudiará el efecto de estos estilos en tres propiedades: el color, la fuente y el margen.

<html>
<head>
   <title>Estilos en conflicto: cascada</title>
   <link rel="stylesheet" type="text/css" href="cascading1.css"/>
   <style type="text/css">
      p { color: teal; }
   </style>
</head>

<body>
   <p>Este es el primer párrafo. Ninguna regla de estilo para <code>p</code>
   especifica la fuente, por lo que <em>hereda</em> la fuente de su elemento padre
   <code>body</code>. Aunque hay una hoja de estilos externa que especifica que el color
   del texto de los párrafos debe ser rojo, éste es <strong>verde azulado</strong>
   porque hay una regla en el encabezado del documento, que está más cerca de este
   párrafo.</p>

   <p style="color: green;">Este es el segundo párrafo. Tiene un atributo de estilo que
   sobrescribe el color del texto a <strong>verde</strong>, ya que por
   <em>especificidad</em> está más cerca del elemento. Ninguna regla de estilo formatea
   la separación vertical entre párrafos, por lo que se aplica la que el navegador tenga
   implementada internamente.</p>
</body>
</html>
Ejemplo de estilos en cascada. Correr este ejemplo.
body
{
   font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
   margin: 2.5cm;
}

p { color: red; }
Archivo cascading1.css referenciado en el ejemplo anterior.

La fuente es una propiedad heredable de acuerdo a la especificación CSS. La mayoría de navegadores asumen una fuente con serifas, como Times New Roman. Al cargar la hoja de estilos el navegador procesa la regla que indica que la fuente del cuerpo (body) del documento debe mostrarse en Verdana, que es por el contrario una fuente sin serifas (sans-serif); cualquier regla del autor tiene más prioridad que el valor por defecto del navegador y por ende el navegador debe usar Verdana para formatear el texto del body, el cual es vacío.

Al desplegar el primer párrafo, el navegador no encuentra una regla de fuente para dicho elemento de párrafo. Como es una propiedad heredable, el navegador toma la fuente del padre del párrafo que es el elemento body, y por ende despliega el texto del párrafo en Verdana. Esto mismo ocurre recursivamente para los elementos hijos del párrafo, como em, strong y code; y luego para el párrafo siguiente. De esta forma, todo el documento aparece en Verdana como el autor podría esperar.

La propiedad de margen (margin) no es heredable de acuerdo a la especificación CSS. El navegador web asume un margen nulo o minúsculo para el body, por lo que el texto se suele pegar con los extremos de la ventana del navegador. Al encontrar la regla margin: 2.5cm, el navegador la aplica al elemento body, haciendo que el margen del documento sea de 2.5 centímetros. Sin embargo, al no ser una propiedad heredable, el navegador no aplica un margen de 2.5 centímetros a los párrafos ni a los elementos em, strong y code, sino que asume el margen por defecto, que es de unos cuantos pixeles para párrafos y de 0 pixeles para los otros elementos citados.

El color es una propiedad heredable según la especificación CSS. El navegador no encuentra una regla para el color de body y asume el por defecto del navegador, que es negro. La regla p { color: red; } en la hoja de estilos cascading1.css, indica al navegador que pinte todos los párrafos en rojo. Pero en la línea 6 del documento web del listado css_location_doc compite por la misma propiedad: establecer el color de los párrafos en verde azulado (teal). Estas dos reglas están en conflicto, y ganará la que tenga mayor especificidad en el selector, sin embargo ambas tienen el mismo selector; y en tal caso el navegador deberá aplicar la que aparezca de último de acuerdo al principio de ubicación, y por ende, los parrafos aparecerán en verde azulado.

Al pintar el segundo párrafo que inicia en la línea 18 del listado css_location_doc, el navegador encuentra una regla para la propiedad color en su atributo style. De acuerdo al principio de especificidad esta regla tiene la mayor prioridad y por ende, el segundo párrafo aparecerá en verde simple en lugar de verde azulado.

El operador @

Selectores

Valores de propiedades

Los valores que pueden tomar las propiedades son de muy diversa naturaleza. Sin embargo, existen algunos dominios comunes que se resumen en la siguiente tabla y a los cuales se les hará referencia luego en este documento.

Dominios comunes de ciertas propiedades CSS
Dominio Descripción Ejemplos
inherit Dele el valor inherit a una propiedad cuando usted explícitamente quiere especificar que esa propiedad tome el mismo valor que la del elemento padre. p { margin: inherit; }
<length>

Son magnitudes compuestas siempre de un valor y una unidad, como 2.5cm las cuales no deben estar separadas por espacios. La unidad se puede omitir sólo cuando la magnitud es 0. Hay dos tipos de unidades:

  • Absolutas: centímetros (cm), milímetros (mm), pulgadas (in), puntos (pt) y picas (pc).
  • Relativas: tamaño de fuente (em), tamaño de la letra x en la fuente actual (ex), pixeles (px), y porcentajes del valor de la propiedad del elemento padre (%).
body { margin: 2.5cm; }

h1
{
   margin-top: 2em;
   margin-bottom: 0;
}

pre { font-size: 85%; }
<number> Son números sin unidades. div.content { z-index: 2; }
<url> URL hacia otro recurso, como una imagen, sonido, etc. Si el URL es relativo, lo será con respecto a la hoja de estilos y no al documento (X)HTML. Encerrar el URL entre comillas es opcional. No separe la palabra url del paréntesis que abre.
body
{
   background: url(water.png) repeat;
}
<color>

Hay tres formas de especificar colores en CSS:

  • Nombres predefinidos. Hay 16 colores predefinidos como se ve abajo. Los nombres no son sentivos a mayúsculas y no se deben especificar dentro de comillas.
  • Notación rgb(). Permite construir un color con la cantidad de rojo, verde y azul, sea con valores entre 0 y 255 (o su correspondiente hexadecimal), o porcentajes. No se debe separar la palabra rgb de los paréntesis que abren.
  • Notación #. Se especifica las cantidades de rojo, verde y azul en notación hexadecimal antecedidos por un símbolo de número, de la forma #rrggbb, o #rgb si los dígitos de cada componente se repiten. Las letras hexadecimales pueden estar en mayúsculas o minúsculas o ambas.
body { background: black; }
h1 { color: rgb(245, 255, 250); }
h2 { color: rgb(100%, 75%, 50%); }
pre
{
   border: solid 1px;
   border-color: #80c0a0 #000 #000 #80c0a0;
   background: #EFE;
}

Propiedades

El modelo de fuente

El modelo de color

El modelo de caja

El modelo de visualización

Comportamiento con JavaScript

JavaScript es un lenguaje interpretado por el navegador web que permite al autor manipular dinámicamente el documento, sus estilos y la ventana misma del navegador, para hacer la experiencia del lector más natural y amena.

JavaScript fue creado en 1995 por Brendan Eich cuando trabajaba para Netscape. Un año después Microsoft produjo su propia versión llamada JScript. También en 1996, Netscape sometió JavaScript a la asociación Ecma International para consideración como estándar de la industria, el resultado fue ECMAScript.

ECMAScript es un estándar internacional de un lenguaje genérico de "scripting" para extender la funcionalidad de un programa cualquiera, no sólo navegadores web. Hay un número creciente de implementaciones basadas en ECMAScript que además extienden su funcionalidad, como el inicial JavaScript de Netscape, JScript de Microsoft, ActionScript de Macromedia (adquirida por Adobe), SpiderMonkey y Rhino de Mozilla, etc. Sin embargo, el nombre ECMAScript no tomó popularidad, y cuando la mayoría de la gente dice "JavaScript" está haciendo referencia al lenguaje en forma general, no a la implementación de Netscape, y así se hará en este documento.

JavaScript es un lenguaje genérico que puede utilizar cualquier software que quiera permitir al usuario automatizar tareas propias; como ocurre en la actualidad para programación de dispositivos móviles, acceso a bases de datos orientadas a documentos, animación digital y otros. Pero su uso más difundido ha sido históricamente el web, en la programación en el lado del cliente y más recientemente en el servidor web. En este documento se presentará este lenguaje ligado al navegador web.

Generalidades

JavaScript es un lenguaje similar a C/C++/Java. Es sensitivo a mayúsculas y minúsculas, por lo que resulta más consistente con XHTML que con HTML. Aunque no es obligatorio, cada sentencia en JavaScript debe terminar en punto y coma, y se considera una mala práctica omitirlos. Los comentarios utilizan la notación de C++:

// comentario hasta el final de línea
/* comentario que puede
   extenderse varias líneas */

El código JavaScript puede aparecer en cuatro lugares: en el elemento script, en un archivo .js externo, en un evento intrínseco, y con el pseudoprotocolo javascript:.

Código JavaScript en el elemento script

Se puede escribir código JavaScript en el contenido del elemento script, el cual debe ser hijo directo de head o body. El siguiente ejemplo muestra los cuadrados de los primeros 20 números naturales:

<body>
   <h1>Cuadrados naturales 1</h1>
   <script type="text/javascript">
   <!--
      document.write('<ul>\n');
      for ( var n = 1; n <= 20; ++n )
         document.write('<li>' + n + '<sup>2</sup> = ' + (n * n) + '</li>\n');
      document.write('</ul>\n');
   -->
   </script>
</body>
Un elemento script en el cuerpo del documento. Correr este ejemplo.

Mientras el navegador está cargando un documento web, va mostrando sus elementos (títulos, párrafos, imágenes, tablas, etc.), a medida que los encuentra. Lo mismo pasa con los scripts. Inmediatamente que el navegador encuentra un elemento script, ejecuta su código.

En el ejemplo anterior, se invoca al método write() del objeto document que representa al documento ante JavaScript. La salida del método document.write() es insertada como código (X)HTML inmediatamente después del elemento script que lo invoca. Una vez que el script ha terminado su ejecución, el navegador continúa procesando el resto del documento como de costumbre, y procesará tanto el código insertado con document.write() así como el provisto por el autor en el documento original.

Como se puede deducir del ejemplo anterior, JavaScript interpreta el texto entre apóstrofes ('') como cadenas de caracteres. El operador de suma (+) cuando alguno de sus operandos es una cadena, hace concatenación. Las variables no se declaran precedidas por su tipo de datos, sino por la palabra reservada var. Y el ciclo for tiene la misma sintaxis de C.

Ejercicio. ¿Por qué el código JavaScript en el ejemplo anterior se escribió dentro de un comentario (X)HTML? ¿Por qué el navegador ejecuta este código en lugar de ignorarlo como cualquier otro comentario? ¿Qué pasa si se retira este comentario del código?

Ejercicio. En un documento aparte, modifique el ejemplo anterior para desplegar en una tabla HTML, las tablas de multiplicar del 1 al 20. Asegúrese de que su documento sea válido ante los estándares.

Ejercicio. En el mismo documento, agregue después una tabla HTML con las primeras 10 potencias naturales de los números 1 a 20. Recuerde validar su documento.

Código JavaScript en un archivo externo

El elemento script permite escribir código JavaScript en su contenido, pero el Consorcio Web recomienda sacar el comportamiento del documento web a un recurso reutilizable, por convención, un archivo con extensión .js. Este es el segundo lugar donde se puede escribir código JavaScript. El mismo elemento script permite hacer la inclusión del archivo externo con el atributo src. El siguiente ejemplo tiene el mismo efecto que el ejemplo anterior:

<body>
   <h1>Cuadrados naturales 2</h1>
   <script type="text/javascript" src="squares_list2.js"></script>
</body>
El código JavaScript puede estar en un recurso externo y ser importado con el atributo src del elemento script. Correr este ejemplo.
document.write('<ul>\n');
for ( var n = 1; n <= 20; ++n )
   document.write('<li>' + n + '<sup>2</sup> = ' + (n * n) + '</li>\n');
document.write('</ul>\n');
El archivo JavaScript squares_list2.js referido en el ejemplo anterior.

La especificación (X)HTML dice que si un elemento script tiene tanto código JavaScript en el contenido como un archivo referido en el atributo src, el contenido será ignorado por completo en favor del archivo externo.

Ejercicio. Extraiga el código JavaScript que escribió en los ejercicios anteriores a uno o varios archivos .js externos.

Ejercicio. Escriba algunas instrucciones JavaScript en el contenido de un elemento script que también referencia un archivo externo con el atributo src. Verifique cuáles instrucciones son ejecutadas.

Ejercicio. Al retirar el código JavaScript del contenido del elemento script para moverlo a un archivo externo, el elemento script queda vacío, y según el estándar XML es válido emplear una etiqueta vacía de la forma <script src="path/to/file.js"/>. Pruebe esto en su documento web. Valídelo contra el estándar. ¿Cómo reacciona el navegador ante este cambio?

Código JavaScript en los eventos intrínsecos

El tercer lugar donde se puede especificar código JavaScript es en los eventos intrínsecos de ciertos elementos, tales como onload, onmouseover y onclick. Por ejemplo:

<body>
   <h1>Eventos en JavaScript</h1>
   <button onclick="alert('No me toque');">Un botón!</button>
</body>
El autor puede proveer código JavaScript se que ejecuta cuando un evento ha ocurrido, como presionar un botón en este caso. Correr este ejemplo.

Código JavaScript en el pseudoprotocolo javascript:

El cuarto lugar donde se puede ejecutar código JavaScript es en la barra de direcciones del navegador, con el pseudoprotocolo javascript:, que es seguido por una o varias instrucciones JavaScript separadas por punto y coma. El resultado de estas instrucciones, si lo hay, se toma como un string, y es desplegado en la ventana del navegador. Ejemplos:

javascript:5%2
javascript:x = 3; (x < 5) ? 'x is less' : 'x is greater'
javascript:d = new Date(); typeof d
javascript:for(i=0,j=1,k=0,fib=1; i>5; i++,fib=j+k,k=j,j=fib) alert(fib);
javascript:s=''; for(i in navigator) s+=i+':'+navigator[i]+'\n'; alert(s);

Tipos de datos y variables

JavaScript define siete tipos de datos: booleanos, números, cadenas de caracteres (strings), funciones, objetos, arreglos, y valores especiales. En las siguientes secciones se explorará cada uno de ellos.

Números

JavaScript no hace diferencia entre números enteros y de punto flotante. Todos son representados internamente como punto flotante de 64bits (IEEE 754). Las constantes numéricas de JavaScript siguen las mismas convenciones de C, excepto los números octales que no son parte de ECMAScript.

Cuando un valor flotante llega a ser más grande que el más grande de los representables, se almacena con el valor especial Infinity o su opuesto negativo. Cuando se hace una operación indefinida, se genera un número especial NaN (not-a-number), el cual nunca es igual a nada, incluso ni a él mismo, por eso debe usarse la función especial isNaN(). Otra función práctica es isFinite(), que prueba si un número no es Infinite y no es NaN. A continuación una lista de constantes numéricas especiales:

  • Infinity. Valor especial usado en lugar de un número gigante que no cabe en un double de 64 bits, por ejemplo el resultado de la expresión 17/0.
  • NaN. Acrónimo de "not-a-number". Es un valor especial usado cuando una expresión no genera un número válido, como la división 0/0 o tratar de convertir un string no apto a un número como parseInt("cinco").
  • Number.MAX_VALUE. El número más grande representable en un double de 64 bits.
  • Number.MIN_VALUE. El número decimal más pequeño (cercano a cero) representable en un double de 64 bits.
  • Number.NaN. Igual a NaN.
  • Number.POSITIVE_INFINITY. Igual a +Infinity.
  • Number.NEGATIVE_INFINITY. Igual a -Infinity.

Ejercicio. Escriba un script que genere una tabla con las siguientes columnas: el nombre de la constante, el valor real de dicha constante, el valor que precede a la constante, el valor sucesor de la constante, el resultado de invocar isNaN() con la constante, y el resultado de invocar isFinite() con la constante. Llene las filas de la tabla con cada una de las constantes listadas anteriormente.

Ejercicio. Agregue dos filas más a la tabla del ejercicio anterior. La primera con una expresión aritmética que genera un valor real, y la segunda que genere un valor indefinido.

Cadenas de caracteres (strings)

Las cadenas literales en JavaScript se encierran entre comillas dobles o simples y se pueden anidar éstos. Ya que es común escribir código HTML dentro de JavaScript y viceversa, es conveniente uniformar las comillas para cada cual. Por ejemplo:

<a href="" onclick="alert('You\'re welcome')">Thanks</a>

A diferencia de otros lenguajes de programación, Las cadenas literales tienen que ser escritas en una única línea, no pueden romperse en dos o más de ellas.

Igual que en C/C++, JavaScript emplea secuencias de escape iniciadas en backslash (\) para representar caracteres especiales, tales como \n para el cambio de línea, \r para el retorno de carro, \" para comillas dobles, \' para apóstrofe o comilla simple, \0 para el carácter nulo y \\ para la barra invertida (backslash).

No se debe utilizar el backslash frente a un cambio de línea para tratar de continuar un string en varias líneas; JavaScript probablemente lo ignorará. En JavaScript las cadenas de caracteres o strings son un tipo de datos atómico, no un arreglo de caracteres. De hecho JavaScript no tiene el concepto de carácter (char) como sí ocurre en otros lenguajes de programación. Un carácter se representa como un string de longitud 1. El método str.charAt(i) permite obtener un string con el carácter que está en la posición i de str, donde el índice i está basado en cero, es decir, 0 representa el primer carácter de la cadena. El siguiente código muestra varias operaciones con strings:

var str = 'Welcome ' + visitorName; // concatenación
var len = str.length; // longitud de cadena
var lastChar = str.charAt(str.length - 1); // obtener un caracter de la cadena
var sub = str.substring(3, 4); // obtiene 'come'
var i = str.indexOf('e'); // Encuentra la posición de la primera letra 'e' en str

Aunque algunas implementaciones permiten el uso del operador [] para acceder a un carácter específico del string, es recomendable evitar su uso, ya que no forma parte del estándar ECMAScript.

Ejercicio. Escriba un poema de su agrado (o cualquier otro texto con varios párrafos) en una variable string de JavaScript. Imprima las estrofas (no las líneas que lo componen) en orden inverso en el documento.

Ejercicio. Para hacer notorio al lector que las estrofas están en orden inverso, imprima el número de la estrofa y con estilos CSS haga a este número visiblemente grande y ubicado a la izquierda o derecha de la estrofa.

Conversiones entre números y strings

Cuando un número aparece en un contexto donde se requiere un string, JavaScript lo convierte automáticamente. Por ejemplo, si uno de los operandos de + es una cadena y el otro es un número, el operador + actuará como el operador de concatenación y convertirá el número en una cadena.

var hdd_gb = 500; // 500 GB
var hdd_gib = 500 * Math.pow(10, 9) / Math.pow(2, 30); // 465.661287 GiB
var text = 'Un disco duro de ' + hdd_gb + 'GB equivale a ' + hdd_gib + 'GiB';
document.write('<p>', text, '</p>\n');
El operador de concatenación convierte números a cadenas automáticamente. Correr este ejemplo.

Las conversiones explícitas se pueden hacer con el constructor String(numero), y varios métodos de la clase Number: numero.toString(base), numero.toFixed(decimales), numero.toExponential(decimales) y numero.toPrecision(decimales). Ejemplos:

hdd_gib + "";              // "465.66128730773926"
String(hdd_gib);           // "465.66128730773926"

hdd_gib.toString();        // "465.66128730773926"
hdd_gib.toString(2);       // "111010001.1010100101001010001"
hdd_gib.toString(16);      // "1d1.a94a2"

hdd_gib.toFixed(0);        // "466"
hdd_gib.toFixed(2);        // "465.66"

hdd_gib.toExponential(0);  // "5e+2"
hdd_gib.toExponential(3);  // "4.657e+2"

hdd_gib.toPrecision(4);    // "465.7"
hdd_gib.toPrecision(7);    // "465.6613"

Cuando un string se utiliza en un contexto donde se requiere un número, será traducido automáticamente por JavaScript, por ejemplo:

var product = "21" * "2";  // product == 42.

Con la notación anterior no se podrá sumar un string a un número con el operador +, ya que será interpretado como concatenación. El constructor Number(str) convierte el string str en un número, siempre que str tenga formato de número en base 10 y no inicie con espacios en blanco. Las funciones parseInt(str,base) y parseFloat(str,base) asumen que str está en base base (10, si se omite) y lo convierten en un número entero o real respectivamente. Ejemplos:

parseInt("3 blind mice");  // Returns 3
parseFloat("3.14 meters"); // Returns 3.14
parseInt("12.34");         // Returns 12
parseInt("0xFF");          // Returns 255

parseInt("11", 2);         // Returns 3 (1*2 + 1)
parseInt("ff", 16);        // Returns 255 (15*16 + 15)
parseInt("zz", 36);        // Returns 1295 (35*36 + 35)
parseInt("077", 8);        // Returns 63 (7*8 + 7)
parseInt("077", 10);       // Returns 77 (7*10 + 7)

parseInt("eleven");        // Returns NaN
parseFloat("$72.47");      // Returns NaN

Si la cadena a convertir inicia con "0x" se interpreta que está en hexadecimal. Si inicia con 0 su resultado es indefinido, ya que algunas implementaciones podría interpretar octal o decimal, por lo que es conveniente siempre especificar la base. Si no se puede convertir a un número, estas funciones retornan NaN.

Booleanos

Los valores booleanos sólo pueden contener los valores literales false o true. Cuando se usa un booleano en un contexto numérico, se convierten automáticamente a los valores 0 y 1. Si se usan en un contexto string, JavaScript los convierte automáticamente a "false" y "true" respectivamente. El recíproco también es válido. Los valores especiales NaN, null, undefined y la cadena vacía ("") siempre se convierten a false; todos los demás a true (como Infinity). Para hacer explícita la conversión, es recomendable emplear la función Boolean():

var x_as_boolean = Boolean(x);

Funciones

El programador puede declarar sus propias funciones con la palabra reservada function, el nombre opcional de la función, los parámetros sin tipos entre paréntesis ( ), y el cuerpo de la función entre llaves { }. Las funciones en JavaScript son un tipo de datos más, por ende, una función es un valor; así, las funciones se pueden almacenar en variables, miembros de objetos, arreglos, y pasarse por parámetros en una forma más natural que en C/C++. Cuando una función se asigna a un objeto como un miembro, recibe el nombre especial de método. En caso de asignarse a una variable, puede omitirse el nombre de la función, lo que en JavaScript se llama función literal o función lambda en homenaje al lenguaje Lisp que fue uno de los primeros en permitir funciones sin nombre:

// una función nombrada
function square1(x) { return x*x; }

// una función literal o "lambda"
var square2 = function(x) { return x*x; }

// una función construida a partir de strings
var square3 = new Function("x", "return x*x;");

// invoca la función a través de la variable square2
square2(2.5);

Una tercera forma de definir una función es pasar sus argumentos y cuerpo como strings a la función constructora Function(), lo cual es poco usado e incluso, menos eficiente. Cuando el valor de una variable es una función, se puede invocar ésta usando el operador () tras el nombre de la variable, como se hizo en el ejemplo anterior con square2.

Ejercicio. Programe la función factorial recursivamente. Imprima en una lista no ordenada el factorial de los primeros 20 naturales. ¿Qué sucede si llama su función con un número muy grande o con parámetro no numérico?

Objetos

Un objeto es una colección de valores nombrados, que usualmente se les refiere como propiedades, campos o miembros del objeto, y se les accede utilizando el operador punto. Por ejemplo:

image.width
image.height
document.myform.button
document.write("write es un método: una propiedad cuyo tipo de datos es una función");

El operador punto permite acceder a las propiedades utilizando identificadores. Pero JavaScript permite también usar cadenas de caracteres para acceder a las propiedades, con el operador corchetes []. Esta segunda notación permite ver a los objetos como arreglos asociativos. Ejemplos:

image["width"]
image["height"]
document["myform"]["button"]
document["write"]("write es un método: una propiedad cuyo tipo de datos es una función");

Los objetos se crean llamando funciones constructoras con el operador new, después de lo cual se usan como de costumbre. Estos objetos son almacenados en memoria dinámica por el navegador, el cual incorpora un recolector de basura (garbage collector), de tal forma que ahorra al programador la responsabilidad de liberar la memoria de cada objeto creado.

var now = new Date();
var pattern = new RegExp("\\sjava\\s", "i");

var point = new Object();
point.x = 2.3;
point.y = -1.2;

En el ejemplo anterior el objeto point se creó como un objeto vacío, y sus propiedades se fueron agregando luego con el operador de asignación. Existe una notación para definir objetos literales, útil para inicializaciones:

{ property1: value1; "property2": value2; ...; propertyN: valueN }

Las llaves {} en JavaScript indican la creación de un objeto literal. Los nombres de las propiedades pueden declararse como identificadores o como strings. Los valores de cada propiedad pueden ser de cualquier tipo de datos de JavaScript (booleano, numérico, string, función, arreglo, u objeto), sea como valores literales o como resultado de una expresión aritmética. Así los objetos se pueden anidar como es de esperar:

var point = { x:2.3, y:-1.2 };

var activo1 =
{
   "tipo": "disco_duro",
   "precio": 30000, // colones
   'tamanno': 500 * Math.pow(10, 9) / Math.pow(2, 30) // GiB
};

var rectangle =
{
   color : "#a4f0ca",

   background:
   {
      color: "lightgray",
      image: "img/bricks.png",
      repeat: "repeat"
   },

   geometry:
   {
      topLeft: { x: 45, y: 10 },
      extend: { width: 21.93, height: 38.34 }
   }
};

Si un objeto se emplea en un contexto Boolean, se traduce a true si no es null. Si el contexto es string se llamará al metodo toString() y si es numérico a valueOf().

Ejercicio. Escriba un objeto Círculo, Triángulo y Rectángulo. En cada uno de ellos almacene su posición y dimensiones. Provea dos métodos en cada objeto, uno para calcular el perímetro y otro para el área. Haga un programa en JavaScript que imprima cada figura: su tipo, posición, dimensiones, perímetro y área. Nota: si A, B y C son los vértices de un triángulo, su área y perímetro se pueden obtener mediante (requiere Firefox para visualizar las fórmulas):

área = Ax ( By Cy ) + Bx ( Cy Ay ) + Cx ( Ay By ) 2

perímetro = ( Ax Bx ) 2 + ( Ay By ) 2 + ( Ax Cx ) 2 + ( Ay Cy ) 2 + ( Bx Cx ) 2 + ( By Cy ) 2

Ejercicio. Agregue otro triángulo a la colección de figuras hechas en el ejercicio anterior. ¿Puede reutilizar el código de los métodos del primer triángulo? Haga que su nuevo triángulo aparezca en la salida del programa. Sugerencia: puede verificar el área de sus triángulos contra esta aplicación.

Arreglos

En JavaScript un arreglo es una colección de datos enumerados. Se acceden con un índice entero, basado en 0, escrito entre corchetes tras el nombre del arreglo. El siguiente ejemplo obtiene el ancho en pixeles de la segunda imagen en el documento:

document.images[1].width

Los arreglos pueden tener datos heterogéneos. Así un elemento del arreglo puede contener otro arreglo, pero no hay arreglos multidimensionales. Los arreglos se crean con el constructor Array() y sus elementos se pueden agregar simplemente asignándolos a sus índices o bien, como parámetros del constructor Array(); pero si se pasa un único entero a este constructor, de la forma Array(N) se creará un arreglo con N elementos indefinidos. Ejemplos:

var a = new Array();                // Arreglo vacío, lo mismo que: var a = [];
a[0] = 1.2;                         // Un elemento es insertado en la posición 0
a[1] = "JavaScript";
a[2] = true;
a[4] = { x:1, y:3 };                // La posición 3 tiene un elemento con el valor undefined
a[5] = function(x) { return x*x; }; // a[5](7) retornará 49
a[6] = new Array();                 // Elemento a[6] almacena un arreglo vacío
a[6][0] = -Infinity;                // Inserta un elemento en el arreglo que está en a[6]

var b = new Array(1.2, "JavaScript", true, { x:1, y:3 }); // 4 elementos

var c = new Array(10); // Arrreglo de 10 elementos indefinidos

JavaScript permite crear arreglos literales, preferiblemente utilizados para inicialización, y son una lista de valores separados por comas dentro de corchetes, que se asignan secuencialmente empezando en 0. Los elementos también pueden ser indefinidos lo cual se logra omitiendo el valor entre comas:

var b = [ 1.2, "JavaScript", true, { x:1, y:3 } ];

var matrix = [[1,2,3], [4,5,6], [7,8,9]];           // matrix[2][1] == 8

var base = 1024;
var table = [base, base+1, base+2, base+3];

var sparseArray = [1,,,,5];

Ejercicio. Almacene las figuras que haya creado en los ejercicios 11 y 12 en un arreglo. Con un ciclo imprima cada figura, su perímetro y su área.

Ejercicio. Escriba una función que crea y retorna una figura geométrica aleatoriamente cada vez que se invoca. Llene un arreglo de 20 figuras aleatorias e imprímalas como hizo en el ejercicio anterior. Puede apoyarse en los métodos Math.random() y Math.floor().

Ejercicio. Escriba un inventario compuesto de una jerarquía de activos en JavaScript. Programe métodos para imprimir cada activo e imprima el inventario en un documento XHTML. Haga que cada activo genere un div en el documento, de tal forma que los div mantengan el mismo anidamiento que los objetos. Con estilos CSS gregue bordes a los div para que sea visualmente clara la relación entre estos.

Valores especiales

El valor especial null se utiliza para indicar que un objeto no tiene valor. El valor especial undefined indica que una variable u objeto nunca ha sido declarado o nunca se le ha asignado un valor.

Objetos especiales

JavaScript provee varios objetos útiles: Date, RegExp, Boolean, Number y String. El objeto Date sirve para obtener y manipular fechas u horas, como se aprecia en el siguiente ejemplo.

var text = '';
var now = new Date();

// Los meses inician en 0. Diciembre es el 11
if ( now.getMonth() == 11 && now.getDate() == 25 )
{
   text = 'Hoy es navidad!';
}
else
{
   // Fijar en un objeto Date la proxima navidad
   var christmas = new Date( now.getFullYear(), 11, 25, 00, 00, 000 );
   if ( now.getMonth() == 11 && now.getDate() > 25 )
   {
      christmas.setFullYear( now.getFullYear() + 1 );
      text = 'la próxima ';
   }

   // Convertir la cantidad de milisegundos que faltan para navidad en dias
   var dias = (christmas.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);
   text = 'Faltan ' + dias.toFixed(0) + ' días para ' + text + 'navidad';
}

document.write('<p>', text, '</p>\n');
Ejemplo de uso del objeto Date. Correr este ejemplo.

El objeto especial RegExp permite manipular expresiones regulares en JavaScript, siguiendo la misma notación de Perl.

Ningún valor primitivo en JavaScript tiene métodos, sin embargo, en algunos ejemplos anteriores, se han invocado algunos a través de valores primitivos. Esto es posible ya que JavaScript define tres clases correspondientes a cada uno de los tipos de datos primitivos, llamadas wrapper objects: Number, String y Boolean. Cuando a un valor primitivo se le invoca un método, JavaScript automáticamente construye un objeto temporal de la clase correspondiente y lo inicializa con el dato primitivo y luego invoca el método. Es decir, cuando se escribe algo como

var s = "Hola mundo!";
var len = s.length;

La propiedad length no proviene del string s, sino de un objeto temporal String inicializado con s, una copia, que después de usarse, se desecha. Este comportamiento también aplica para los datos primitivos de Number y Boolean.

Ejercicio. Calcule el tiempo que tarda en ejecutarse todos los scripts en conjunto, que haya programado en este laboratorio. Presente el resultado al usuario, en milisegundos o segundos (si se tarda más de uno).

Por valor o por referencia

En JavaScript los datos se manipulan por valor y por referencia. Automáticamente JavaScript sigue esta regla: valores primitivos (booleanos y números) son manipulados por valor; los objetos, arreglos y funciones, por referencia. No existe una notación especial para que el programador pueda decidir cuáles manipular por referencia y cuales por valor. Debe ajustarse a las reglas expuestas y tener los cuidados respectivos.

La manipulación tiene tres escenarios: cuando se copia un dato, cuando se pasa por parámetro a una función y cuando se compara. Cuando se invoca a una función, las copias y referencias simplemente se harán de acuerdo al tipo de dato enviado. Por ejemplo:

var n = 1;   // Es un número, el literal 1 se copia por valor a n
var t = n;   // t será un número, se copia también por valor

// Los parametros de la funcion no tienen tipo de datos, reciben por valor o referencia
// de acuerdo a los datos enviados en la invocacion
function addto(total, value) { total += value; }

// Se invoca con dos numeros, al llamar a addto, total y value tendrán copias por valor
// así que la funcion no tendra el efecto que quiere conseguir, y por ende, t se mantendra
// intalterado con su valor 1
addto(t, n);

// La comparacion se hace por valor, es decir, se comparan byte a byte variables distintas
if ( t == n ) document.write('Los números son copiados');   // Se evaluará como true

Cuando los datos se manipulan por referencia, se hace una copia de la dirección del objeto. Es decir, se copian, pasan por parámetro y comparan direcciones que apuntan a lo mismo, de forma similar a los punteros C. Por ejemplo:

// Crea un objeto y su direccion la copia a hoy
var hoy = new Date();

// Copia la direccion de hoy a navidad, ambas variables refieren al mismo objeto
var navidad = hoy;

// Modifica al objeto apuntado, por ende, 'hoy' tambien referirá al 25 de diciembre
navidad.setMonth(11);
navidad.setDate(25);

// Si se comparan, se hará una comparación de direcciones, y se evaluará como true
if ( hoy == navidad ) document.write('Hoy es navidad!');

// se crea otro objeto distinto pero con iguales datos
var navidad2 = new Date(navidad.getFullYear(), 11, 25);

// Esta comparacion evaluara como falsa, ya que tienen direcciones distintas
if ( navidad == navidad2 ) document.write('La navidad es universal!');


// Una funcion recibira por valor o referencia dependiendo de como se le invoque
function addDays(date, days)
{
   date.setDate( date.getDate() + days );
}

// En esta invocacion, date recibira una copia de direccion de navidad y days una copia del
// literal 6 logrando el efecto deseado para navidad, pero 'hoy' se vera afectado tambien.
addDays(navidad, 6);

// Esta funcion no provoca el efecto deseado pero ilustra como trabajan las direcciones
function addDaysB(dateB, daysB)
{
   var tmpDate = new Date(dateB);
   tmpDate.setDate( tmpDate.getDate() + daysB );
   dateB = tmpDate; // Esto solo modifica la direccion de dateB y no al objeto real
}

// Al hacer esta invocacion la funcion addDaysB crea otro objeto internamente y lo asigna a su
// parametro temporal dateB y no a navidad2. Al salir de la función, navidad2 permanece
// inalterada. Esto tambié explica cómo actuar cuando no se quiere alterar datos que son pasados
// por referencia: haciendo copias
addDaysB(navidad2, 6);

Los strings no caben en ninguna de esas dos categorías. Una vez creado un string, JavaScript no permite modificarlo, es decir, son inmutables. Asi que no tiene importancia saber si se pasa por copia o por referencia. Puede pensarse lo segundo por razones de eficiencia. Lo único que se puede averiguar es si se comparan por valor o por referencia, de esta forma:

// Determining whether strings are compared by value or reference is easy.
// We compare two clearly distinct strings that happen to contain the same
// characters. If they are compared by value they will be equal, but if they
// are compared by reference, they will not be equal:
var s1 = "hello";
var s2 = "hell" + "o";
document.write(s1 == s2 ? "Strings compared by value" : "Strings compared by reference");

Variables

Una de las primeras diferencias con otros lenguajes como C es que las variables de JavaScript no tienen un tipo de datos, por lo que es perfectamente válido asignarles un valor de un tipo y luego otro de diferente naturaleza:

var i = 16;
i = "dieciséis";

Antes de que usted pueda usar una variable, tiene que haberla declarado con la palabra reservada var, o JavaScript declarará una implícitamente por usted. Lo cual puede traer efectos secundarios. En la declaración se deben inicializar las variables, sino JavaScript lo hará con el valor undefined.

Si una variable se declara dos veces en secciones var distintas y la segunda declaración tiene inicialización actúa como una simple asignación. Todas las variables declaradas en una sección var son permanentes, es decir, no se pueden eliminar con el operador delete. La primera sección de la cláusula for o for/in, permite también declarar variables.

for(var i = 0; i < 10; ++i) document.write(i, '\n');
for(var i in obj) document.write(i, '\n');

Cuando se trata de leer una variable que no existe, se genera un error y el navegador detiene la ejecución del script. Si se le trata de asignar algo a una variable inexistente, se creará una variable global implícita. Las variables globales son visibles en cualquier lugar donde haya código JavaScript.

Las variables locales son aquellas creadas en secciones var en los cuerpos de las funciones, incluyendo a los parámetros. Las variables locales se superponen a las globales. A diferencia de C y Java, JavaScript no tiene diferente alcance (scope) en cada bloque. Mejor dicho, los bloques de JavaScript lo definen las mismas funciones y no las parejas { }, así todas las variables de una función comparten el mismo alcance, indiferentemente del nivel de anidamiento de llaves en que se declaren las variables. En el siguiente ejemplo; las variables o, i, j, k comparten el mismo scope.

function test(obj)
{
   var i = 0;                        // i is defined throughout function
   if (typeof obj == "object")
   {
      var j = 0;                     // j is defined everywhere, not just block
      for(var k=0; k < 10; ++k)   // k is defined everywhere, not just loop
      {
         document.write(k);
      }
      document.write(k);             // k is still defined: prints 10
   }
   document.write(j);                // j is defined, but may not be initialized
}

Las variables locales ocultan las globales, y las locales tienen alcance (scope) de toda la función. Esto puede generar resultados confusos o sorprendentes. Por ejemplo:

var str = "global";
function f( )
{
   alert(str);         // Displays "undefined", not "global"
   var str = "local";  // Variable initialized here, but defined everywhere
   alert(str);         // Displays "local"
}
f( );

En la función anterior no se imprime "global" en el primer alert de la línea 4, porque se está usando la variable str local de la línea 5, la cual no ha sido inicializada aún. Este comportamiento equivale al siguiente código:

var str = "global";
function f( )
{
   var str;
   alert(str);         // Displays "undefined", not "global"
   str = "local";      // Variable initialized here, but defined everywhere
   alert(str);         // Displays "local"
}
f( );

La moraleja es siempre declare sus variables juntas al inicio de una función JavaScript. Se puede distinguir dos tipos de variables: undefined y unassigned. Una variable sería indefinida si nunca se ha declarado. Al tratar de leerla se generará un error y el navegador detiene la ejecución del script. Una variable es unassigned si está declarada pero no se le ha asignado un valor aún. Al tratar de leerla, se evaluará con el valor especial undefined (posiblemente mejor hubiera sido llamado unassigned).

var x;     // Declare an unassigned variable. Its value is undefined.
alert(u);  // Using an undeclared variable causes an error.
j = 3;     // Assigning a value to an undeclared variable creates the variable.

Cuando se inicia un intérprete de JavaScript, lo primero que hace antes de ejecutar el código fuente, es crear el global object, el cual se encarga de almacenar todas las variables globales que se declaren en el código. Cuando usted declara una variable global, realmente está declararando una propiedad del global object.

El intérprete de JavaScript también define convenientemente otras propiedades (variables y métodos) en el global object que son de uso común, como Infinity, parseInt(), y Math. La palabra reservada this usada en un contexto global (fuera del cuerpo de una función), referencia específicamente al global object. En el caso del navegador, el global object es además la ventana del navegador, y se cumple:

this == this.window == window

Si las variables globales son propiedades de un global object ¿qué son las variables locales? También son propiedades de un objeto temporal llamado call object, el cual se construye en cada invocación de una función, y mantiene juntos los parámetros, variables declaradas en el cuerpo de la función y el código de la misma. Este objeto es el que impide que las variables locales sobrescriban las globales cuando tienen el mismo nombre. Dentro de una función, this hace referencia al call object en lugar del global object.

Expresiones

Operadores

Los operadores aritméticos de resta (-), multiplicación (*), división (/) y módulo (%) sólo trabajan con números. Si se usan con otro tipo de datos, JavaScript intentará convertirlos a números invocándoles el método valueOf(). Ya que todo número en JavaScript es un flotante, la división siempre es real, es decir, no hay división entera como ocurre en otros lenguajes. El operador + actúa como operador de suma si sus operandos son numéricos, y como el operador de concatenación si al menos uno de ellos es string.

JavaScript tiene dos operadores para determinar semejanza. El operador de igualdad (==) indica si dos valores son el mismo incluso tras hacer conversiones de tipos. El operador de identidad (===) dice que dos valores son idénticos si son iguales y tienen el mismo tipo de datos. Sus opuestos respectivos son el operador de desigualdad (!=) y de no identidad (!==). Por ejemplo:

"0" ==  false  // produce true
"0" === false  // produce false

"037.50" ==  37.5 // produce true
"037.50" === 037.50 // produce false

Ejercicio. Pruebe todas las expresiones que aparecen en los ejemplos de esta sección en el intérprete de JavaScript de su navegador. Por ejemplo, Chrome provee una "JavaScript Console" (Ctrl+Alt+J), donde puede introducir sentencias que serán evaluadas "en vivo". Sugerencia cognitiva: escriba cada expresión manualmente, en lugar de copiarla y pegarla.

Los operadores de comparación <,<=,>,>= actúan como de costumbre si sus operandos son del mismo tipo, sino se tratan de convertir a números, y si esta conversión falla se generará NaN, que siempre se evalúa como false en expresiones lógicas.

1 + 2        // Addition. Result is 3.
"1" + "2"    // Concatenation. Result is "12".
"1" + 2      // Concatenation; 2 is converted to "2". Result is "12".
11 < 3       // Numeric comparison. Result is false.
"11" < "3"   // String comparison. Result is true.
"11" < 3     // Numeric comparison; "11" converted to 11. Result is false.
"one" < 3    // Numeric comparison; "one" converted to NaN. Result is false.

Los operadores lógicos son los mismos de C/C++/Java: &&, || y !. Pero guardan una diferencia. Los operandos se convierten a Boolean temporalmente para hacer la evaluación del operador, pero el resultado del operador no es Boolean, sino del tipo de datos del operando izquierdo. A esto se le puede sacar ventaja. En el siguiente ejemplo, la variable max adquirirá el primer valor numérico que no sea null en lugar de un valor booleano.

// If max_width is defined, use that. Otherwise look for a value in
// the preferences object. If that is not defined use a hard-coded constant.
var max = max_width || preferences.max_width || 500;

El operador ternario ?: funciona igual que en C. Los operadores de bits (bitwise operators): and (&), or (|), xor (^), not (~), shift left (<<), shift right con signo (>>) y shift right with zero fill (>>>), sólo trabajan con enteros de 32 bits y su uso es inusual en JavaScript.

El operador in recibe al lado izquierdo un string y al derecho un objeto o arreglo. Se evalúa como true si el string es el nombre de una propiedad del objeto al lado derecho, incluso si es heredada. Ejemplos:

var point = { x:1, y:1 };        // Define an object
var has_x_coord = "x" in point;  // Evaluates to true
var has_y_coord = "y" in point;  // Evaluates to true
var has_z_coord = "z" in point;  // Evaluates to false; not a property of point
var tstr = "toString" in point;  // Inherited property from Object; evaluates to true

El operador instanceof espera al lado izquierdo un objeto y al lado derecho el nombre de una clase (realmente una función constructora). Evalúa a true si el lado izquierdo (el objeto) es una instancia de la clase.

var d = new Date();  // Create a new object with the Date( ) constructor
d instanceof Date;   // Evaluates to true; d was created with Date( )
d instanceof Object; // Evaluates to true; all objects are instances of Object
d instanceof Number; // Evaluates to false; d is not a Number object

var a = [1, 2, 3];   // Create an array with array literal syntax
a instanceof Array;  // Evaluates to true; a is an array
a instanceof Object; // Evaluates to true; all arrays are objects
a instanceof RegExp; // Evaluates to false; arrays are not regular expressions

El operador unario typeof genera un string con el nombre del tipo de datos de su argumento a la derecha, que puede o no estar entre paréntesis. Típicos resultados son "number", "string", "boolean", "object" (incluye los arreglos y null), "function" y "undefined" cuando el parámetro no ha sido declarado. Ejemplo:

typeof undefined      // retorna "undefined"
typeof false          // retorna "boolean"
typeof (3 / 1.1)      // retorna "number"
typeof '0'            // retorna "string"
typeof {}             // retorna "object"
typeof []             // retorna "object"
typeof null           // retorna "object"
typeof function() {}  // retorna "function"

return typeof value == "string" ? "'" + value + "'" : value;

Ya que typeof retorna "object" para objetos de tanta naturaleza, su utilidad se reduce a saber si su operando es o no de un tipo de datos primitivo. Para saber la clase de un objeto hay que recurrir a otras técnicas como el operador instanceof, o la propiedad Object.constructor, como se verá luego.

Igual que en C++ y Java, el operador new crea un objeto vacío, invoca un constructor para que lo inicialice, y retorna la dirección de memoria del objeto. Si el constructor no recibe parámetros, puede omitirse sus paréntesis.

o = new Object;    // Optional parentheses omitted here
d = new Date();    // Returns a Date object representing the current time

function Point(x, y) { this.x = x; this.y = y; }  // Una función constructora
p = new Point(3.0, 4.0);   // Crea un objeto vacío y lo inicializa con la función constructora

El operador delete intenta eliminar una propiedad de un objeto, un elemento de un arreglo o la variable especificada como operando. Retorna true si la eliminación fue exitosa. Algunas variables o propiedades no pueden ser eliminadas, como las declaradas en secciones var. Si el único argumento de delete no existe, se retorna true. Ejemplos:

var o = {x:1, y:2};  // Define a variable; initialize it to an object
delete o.x;          // Delete one of the object properties; returns true
typeof o.x;          // Property does not exist; returns "undefined"
delete o.x;          // Delete a nonexistent property; returns true
delete o;            // Can't delete a declared variable; returns false
delete 1;            // Can't delete an integer; returns true
x = 1;               // Implicitly declare a variable without var keyword
delete x;            // Can delete this kind of variable; returns true
x;                   // Runtime error: x is not defined

Nótese que delete no se trata de eliminar memoria dinámica como ocurre en C++, lo cual se hace en JavaScript con el garbage collector, no hay otra forma. El delete de JavaScript elimina una propiedad, es decir, deja de existir; no es que se le asigne undefined:

var my = new Object( );   // Create an object named "my"
my.hire = new Date( );    // my.hire refers to a Date object
my.fire = my.hire;        // my.fire refers to the same object
delete my.hire;           // hire property is deleted; returns true
document.write(my.fire);  // But my.fire still refers to the Date object

Sentencias

Las sentencias en JavaScript deben terminar en punto y coma, aunque no es obligatorio. Las estructuras de control condicional son las mismas de C: if, else y switch, como muestra el siguiente ejemplo.

if (username != null)
   alert("Hello " + username + "\nWelcome to my blog.");
else if ( askname )
{
   username = prompt("Welcome!\n What is your name?");
   alert("Hello " + username);
}
else
   alert("Hello there");

La estructura de control switch es más flexible que en C. Al ser un lenguaje interpretado, los casos del switch son expresiones, no sólo constantes. El parámetro del switch se compara contra cada case utilizando el operador de identidad ===, hasta encontrar una coincidencia o el default si fue provisto por el programador.

function transfer(file, method)
{
   switch(method)
   {
      case 'none':
         break;

      case 'ftp':
         return transfer_ftp(file);

      case 'rsync':
      default:
         return transfer_rsync(file);
   }
}

Los ciclos for, while y do/while tienen la misma sintaxis de C. Por ejemplo:

// Llenar un arreglo con 20 factoriales: del 0 al 19
var factorials = new Array(20);
for(var i = 0; i < factorials.length; ++i)
   factorials[i] = i ? i * factorials[i - 1] : 1;

// Imprimir el arreglo en el documento
for(var i = 0; i < factorials.length; ++i)
   document.write(i, '! = ', factorials[i], '<br>');
Ejemplo del ciclo for en JavaScript. Correr este ejemplo.

Ejercicio. Cree una función JavaScript que reciba un número n y retorne un arreglo con los primeros n números de Fibonacci. Utilice su función para crear un arreglo con los primeros 40 números de Fibonacci e imprímalos en una lista ordenada.

JavaScript agrega un tipo de ciclo más, el ciclo for/in, cuya sintaxis es:

for (property_name in object)
   statement;

donde property_name se refiere al nombre de una variable, una declaración var, un elemento de un arreglo, la propiedad de un objeto, o incluso una expresión. object se refiere a un objeto o una expresión que evalúa en un objeto. El cuerpo del for/in, se ejecuta una vez por cada propiedad del objeto y en cada iteración, property_name adquiere el nombre la propiedad, es decir, un string. Por ejemplo:

for (var prop_name in my_object)
   document.write("name: " + prop_name + "; value: " + my_object[prop_name], "<br/>");

Ejercicio. Utilice un ciclo for/in para imprimir recursivamente los activos del inventario que hizo en el ejercicio 5.16.

Ya que property_name puede ser una expresión arbitraria, cada vez que se itera se podría evaluar de forma diferente. Por ejemplo, este código copia los nombres de un objeto en un arreglo:

var obj = {x:1, y:2, z:3};
var arr = new Array( );
var i = 0;
for( arr[i++] in obj )
   ;

// Imprime cada nombre en el arreglo
for(i in arr) alert(i);

No hay forma de especificar en un for/in el orden de recorrido, es dependiente de la implementación de JavaScript; lo único que asegura el estándar es que se recorrerán todas las propiedades enumerables del objeto. Una propiedad es enumerable si es definida por el objeto mismo, es decir, no es heredada.

Ejercicio. Cree un juego de funciones que cada vez que son invocadas generan un valor aleatorio de uno de los siete tipos de datos de JavaScript: valor especial, booleano, número, string, función, objeto, y arreglo. Por ejemplo:

function genSpecialValue() { return Math.random() < 0.5 ? null : undefined; }
function genBoolean() { ... }
function genNumber() { ... }
function genString() { ... }
function genFunction() { ... }

function genObject() { ... }
function genArray() { ... }

function genRandomValue() { ... }

Cree una función genRandomValue(), que invoca aleatoriamente a cualquiera de las funciones anteriores para retornar un valor aleatorio de un tipo de datos aleatorio.

Modifique genArray() para que genere un arreglo con n elementos aleatorios, cada uno resultado de invocar a genRandomValue().

Modifique genObject() para que genere un objeto con n propiedades aleatorias. Haga que el nombre de la propiedad sea un string aleatorio generado por genString() (considere si se debe mejor implementar una función genIdentifier()). Haga que el valor de la propiedad sea generado por genRandomValue().

Ejercicio. Provea una función (o un juego de funciones) que reciba por parámetro un valor de cualquier tipo de datos, y lo escriba en el documento utilizando la misma notación de objetos de JavaScript (JSON). Note que esta función (o funciones) debe ser recursiva. Idee algún mecanismo para mantener una indentación impecable en el resultado.

Ejercicio. Invoque a su función genArray() para generar un arreglo de elementos aleatorios, e imprímalo en el documento utilizando la función que escribió en el ejercicio anterior. Debe ocurrir que cada vez que se refresque el documento, el navegador genere una estructura de datos distinta, con niveles de composición aleatorios.

Arreglos y funciones como objetos

En JavaScript los arreglos y las funciones son objetos, y por ende tienen propiedades heredadas de las pseudoclases Array y Function respectivamente. A su vez, estas pseudoclases heredan de Object. Esta sección da una introducción de algunas propiedades que estas clases proveen.

Propiedades de Object

Cuando se crea un objeto se guarda una referencia a la función que se utilizó para inicializar el objeto, en la propiedad constructor. Por ejemplo:

var d1 = new Date();
d1.constructor == Date    // Se evalúa como true

La propiedad constructor sirve para saber si un objeto es instancia directa de una psudoclase particular, mientras que el operador instanceof sirve para determinar si el objeto es descendiente de una pseudoclase dada. Por ejemplo:

var d1 = new Date();
d1.constructor == Date    // true
d1.constructor == Object  // false
d1 instanceof Date        // true
d1 instanceof Object      // true, todo objeto es descendiente de Object
d1 instanceof Array       // false

var o1 = new Object();
var o2 = {};
o1.constructor == Object  // true
o2.constructor == Object  // true
o1 instanceof Object      // true
o2 instanceof Object      // true

var a1 = new Array();
var a2 = [];
a1.constructor == Array   // true
a2.constructor == Array   // true
a1.constructor == Object  // false
a2.constructor == Object  // false
a1 instanceof Array       // true
a2 instanceof Array       // true
a1 instanceof Object      // true
a2 instanceof Object      // true

function f1() {};
var f2 = function() {};
var f3 = new Function('', ';');
f*.constructor == Function // true
f*.constructor == Object   // false
f* instanceof Function     // true
f* instanceof Object       // true

El método toString() heredado de la pseudoclase Object es invocado automáticamente por JavaScript cuando necesita convertir el objeto en un string. La implementación por defecto retorna la cadena "[object Object]" que es poco significativa. Por ende, este método debería ser sobrescrito por clases descendientes.

El método valueOf() es invocado automáticamente por JavaScript cuando el objeto se utiliza en un contexto numérico. También debería ser sobrescrito para clases descencientes.

Propiedades de Array

La propiedad más natural de un arreglo es length que indica la cantidad de elementos que hay almacenados en él. De hecho es la principal diferencia entre un arreglo y un objeto tradicional. La siguiente tabla muestra una lista de métodos heredados de la pseudoclase Array.

Métodos de Array
Método Detalles
push(elems) Inserta los elementos enviados por parámetro al final del arreglo. Retorna la cantidad de elementos que quedan en el arreglo.
[1,2,3].push(9,null) // retorna 5, deja [1,2,3,9,null]
pop() Elimina y retorna el último elemento del arreglo. Si está vacío, retorna undefined.
[1,2,3].pop() // deja [1,2]
unshift(elems) Inserta los elementos enviados por parámetro al inicio del arreglo. Retorna la cantidad de elementos que quedan en el arreglo.
[1,2,3].unshift(9,null) // retorna 5, deja [9,null,1,2,3]
shift() Elimina y retorna el primer elemento del arreglo
[1,2,3].shift() // deja [2,3]
join(sep) Retorna un string resultado de concatenar todos los elementos en el arreglo, separados por la cadena sep, o comas si se omite.
[1,2,3].join() // genera "1,2,3"
[1,2,3].join('; ') // genera "1; 2; 3"
toString() Está sobrescrito para hacer lo mismo que join().
[1,[8,9],2].toString() // retorna "1,8,9,2"
reverse() Invierte el orden de los elementos en el arreglo.
[1,2,3].reverse() // genera [3,2,1]
sort(comp) Ordena los elementos en el arreglo de acuerdo a la función comparadora comp(), sino los ordena alfabéticamente invocando toString() a cada uno de sus elementos.
[7, 200, 81].sort() // genera [200,7,81]
[7, 200, 81].sort(function(a,b){return a-b;}) // genera [7,81,200]
slice(i,j) Retorna un nuevo subarreglo con los elementos encontrados desde la posición i hasta j - 1, es decir, el que está en j no se incluye.
[1,2,3,4,5].slice(1,3) // genera [2,3]

Propiedades de Function

En JavaScript las funciones son objetos y por ende también tienen propiedades. Una función puede ser invocada con igual, menos o más argumentos de los esperados por la función. En JavaScript es fácil, incluso, implementar una función que reciba una cantidad arbitraria de parámetros.

Cada vez que se invoca una función, JavaScript llena un objeto-arreglo arguments que es accesible desde la función. Como es de esperar arguments.length indica la cantidad de argumentos con que se invocó la función y arguments[i] accede al argumento i + 1 en la lista. El siguiente es un ejemplo de una función tradicional:

// Sea una funcion que espera dos parametros, ignora los demas
function min(a,b) { return a < b ? a : b; }

// En JavaScript es completamente valido invocarla con menos o mas parametros
min(3, 0.07, -0.5);   // retorna 0.07

// Al hacer la invocacion anterior en min() ocurre lo siguiente:
//   a == 3
//   b == 0.07
//   arguments[0] == 3
//   arguments[1] == 0.07
//   arguments[2] == -0.5
//   arguments.length == 3
//   min.length == 2   // la cantidad de argumentos declarados en la funcion

Como se ve en el ejemplo anterior, la invocación de min() con tres parámetros falla el resultado esperado por el llamador. La función min podría detectar esto con la propiedad length, heredada de Function, la cual indica la cantidad de parámetros especificados en la declaración de la función. Por ejemplo:

// Esta version de min solo trabaja con dos parametros
function min(a,b)
{
   // Si el numero dado de argumentos es diferente del esperado, lance un error
   if ( arguments.length != min.length )
      throw new Error('min(): expected ' + min.length + ' arguments, sent ' + arguments.length);

   // Solo dos parametros fueron provistos como se esperaba
   return a < b ? a : b;
}

try
{
   min();              // Lanza excepcion
   min(3);             // Lanza excepcion
   min(3, 0.07);       // Retorna 0.07
   min(3, 0.07, -0.5); // Lanza excepcion
}
catch(exc)
{
   document.write(exc);
}
Una función que exige un número estricto de argumentos. Correr este ejemplo.

Sin embargo, en JavaScript es fácil escribir una función que trabaje con un número arbitrario de parámetros, como se ve a continuación.

function min()
{
   var result = arguments.length ? Number.MAX_VALUE : undefined;
   for (var i = 0; i < arguments.length; ++i)
      if ( arguments[i] < result )
         result = arguments[i];
   return result;
}

min();               // retorna undefined
min(3);              // retorna 3
min(3, 0.07, -0.5);  // retorna -0.5
Una función que recibe un número arbitrario de argumentos. Correr este ejemplo.

Ejercicio. Escriba una función que calcule el promedio de una cantidad arbitraria de parámetros. Si es invocada sin parámetros retorna undefined. Llame su función con algunos parámetros y compruebe que el resultado es correcto.

Ya que una función es un objeto, puede tener propiedades, como la propiedad length que es creada por la pseudoclase Function, y el arreglo arguments visto anteriormente. El programador también puede crear sus propias propiedades, las cuales tendrán el efecto de ser "variables estáticas" de la función. El siguiente ejemplo muestra una función que reporta el número de veces que ha sido invocada.

// Crea una "variable estática" como propiedad de la función. JavaScript "parsea" el código
// antes de ejecutarlo, por lo que el nombre de la función puede usarse antes de ser declarada
countCalls.count = 0;

function countCalls()
{
   // Usar la propiedad como si fuese una variable estática
   document.write('<p>countCalls() ha sido llamada ' + ++countCalls.count + ' veces</p>');
}

countCalls();  // Imprime "countCalls() ha sido llamada 1 veces"
countCalls();  // Imprime "countCalls() ha sido llamada 2 veces"
countCalls();  // Imprime "countCalls() ha sido llamada 3 veces"
Simular una variable estática dentro de una función. Correr este ejemplo.

Pseudoclases

JavaScript no tiene el concepto de clase como sí C++ o Java, sino que las clases se simulan con funciones inicializadoras, objetos y prototipos de objetos. Una función constructora o función inicializadora es una función cualquiera que recibe un objeto por parámetro this y le crea propiedades y métodos. Por ejemplo:

// Construye un rectángulo. Recibe un objeto en el parámetro oculto this
function Rectangle(w, h)
{
   // Declara propiedades y las inicializa
   this.width = w;
   this.height = h;

   // También puede crear métodos
   this.area = function() { return this.width * this.height; }
}

Las funciones constructoras son invocadas con el operador new. Este operador hace lo siguiente. Crea un objeto vacío, sin propiedades. Invoca a la función constructora pasándole el objeto vacío por parámetro para que lo incialice. Finalmente retorna la dirección de memoria del nuevo objeto.

var rect1 = new Rectangle(2, 4);    // rect1 = { width:2, height:4 };
var rect2 = new Rectangle(8.5, 11); // rect2 = { width:8.5, height:11 };
document.write("Area of rect2: " + rect2.area() ); // Imprime "Area of rect2: 93.5"

Todos los objetos que sean inicializados con la función Rectangle() tendrán un width y un heigth, el desarrollador puede utilizar con confianza estas propiedades. La función constructora no debe retornar un valor, ya que reemplazará el resultado de la expresión new. Nótese que nunca hubo una clase, sino una función constructora y objetos tradicionales que son inicializados con ella. Por esto se les llama pseudoclases y la función constructora es quien da nombre a la pseudoclase.

Como es de esperarse, todos los objetos inicializados con Rectangle() tienen su propia copia independiente de width y height. Esto implica que en el ejemplo anterior, si a rect2.width se le asigna otro valor, no afectará al valor de rect1.width, como el programador naturalmente espera. Sin embargo, esto mismo ocurre con los métodos: cada objeto inicializado con la función constructura Rectangle() tendrá su propia copia independiente del método area(), lo cual es ineficiente. Esta redundancia se elimina con los objetos prototipo.

Todas las funciones que el programador defina, tendrán automáticamente otra propiedad llamada prototype creada por Function, de la misma forma que length y arguments. La propiedad prototype es un objeto, definido como una "variable estática" y por tanto, todas las invocaciones a la función tendrán acceso al mismo prototype.

Como la funciones constructoras son funciones normales, también tienen una propiedad prototype, y cada vez que se invoca el operador new, éste implícitamente agrega una propiedad al nuevo objeto para que apunte al prototype de la función constructora. El siguiente pseudocódigo explica lo que hace el operador new internamente:

// El programador define una función constructora
function Constructor(a,b) { this.prop1 = a; this.prop2 = b; }

   // La pseudoclase Function inserta una propiedad length en la función implícitamente
   Constructor.length = 2;

   // La pseudoclase Function inserta una propiedad prototype en la función implícitamente
   Constructor.prototype = {};


// El programador crea un nuevo objeto con su función constructora
var obj1 = new Constructor(a,b);

   // El operador new hace esto internamente:

   // 1. Crea un objeto vacío
   var newObject = {};

   // 2. Crea una propiedad prototype en el objeto que apunta al prototipo de la función
   newObject.prototype = Constructor.prototype;

   // 3. Invoca a la función constructora para que inicialice el nuevo objeto, la cual
   // creará las propiedades prop1 y prop2
   Constructor.call(newObject, a, b);

   // 4. Retorna el objeto recién inicializado
   return newObject;

// obj1 tendrá al final las siguientes propiedades:
obj1.prop1
obj1.prop2
obj1.prototype

El lenguaje JavaScript especifica que cualquier propiedad asignada al objeto Constructor.prototype, será automáticamente una propiedad compartida por todos los objetos construidos con la función Constructor, incluso, aunque dicha propiedad sea asignada después de que los objetos fueron creados. De esta forma, las propiedades creadas por la función constructora directamente en el objeto serán copias independientes en cada objeto, y las propiedades creadas en el prototipo de la misma, serán propiedades compartidas por todos los objetos (lo que en C++ y Java se conoce como miembros estáticos). Por ejemplo:

// Construye un rectángulo. Recibe un objeto en el parámetro oculto this
function Rectangle(w, h)
{
   // Cada objeto tendrá una copia independiente de estas propiedades
   this.width = w;
   this.height = h;
}

// Este método será compartido por todos los objetos creados con new Rectangle()
Rectangle.prototype.area = function() { return this.width * this.height; }

Nótese que el método area() no se definió dentro de la función constructora. Si eso se hubiera hecho, se haría la asignación por cada rectángulo creado durante la ejecución del programa, lo cual es ineficiente.

El prototype es el lugar ideal para definir métodos, constantes y variables compartidas por todos los objetos de una misma pseudoclase. El programador querrá la mayor parte del tiempo tratar estas propiedades como de sólo lectura. Si modifica una propiedad en el prototipo directamente, el cambio afectará a todos los demás objetos inmediatamente; pero si intenta modificar la propiedad a través de un objeto cualquiera, creará una nueva propiedad en ese objeto que oculta la del prototipo. Por ejemplo:

// El programador define una pseudoclase con una función constructora
function Circle(radius) { this.radius = radius; }

// Y un método que será compartido por todos los objetos creados con new Rectangle()
Circle.prototype.area = function() { return Math.PI * this.radius * this.radius; }

// circle1 y circle2 tienen su respectivo radius, son copias independientes
var circle1 = new Circle(7);
var circle2 = new Circle(2.5);

// circle1 y circle2 comparten el mismo método area()
if ( circle1.area === circle2.area )
{
   document.write('circle1.area() = ', circle1.area() ); // Imprime 153.94
   document.write('circle2.area() = ', circle2.area() ); // Imprime 19.63
}
else
   document.write('Circles do not share the same area() method!');

// El programador trata de modificar el método area() a traves de un objeto
circle2.area = function() { return Math.PI * this.radius * this.radius / 2; }

// Lo anterior crea una propiedad area() sólo en circle2 que tapa la del prototipo
document.write('circle1.area() = ', circle1.area() ); // Imprime 153.94
document.write('circle2.area() = ', circle2.area() ); // Imprime 9.82

// Al modificar una propiedad del prototipo afecta a todos los objetos, creados o nuevos
Circle.prototype.area = function() { return 0; }

// Un nuevo objeto aparece
var circle3 = new Circle(3.1);

// Todos los objetos reflejan el cambio excepto aquellos que han sobrescrito la propiedad
document.write('circle1.area() = ', circle1.area() ); // Imprime 0
document.write('circle2.area() = ', circle2.area() ); // Imprime 9.82
document.write('circle3.area() = ', circle3.area() ); // Imprime 0
Conflicto de propiedades en el objeto y el prototipo. Correr este ejemplo.

Idealmente cuando un programador define una pseudoclase, debe redefinir algunos métodos heredados de la pseudoclase Object. El método toString() debe regresar una representación "string" del objeto. Opcionalmente puede redefinir el método parse(). Si su pseudoclase se puede convertir en un valor primitivo, implemente también valueOf().

Ejercicio. Escriba una jerarquía de pseudoclases (funciones constructoras) que generen un documento XHTML válido de longitud y estructura aleatoria. Por ejemplo, comience con la pseudoclase Document. Su función constructora crea las propiedades de un documento típico: el encabezado y el cuerpo, los cuales son a su vez otros objetos cnstruidos con pseudoclases.

La función constructora de Body crea un arreglo de una cantidad aleatoria de elementos hijos escogidos también aleatoriamente. Para efectos de este ejercicio, basta con la siguiente jerarquía.

Document: Header, Body
Header: Style
Body: (Paragraph|Heading|List)+
List: ListItem+
ListItem: Text|Paragraph+|List

De esta forma cada elemento de su jerarquía tendrá al menos: un arreglo de elementos hijos (llámese children); y un método print() que imprime el elemento usando document.write(), y propaga el llamado a todos sus hijos. El método print() debe declararse en el prototipo de la función constructora para que sea compartido por todos los objetos de la misma pseudoclase.

Escriba un documento XHTML que cree un objeto Document y llame su método print(), el cual debe imprimir el cuerpo del documento. Sugerencia, puede utilizar el siguiente ejemplo:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Random document</title>
   <script type="text/javascript" src="random_doc.js"></script>
   <script type="text/javascript">
      var doc = new Document();
      doc.printHeader();
   </script>
</head>

<body>
   <script type="text/javascript">
      doc.print();
   </script>
</body>
</html>

Ejercicio. Implemente en su jerarquía de elementos una pseudoclase Style que es hijo de Header y que se encarga de generar estilos CSS aleatorios en el documento, de tal forma que al refrescar, la apariencia del mismo sea compleamente aleatoria. Para esto, agregue un método printHeader() a su pseudoclase Document, el cual invoca al print() de Header y éste al de Style.

Programación del navegador

JavaScript es un lenguaje genérico que cualquier software puede implementar para permitir al usuario programarlo. Aunque el número de sistemas que implementan JavaScript va en crecimiento, han sido los navegadores web el ejemplo histórico más evidente. El navegador web utiliza el lenguaje de programación JavaScript para exponer cierta funcionalidad al autor de un sitio web, quien debe aprovecharla sólo para mejorar la experiencia del usuario con su obra, haciéndole más fácil obtener o transmitir información. Por ejemplo, creando efectos visuales que guíen al usuario, ordenando los valores de una tabla para ayudarle a encontrar lo que necesita, ocultado o mostrando información que le es de interés, o intercambiando información con el servidor de tal forma que el usuario no se confunda con una recarga completa de la página.

Ha ido ganando popularidad una corriente llamada Unobtrusive JavaScript (JavaScript no impertinente). Este paradigma sustenta que JavaScript no debería tratar de llamar la atención a sí mismo. No consiste en una variación del lenguaje, sino en un conjunto de prácticas recomendadas, por ejemplo:

El ambiente de desarrollo del navegador

Utilizando JavaScript, el navegador pone a disposición del autor un conjunto de objetos que permiten manipular ventanas, cambiar el URL, modificar el contenido o estilo del documento, comunicarse con el servidor web, y otras operaciones útiles.

El objetivo primordial de un navegador web es desplegar al usuario documentos (X)HTML en una ventana. De ahí nacen los dos objetos más importantes para el programador: la ventana del navegador (window), la cual contiene al documento web (document) del autor.

El objeto window es quizá el más importante, porque no sólo es un simple objeto, es el objeto global. Todas las propiedades del objeto global son variables globales, y viceversa: las variables globales que el programador defina se crearán como propiedades del objeto global window. Así, las dos siguientes declaraciones hacen esencialmente lo mismo:

var answer = 722;
window.answer = 722;

De lo anterior se obtiene que da lo mismo escribir window.document que simplemente document. Dicho de otra forma, el objeto window es el más importante porque todos los demás objetos que existen se acceden a através de él, como se ve en la siguiente jerarquía parcial:

window
   parent
   top
   navigator
   location
   history
   screen
   document
      anchors[]
      links[]
      images[]
      applets[]
      forms[]
         elements[]

La jerarquía que nace del objeto document ha sido estandarizada por el Consorcio Web (W3C) en lo que se llama Document Object Model (DOM), de tal forma que permite a los programadores JavaScript escribir código portable entre navegadores para manipular el documento y sus estilos.

Como se indicó al iniciar este capítulo, el código JavaScript puede correr en cuatro lugares: en un elemento script, en un archivo .js externo, en atributos manejadores de eventos y en el pseudoprotocolo javascript:. Todos comparten el mismo objeto global window y su descendencia; por ende, cualquier propiedad definida en uno de estos lugares, es accesible en todos los demás. En el siguiente ejemplo, la función global genRandom() es definida en el primer script y usada en el segundo script.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Global properties</title>
   <script type="text/javascript">
      // Global property, same as window.genRandom = function() {...}
      function genRandom(maxValue)
      {
         return Math.floor(Math.random() * maxValue);
      }
   </script>
</head>

<body>
   <p>Su número de la suerte es:
   <script type="text/javascript">
      // genRandom() is defined in another script. It can be called here
      // because both scripts share the same global object: window
      var num = genRandom(100);    // Same as window.num = window.genRandom(100);
      document.write( num == 13 ? '(no juegue lotería esta semana)' : window.num );
   </script>
   .</p>
</body>
</html>
Variables globales son propiedades del objeto window. Correr este ejemplo.

El modelo de ejecución de JavaScript

El código almacenado en los elementos script se ejecuta sólo una única vez: cuando el documento (X)HTML es cargado en el navegador. Una vez que se ha completado la carga, la interacción se realiza mediante eventos. Cuando ocurre un evento, normalmente generado por el usuario, el navegador puede invocar código JavaScript del autor que reaccione al evento, el cual se asocia al objeto que genere eventos a través de atributos intrínsecos. En el siguiente ejemplo, cuando el usuario hace click en la imagen se invoca el método next() del objeto window.player, y mientras el mismo botón se mantiene presionado, se invoca el método forward del mismo objeto.

<img onclick="player.next();" onmousedown="player.forward();" src="img/button_next.svg">

El modelo de ejecución de código JavaScript sigue esta regla. El código que aparece en los elementos script es ejecutado en el mismo orden en que aparecen, durante la carga (parsing) el documento web. Cuando un elemento script termina de ejecutarse, es reemplazado por su salida, la cual es el resultado de las posibles invocaciones a document.write() que se hayan hecho. Inmediatamente el navegador continúa analizando esta salida como cualquier otro código (X)HTML, y posteriormente el resto del documento web.

Una vez que el documento ha terminado de analizarse, todos los elementos script se habrán ejecutado y el contenido externo, como imágenes o sonidos, habrá sido cargado; el navegador dispara el primer evento: onload. El código manejador de este evento se escribe como valor del atributo onload del elemento body. El código ejecutado en este punto, puede hacer modificaciones libremente sobre la estructura del documento, ya que éste se encuentra totalmente cargado y analizado (parsed); pero no debe invocar a document.write(), ya que hará algo inesperado, como reemplazar el documento por completo.

Después de cargar el documento, correr los elementos script y atender el evento onload, el navegador entra en la fase de manejo de eventos (event-driven phase), ejecutando el código JavaScript que se haya asociado a los atributos intrínsecos según los vaya disparando el usuario. Estos manejadores de eventos tampoco deben invocar a document.write(), ya que construirán un nuevo documento, remplazando el anterior.

Finalmente, cuando el usuario abandona la página, el browser dispara el evento onunload también de body, dando una oportunidad final al código JavaScript de correr. Este debe ser eficiente y silencioso, por ejemplo para hacer alguna limpieza necesaria, evitando demoras que confundan al usuario. La siguiente tabla resume los eventos intrínsecos que el autor dispone para mejorar la comunicación con el lector.

Eventos intrínsecos
Evento Descripción
onclick onclick se dispara cuando el usuario hace click sobre el elemento. Si onclick retorna false, el browser no efectúa la operación por defecto asociada al elemento, por ejemplo, no seguirá el hiperlink en caso de un enlace (a), o no enviaría un formulario en caso de un botón submit.
onmousedown, onmouseup onmousedown es disparado cuando el usuario presiona el ratón sobre un elemento y onmouseup cuando suelta el botón del ratón. Si ambos eventos ocurren sobre el mismo lugar de un elemento, se generará un onclick. La mayoría de elementos (X)HTML implementan estos dos eventos.
onmouseover, onmouseout onmouseover se dispara mientras el cursor del ratón está sobre un elemento (X)HTML y onmouseout cuando sale de él.
onchange El manejador onchange se dispara en los elementos que permiten ingresar texto, como input, select y textarea cuando el usuario cambia el valor desplegado del elemento y después mueve el foco hacia otro elemento.
onload El evento onload sólo se puede asociar a body y es lanzado cuando el documento y su contenido externo, incluyendo imágenes, han sido completamente cargados.

Supóngase que en una ventana del navegador el documento actual es reemplazado por otro, por ejemplo, cuando el usuario escribe un nuevo URL. El nuevo documento no puede acceder a las propiedades que el documento previo creó en la ventana, ya que el objeto window es reestablecido (reset) por el navegador. Esto implica que todas las funciones y propiedades que los scripts definan, durarán mientras el documento actual se mantenga activo.

Consideraciones de seguridad

El hecho de que código ajeno sea ejecutado en su máquina a través de un navegador, da oportunidad a que intenciones malignas puedan llevarse a cabo. Por esto, los navegadores simplemente no soportan ciertas funcionalidades que en otros lenguajes son naturales, como la capacidad de leer, escribir o eliminar archivos en la computadora cliente.

Otras funcionalidades están limitadas. Por ejemplo, JavaScript puede emplear el protocolo HTTP para intercambiar información con el servidor pero no puede abrir un socket o utilizar alguna primitiva de programación de red. Algunas de estas restricciones son controlables por el usuario, ajustando la política de seguridad del navegador. De acuerdo a esta configuración su programa JavaScript:

  • Podría abrir nuevas ventanas pero sólo en respuesta a un evento del usuario (onclik).
  • Podría cerrar ventanas, pero sólo las abiertas por sí mismo.
  • No puede ocultar el texto en la barra de estado cuando el cursor se mueve sobre un enlace.
  • No puede crear una ventana de área pequeña o muy grande, sin barra de título ni estado.
  • No puede leer ni modificar documentos cargados de servidores diferentes (same-origin policy).

La política del mismo origen (same-origin policy) indica que un script puede acceder únicamente a contenido que tiene el mismo origen que el documento que contiene el script. Es decir, que su código no puede acceder a otro documento que tenga cargado su navegador en otra ventana obtenido de otro sitio web. También dicta que el código JavaScript puede comunicarse a través del objeto XMLHttpRequest sólo con el servidor web de donde se obtuvo el documento.

El objeto window

El objeto global window provee varios métodos y otras propiedades para manipular el navegador, ventanas, direcciones, el historial y similares.

Temporizadores y animaciones

El método window.setTimeout(code, delay) ejecuta el código JavaScript enviado en el parámetro code, delay milisegundos después de que se hace la invocación. Retorna un identificador opaco que necesitará el programador si desea cancelar dicho temporizador con el método window.clearTimeout(). El código es invocado sólo una vez. Si se quiere que ocurra repetitivamente, el código en code debe volver a establecer un setTimeout() o bien usar el método timer = window.setInterval(code, period), el cual ejecuta el código code repetitivamente en intervalos de period milisegundos, hasta que se invoque window.clearInterval(timer). Esto es muy útil para realizar animaciones, como el siguiente reloj digital.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Digital clock</title>
   <style type="text/css">
      body { background: black; margin: 4cm 0; text-align: center; }
      #clock { font-size: 6em; color: #151520; text-shadow: 2px 2px 7px lightgray; }
   </style>
</head>

<body>
   <div id="clock">HH:MM:SS</div>
   <script type="text/javascript">
      window.setInterval( 'updateClock()', 1000 );
      function updateClock()
      {
         var date = new Date();
         document.getElementById('clock').innerHTML = date.toLocaleTimeString();
      }
   </script>
</body>
</html>
Un reloj digital animado con JavaScript. Correr este ejemplo.

En el documento anterior, el elemento con identificador clock de la línea 11 será utilizado para desplegar la hora local del navegador. Cuando el navegador carga el cuerpo de este documento, mostrará el div como de costumbre, aplicándole los estilos que se declararon en la parte superior. Luego encontrará un elemento script y lo ejecutará como es sabido. La invocación a setInterval() en la línea 13 le indica al navegador que debe ejecutar el código updateClock() dentro de 1 segundo, cada segundo. Al ser invocada, la función updateClock() debe cambiar el contenido del <div id="clock">...</div> con el valor actual de la hora. JavaScript requiere una referencia al objeto div que tiene el identificador clock, y la consigue invocando el método document.getElementById() que se estudiará luego. Una vez obtenida la referencia al elemento div, se cambia su contenido con la hora actual en el navegador obtenida en forma de string.

Ejercicio. Modifique el ejemplo del reloj digital para que muestre los milisegundos, y se actualice al menos cuatro veces cada segundo.

El siguiente ejemplo muestra un texto moverse de izquierda a derecha, en un ancho de 320 pixeles. Esta vez no se modifica el contenido de un elemento particular, sino su posición, lo cual es responsabilidad de la presentación, por lo que el código JavaScript debe modificar el estilo CSS del elemento con identificador text dinámicamente. El estilo de un elemento se accede en JavaScript a través de su propiedad style, la cual es un objeto cuyas propiedades tienen el mismo nombre que las propiedades CSS en notación "camelCase" (margin, marginTop, etc.), y sus valores son siempre un string -esto se estudiará con más detalle luego-. En este ejemplo, cada vez que se invoca, animateText() calcula en la variable global textLeft la nueva posición del texto (líneas 18 y 19); y en la línea 21 asigna el resultado de este cálculo a la propiedad style.left, cuyo efecto es el mismo a haber escrito #text { left: textLeft; } en CSS. Para que esto funcione, el elemento #text debe "flotar" sobre el documento, lo cual se hizo en la línea 6 indicando que su posición es absoluta.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Texto animado</title>
   <style type="text/css">
      body { margin: 0; background: #ccc; color: #C8C8C8; font-size: 3em; }
      #text { position: absolute; top: 2em; text-shadow: 1px 1px white, -1px -1px #444; }
   </style>
</head>

<body>
   <div id="text">Su nombre aquí</div>
   <script type="text/javascript">
   <!--
      window.setInterval( 'animateText()', 50 );
      var textLeft = 0;
      function animateText()
      {
         textLeft += 5;
         if ( textLeft > 320 ) textLeft = -320;
         var element = document.getElementById('text');
         element.style.left = Math.abs(textLeft) + 'px';
      }
   -->
   </script>
</body>
Un texto animado con JavaScript para desplazarse en vaivén de izquierda a derecha. Correr este ejemplo.

Ejercicio. Modifique el ejemplo del texto animado para que cuando el usuario haga click en el texto, la animación se detenga, y si se vuelve a hacer click, la animación se reanude.

Información del navegador, la ventana y la pantalla

El objeto global window tiene varias propiedades de sólo lectura que informan el tamaño de la ventana, el tamaño del documento y la posición del navegador en el escritorio:

Propiedades para obtener la geometría del navegador
Propiedad Descripción
window.outerWidth, window.outerHeight Ancho y alto de la ventana del navegador.
window.screenX, window.screenY Posición del navegador en el escritorio del usuario.
window.innerWidth, window.innerHeight Tamaño del área donde el documento se despliega (también llamada área cliente o viewport. Equivale al tamaño de la ventana del navegador sin las barras de menú, herramientas, scrollbars, etc.
window.pageXOffset, window.pageYOffset El segmento del documento actualmente desplegado en la ventana del navegador, expresado como la posición de las barras de desplazamiento (scrollbars).

Ejercicio. Modifique su solución del texto animado para que el texto no esté limitado a un ancho de 320 pixeles, sino que se ajuste dinámicamente al ancho del navegador.

Ejercicio. En su solución del texto animado, reemplace el texto por una imagen, como una esfera o algún gráfico de su agrado. Haga que el gráfico se mueva aleatoriamente por el área de la ventana sin salirse de esta. Es decir, que el gráfico siempre es visible aún cuando se acerque a los extremos de la ventana.

El objeto window.screen provee información sobre la pantalla del usuario, por ejemplo: las dimensiones en pixeles del escritorio (screen.width y screen.height), la profundidad de color en bits por píxel (screen.pixelDepth), el área usable del escritorio sin incluir la barra de tareas (screen.availWidth y screen.availHeight). Con estas propiedades el programador puede escoger qué imágenes presentar, centrar una venana en el escritorio o tareas similares.

El objeto window.navigator contiene información sobre el navegador mismo, como su nombre (navigator.appName), versión (navigator.appVersion), el nombre codificado (navigator.appCodeName, el sistema operativo para el que se compiló el navegador (navigator.platform), si tiene habilitados los cookies (navigator.cookieEnabled), y la identificación que el browser envía en sus encabezados HTTP (navigator.userAgent). No es recomendable utilizar esta información para escribir funcionalidad dependiente del navegador, sino para propósitos de estadísticas o bitácoras.

Ejercicio. Escriba un programa JavaScript que genere una o varias tablas con la información del navegador, la ventana y la pantalla citada anteriormente. No utilice document.write(), sino que su programa debe llenar un elemento div del documento. Provea un botón "Actualizar" que cada vez que se presiona, refresca la información anterior. Recuerde aprovechar el operador for-in.

Abrir y manipular ventanas

JavaScript permite al autor crear nuevas ventanas de navegador en las que puede cargar documentos existentes de su servidor web o generar un nuevo documento dinámicamente. Sin embargo, el abuso que ha cometido la publicidad y otros fines, ha obligado a los fabricantes de navegadores a restringir esta funcionalidad de acuerdo a la política de seguridad, por ejemplo, a permitir abrir ventanas sólo en respuesta a un evento del usuario.

Una invocación al método window.open(url, name, features, repHistory) crea y retorna una referencia hacia una nueva ventana, y por ende, un nuevo objeto window con toda su descendencia de objetos. La nueva ventana cargará un documento si se especifica un url, de lo contrario, tendrá un documento vacío. El parámetro name le da un nombre a la ventana, si ya existe una con ese nombre simplemente se reutiliza, y en tal caso, si repHistory es true el historial de navegación en esa ventana es reemplazado; si es false o se omite, el nuevo documento es simplemente agregado al historial. El siguiente ejemplo crea una pequeña ventana emergente de ayuda cuando se presiona el botón, y la cierra cuando se vuelve a presionar con el método window.close() que cierra gráficamente la ventana pero no destruye el objeto window.

<button id="helpButton" onclick="toggleHelp()">Ayuda</button>
<script type="text/javascript">
   var helpWindow;
   function toggleHelp()
   {
      if ( helpWindow )
      {
         helpWindow.close();
         helpWindow = undefined;
         document.getElementById('helpButton').innerHTML = 'Ayuda';
      }
      else
      {
         helpWindow = window.open('help.html', 'help_window', 'width=400,height=350,status=yes,resizable=yes');
         document.getElementById('helpButton').innerHTML = 'Cerrar ayuda';
      }
   }
</script>
Abrir una ventana en JavaScript. Correr este ejemplo.

El parámetro features permite especificar el tamaño de la nueva ventana y otras decoraciones gráficas. Si se omite, se le dará tamaño estándar y todas las decoraciones existentes (barra de menú, barra de estado, ...). Algunas combinaciones son restringidas por razones de seguridad, por ejemplo, una ventana muy pequeña o situada en una zona no visible.

El objeto retornado por window.open() se puede manipular como cualquier otro objeto window. El siguiente ejemplo, genera un documento dinámicamente en una nueva ventana.

var subwindow = window.open();           // Create a new window with no content
var subdoc = subwindow.document;         // Get its Document object
subdoc.open();                           // Start a new document (optional)
subdoc.write("<h1>Hello world!</h1>");   // Output document content
subdoc.close();                          // End the document

Nótese que tanto document como window tinen métodos open() y close(). El document.open(url) carga un documento a partir del url o genera uno vacío si se omite url. Invocar a document.write() es seguro en una ventana nueva ya que el documento se está cargando (parsing). El método document.close() indica al navegador que el documento ha terminado de cargarse (parsing), el cual responde deteniendo la animación de carga. Sino nunca se llama a document.close(), el navegador creerá que está ante un documento incompleto.

Ejercicio. Modifique el ejemplo de la ventana de ayuda. Cuando se presiona el botón, cree una nueva ventana con un documento vacío, es decir, el parámetro url de window.open() debe estar vacío. Haga que su programa JavaScript genere un documento dinámicamente en la nueva ventana, cuyo contenido sea la misma del ejercicio anterior. Puede utilizar subwindow.document.write() si lo desea.

El objeto window tiene métodos para mover y redimensionar la ventana, lo cual es una pobre práctica, ya que esto debe ser un privilegio del usuario. Son los siguientes: window.moveTo(x, y) mueve la esquina superior izquierda a las coordenadas dadas; window.moveBy(despX, despY) mueve la ventana tantos pixeles relativos a la posición actual; window.resizeTo(width,height) y window.resizeBy(addToWidth, addToHeight) cambian el tamaño de la ventana.

Ejercicio. Haga que la ventana emergente del ejercicio anterior aparezca en el mejor lugar. Para esto, defina un ancho mínimo de la ventana emergente. Si en el escritorio hay campo a la izquierda o a la derecha del navegador, use el que tenga mayor espacio y ocupe ese ancho por completo, de tal forma que la nueva ventana aparecerá al lado del navegador y tendrá su misma altura. Si no hay espacio, sitúe la ventana emergente centrada en el navegador.

El método window.focus() hace que la ventana sea la activa, y window.blur() hace que renuncie a serlo. Es común llamar a focus() después de hacer un window.open(), ya que las ventanas recién creadas no lo tienen por defecto.

Es común querer abrir un documento existente en una nueva ventana y luego hacer que ésta se desplace a diferentes fragmentos del documento dinámicamente. Si los fragmentos están identificados con el atributo id (como en <div id="frag1">...</div>) o con anclas (<a name="frag2">...</a>), se puede cambiar la ubicación del documento con un una de las siguientes instrucciones:

var subwindow = window.open('help.html', 'help_window');
subwindow.location.hash = "#frag1";    // crea una entrada en el History de subwindow
subwindow.location.replace("#frag1");  // no crea una entrada en el History de subwindow

Ubicación del documento

La propiedad window.location de una ventana representa el URL del documento que está desplegado en dicha ventana. Es un objeto que tiene las siguientes propiedades:

Propiedades del objeto window.location
Propiedad Descripción
href Es un string con el texto completo del URL.
protocol, host, pathname, search Representan partes individuales del URL.
reload() Recarga la página como si el usuario lo hiciera en el navegador.
replace(url) Reemplaza el documento actual en la ventana por uno nuevo cuyo URL es dado por parámetro. No genera una entrada en el historial de la ventana, de modo que el usuario no tiene forma de regresar al documento previo.

Al objeto mismo window.location se le puede asignar un string, de la forma window.location = url, que causa el mismo efecto que llamar su método window.location.replace(url), con la diferencia de que se agrega una entrada al historial de navegación de la ventana, de tal forma que el usuario puede retornar al documento previo.

Cuadros de diálogo

El objeto window provee tres métodos para desplegar cuadros de diálogo. window.alert(msg) despliega un mensaje y espera que el usuario lo acepte. window.confirm(msg) presenta un mensaje y solicita al usuario que decida si lo acepta o cancela, lo cual se retorna como un boolean. window.prompt(msg, defaultValue) presenta el mensaje msg y espera que el usuario ingrese un string el cual es retornado o null si cancela. Obligatoriamente.

Deben usarse con moderación, o mejor aún, nunca. La mayoría de usuarios se sentirá molesta al experimentarlos y recuerde que ellos tienen el poder de cerrar el navegador. Así que estos métodos son de ligera utilidad para el programador durante el proceso de desarrollo. Hace varios años, el navegador reportaba errores de JavaScript utilizando diálogos, lo cual era impertinente: le reclamaba al usuario errores que no eran de él. Los navegadores han cambiado a ocultar los errores y el programador debe buscarlos en alguna bitácora o similar.

El objeto document

En esencia un navegador es un programa que despliega un documento web en una ventana. La sección anterior explica cómo manipular con JavaScript esa ventana. Esta sección explica cómo manipular dinámicamente el documento en ella.

Las propiedades más conocidas del objeto document, son los métodos write() y writeln(). Ambos reciben una cantidad arbitraria de parámetros y los escriben en el documento para que sean analizados (parsed) por el navegador posteriormente. Por eso, estos métodos sólo se deben invocar mientras se está cargando (parsing) el documento. Como es de esperar, writeln() después de imprimir todos sus parámetros, agrega un carácter de cambio de línea. Otras propiedades útiles de document son:

Propiedades del objeto window.document
Propiedad Descripción
document.title Accede al título del documento que se encuentra en el encabezado del mismo.
document.lastModified Contiene un string con la fecha de última modificación del documento.
document.URL Es un string con el URL completo de donde se obtuvo el documento.
document.referrer Es un string con el URL del recurso a través del cual el usuario llegó al documento actual.
document.domain Indica el dominio al cual pertenece el documento, y es usado por la política del mismo origen (same origin policy).

Las propiedades anteriores permiten, por ejemplo, escribir un pie de página dinámico, como el siguiente:

document.writeln('<address>');
document.write('<strong><a href="', document.URL, '">', document.title,'</a></strong>');
document.writeln('. Última actualización: ', document.lastModified, '.<br/>');
document.write('&copy;', new Date().getFullYear());
document.write(' <strong><a href="http://', document.domain,'">Mi Sitio</a></strong>');
document.writeln('. Todos los derechos reservados.');
document.writeln('</address>');
Un pie de página dinámico con propiedades del objeto document. Correr este ejemplo.

El modelo de objetos del documento (DOM)

Cuando el navegador carga un documento (X)HTML crea un objeto JavaScript por cada elemento, comentario, o trozo de texto que encuentra en el archivo. Estos objetos se asocian entre sí en una jerarquía llamada modelo de objetos del documento (DOM: Document Object Model), la cual es accesible y modificable a través de JavaScript. Es decir, el DOM es una representación interna del documento utilizando objetos. Un cambio en el DOM se refleja de inmediato en el navegador, lo que permite al autor ajustar el documento dinámicamente para mejorar la comunicación con sus lectores. Es quizá la funcionalidad más importante que un autor busca de JavaScript.

Los objetos en la jerarquía del DOM reciben el nombre genérico de nodos, construidos con la pseudoclase Node. El nodo raíz es siempre el documento, el cual contiene nodos opcionales (declaración XML, declaración de tipo de documento, comentarios) y el nodo del elemento html. Este último tiene dos hijos: el nodo del encabezado (head) y el nodo del cuerpo del documento (body). Los hijos del nodo body varían de acuerdo al documento. Por ejemplo, la siguiente ilustración muestra un documento sencillo y el árbol de nodos que el navegador genera a partir de él.

Un documento y su árbol de nodos

Las relaciones jerárquicas entre nodos implican que un nodo puede tener cero o más nodos hijos (child nodes), nodos hermanos (siblings), un nodo padre (parent), nodos descendientes y nodos ancestros. Estas relaciones se implementan como propiedades de la pseudoclase Node. Dado un nodo se puede acceder a los otros nodos con los que está relacionado. Estas y otras propiedades se resumen en la siguiente tabla.

Propiedades de la pseudoclase Node del XML DOM
Propiedad Descripción
Node.childNodes[] Un arreglo que contiene los hijos del nodo actual en el mismo orden en que fueron definidos en el documento. Si el nodo actual no tiene hijos, este arreglo estará vacío.
Node.firstChild El primer nodo hijo del nodo actual, o null si no tiene hijos.
Node.lastChild El último nodo hijo del nodo actual, o null si no tiene hijos.
Node.nextSibling El nodo hermano que continúa al actual o null si es el último hijo del nodo padre (parentNode). Equivale al nodo que sigue al actual en el arreglo parentNode.childNodes[].
Node.previousSibling El nodo hermano que precede al actual o null si es el primer hijo del nodo padre (parentNode). Equivale al nodo que precede al actual en el arreglo parentNode.childNodes[].
Node.parentNode El nodo padre o contenedor del nodo actual. Es null en el caso del nodo raíz, nodos que han sido separados (removidos) del documento, o nodos creados libremente que aún no han sido insertados en el documento.
Node.nodeType Un entero que indica el tipo de nodo, por ejemplo: elemento (1), texto (3), comentarios (8).
Node.nodeName Un string con un posible nombre para el nodo. Si el nodo es un elemento se utiliza el nombre del elemento ("body", "div", "p", etc.). Para los demás tipos de nodos se utiliza un texto generado de concatenar el símbolo de número con el tipo de nodo, por ejemplo "#text", "#comment", "#document", etc.
Node.nodeValue Si el nodo es de texto (nodeType == 3) o comentario (nodeType == 8), esta propiedad tendrá un string con dicho texto; de lo contrario el valor null.
Node.textContent Contiene el texto si el nodo es de texto (nodeType == 3) o comentario (nodeType == 8), en la misma forma que nodeValue. Para un elemento (nodeType == 1) contiene el texto resultado de concatenar todos los nodos de texto descendientes.
Node.ownerDocument Una referencia hacia el documento que contiene este nodo. Sirve, por ejemplo, para saber si dos nodos son parte del mismo documento.

El siguiente código recorre recursivamente todos los nodos del documento e imprime algunas de las propiedades heredadas de la pseudoclase Node. La cantidad de nodos está fuertemente influenciada por el espacio en blanco que se utilice para indentar el marcado, ya que el parser lo interpretará como datos de carácter y los almacenará en nodos de texto. [Para este ejemplo, la impresión con document.write() debe hacerse en un documento distinto, ya que si se hace sobre el mismo, generará una recursión infinita].

function printNodes(node, doc)
{
   printProperties(node, doc);
   for ( var i = 0; i < node.childNodes.length; ++i )
      printNodes( node.childNodes[i], doc );
}

function printProperties(node, doc)
{
   var properties = ['firstChild', 'lastChild', 'previousSibling', 'nextSibling',
      'nodeName', 'nodeType', 'nodeValue', 'textContent' ];

   for ( var i = 0; i < properties.length; ++i )
      doc.writeln(properties[i], ': ', node[properties[i]]);
}
Una función recursiva que recorre el árbol de nodos del documento e imprime propiedades heredadas de la pseudoclase Node. Correr este ejemplo con indentación, o sin espacios en blanco.

Además de las propiedades para acceder a nodos relacionados, la pseudoclase Node provee métodos para modificarlos:

Métodos de la pseudoclase Node del XML DOM
Propiedad Descripción
Node.appendChild(newChild) Agrega el nodo newChild al final del arreglo childNodes[]. El cambio se ve reflejado de inmediato en el documento cargado en el navegador. Si newChild ya forma parte del documento, será removido de su posición actual y reinsertado en la nueva posición.
Node.insertBefore(newChild, referenceChild) Inserta el nodo newChild en el arreglo childNodes[] justo antes que el nodo referenceChild, el cual obligatoriamente debe existir y ser hijo del nodo al cual se le invocó este método. Si referenceChild es null, newChild se insertará al final. Si newChild ya es parte del documento, será removido de su posición actual y reinsertado en la nueva posición.
Node.replaceChild(newChild, oldChild) Reemplaza oldChild con newChild. Si newChild ya es parte del documento, será removido de su posición actual y reinsertado en la nueva posición. oldChild es separado del documento y el cambio se refleja en el navegador. El objeto oldChild no es eliminado de la memoria, sino que sigue vigente y puede ser reinsertado en el documento posteriormente.
Node.removeChild(child) Quita al nodo child del arreglo childNodes[] y del documento (el cambio se refleja visualmente en el navegador). El objeto nodo child no es eliminado de la memoria, y puede ser reinsertado en el documento posteriormente.
Node.cloneNode(recursively) Retorna una copia del nodo al cual este método es invocado. No clona los hijos y descendientes del nodo a menos que se envíe true en el parámetro recursively. El nodo retornado no es parte del documento, por lo que debe ser insertado posteriormente si se quiere que sea visible al lector.
Node.isEqualNode(other) Retorna true si el nodo es idéntico al enviado por parámetro, es decir, tienen los mismos valores para sus propiedades de datos (como el nombre y tipo), y así recursivamente para todos sus hijos.

Los métodos anteriores permiten mover nodos existentes de un lugar a otro, o clonarlos. El siguiente ejemplo ordena los nodos existentes de una lista alfabéticamente.

<body>
    <h1>Reubicar nodos en el documento</h1>
    <ol id="sortable_list">
        <!-- Note: comments and text nodes will be ignored -->
        <li>Los</li>
        <li>12</li>
        <li>elementos</li>
        <li>de</li>
        <li>esta</li>
        <li>lista</li>
        <li>serán</li>
        <li>ordenados</li>
        <li>alfabéticamente</li>
        <li>sin contar</li>
        <li>Mayúsculas</li>
        <li><p>Incluyendo <strong>este párrafo</strong>.</p></li>
    </ol>
    <p><button id="sort_button" onclick="sort_list()">Ordenar</button></p>
    <script type="text/javascript">
    <!--
        function sort_list()
        {
            // Get the parent node whose children will be sorted
            var list_element = document.getElementById('sortable_list');

            // Do not sort list_element.childNodes[] in place to avoid flickering
            // Get an array of the elements to be processed and work with this array
            var elements = [];

            // Fill the working array with only the desired nodes to be sorted
            for ( var child = list_element.firstChild; child; child = child.nextSibling )
                if ( child.nodeName.toLowerCase() == 'li' )
                    elements.push(child);

            // Function used to sort the array
            function sort_elements(nodeA, nodeB)
            {
                var textA = nodeA.textContent.toLowerCase();
                var textB = nodeB.textContent.toLowerCase();
                return textA < textB ? -1 : textA > textB ? 1 : 0;
            }

            // Sort the elements alphabetically
            elements.sort(sort_elements);

            // Now elements are sorted in the array, not in the document, reinsert them
            // in the desired order. Note, each node will be dettached from its current
            // position and reinserted in the desired position automatically
            for ( var i = 0; i < elements.length; ++i )
                list_element.appendChild( elements[i] );
        }
    -->
    </script>
</body>
Reubicar nodos en el documento. Correr este ejemplo.

Nótese que en el ejemplo anterior se construyó un arreglo temporal (llamado elements[]), el cual se llenará únicamente con los elementos que deben ser ordenados, y luego serán reinsertados en el documento. ¿Por qué no se hizo este cambio directamente en list_element.childNodes[]? Hay varias razones. La más importante es porque childNodes[] no es realmente un arreglo, sino un objeto que se comporta como tal definiendo la propiedad length y una propiedad con nombre numérico para cada elemento que se inserte en él. A estos objetos se les llama "objetos que aparentan ser arreglos" (array-like objects), y su propósito es proveer un arreglo controlado, casi de sólo lectura. Por ejemplo, el programador no puede invocar a list_element.childNodes.sort() porque el array-like object no implementa este método.

La segunda razón es que el arreglo list_element.childNodes[] muy probablemente contiene otros hijos que no son de importancia ordenar, como nodos de texto (surgidos de los espacios en blanco usados para indentar el código fuente) o comentarios. Por esto, es una práctica común construir un nuevo arreglo escogiendo los nodos que realmente se quieren modificar, afectar al arreglo y cuando el procesamiento esté listo, actualizar el documento. Esto reduce además un desagradable efecto de parpadeo (flickering) que confunde al lector al ver partes del documento cambiar arbitrariamente.

Ejercicio. Modifique el ejemplo de ordenar elementos de la lista para que cuando se presiona el botón, ordene intercaladamente en forma ascendente o descendente la lista de elementos.

Cuando se debe crear un nuevo nodo en el documento, no se debe hacer con el operador new, sino utilizar los métodos document.createXxx(data) de la pseudoclase Document. Por ejemplo, document.createElement(tagName) crea un nodo elemento con la etiqueta tagName; document.createTextNode(string) crea un nodo de texto con el string como contenido; document.createComment(string) crea un comentario. Todos ellos crean un nuevo nodo el cual no está asociado al documento. El programador debe insertarlo posteriormente en la jerarquía de nodos para que sea visible al lector, usando alguno de los métodos de la pseudoclase Node como appendChild(), insertBefore() o replaceChild(). El siguiente ejemplo inserta nodos en una lista ordenada.

<body>
    <h1>Crear e insertar nodos en el documento</h1>
    <p><button onclick="create_node()">Agregar nodo</button></p>
    <ol id="dynamic_list">
        <li>Presione el botón para agregarle elementos a la lista</li>
    </ol>
    <script type="text/javascript">
        var counter = 0;
        function create_node()
        {
            var list_item = document.createElement('li');
            var text_node = document.createTextNode('Elemento autogenerado ' + ++counter);
            list_item.appendChild( text_node );
            document.getElementById('dynamic_list').appendChild(list_item);
        }
    </script>
</body>
Crear e insertar nodos en el documento. Correr este ejemplo.

Ejercicio. Escriba un documento XHTML con un poema, y un botón que al ser presionado invierte el orden de las estrofas (no de los versos), utilizando los métodos de la pseudoclase Node. Imprima los números de las estrofas y resáltelos con estilos CSS para que el cambio sea visualmente evidente.

Los nodos pueden ser de distinta naturaleza: elementos, atributos, comentarios, texto, etc. La propiedad Node.nodeType indica el tipo de un nodo particular; es un entero que puede tomar el valor de una de las siguientes constantes:

Tipos de Node del XML DOM
Valor Constante Pseudoclase XML Pseudoclase HTML
1 Node.ELEMENT_NODE Element HTMLElement
2 Node.ATTRIBUTE_NODE Attr
3 Node.TEXT_NODE Text
4 Node.CDATA_SECTION_NODE CharacterData
5 Node.ENTITY_REFERENCE_NODE EntityReference
6 Node.ENTITY_NODE Entity
7 Node.PROCESSING_INSTRUCTION_NODE --
8 Node.COMMENT_NODE Comment
9 Node.DOCUMENT_NODE Document
10 Node.DOCUMENT_TYPE_NODE --
11 Node.DOCUMENT_FRAGMENT_NODE DocumentFragment
12 Node.NOTATION_NODE --

El siguiente ejemplo muestra una función que recorre recursivamente todos los nodos del documento contando cuántos de ellos son elementos:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Conteo de elementos</title>
   <script type="text/javascript">
   <!--
      function countElements(node)
      {
         var count = 0;
         if ( node.nodeType == Node.ELEMENT_NODE )
            ++count;
         for ( var i = 0; i < node.childNodes.length; ++i )
            count += countElements(node.childNodes[i]);
         return count;
      }

      function fillCount()
      {
         document.getElementById('count').innerHTML = countElements(document);
      }
   -->
   </script>
</head>

<body onload="fillCount()">
   <h1>Conteo de elementos</h1>
   <p>El <em>número de elementos</em> presentes en este documento es:
      <strong id="count"></strong></p>
</body>
</html>
Una función recursiva que recorre el árbol de nodos del documento y retorna cuántos de ellos son de tipo elemento. Correr este ejemplo.

Ejercicio. Escriba una función que recorre el documento en que fue invocada y retorna un string con el texto de todo el documento. Imprima este string en un elemento pre al final del documento. Puede usar este enunciado de laboratorio para probar.

Por cada tipo de nodo hay una pseudoclase hija de Node, que aparece en la tercera columna de la tabla anterior. Por ejemplo, cuando el navegador carga el documento y encuentra un comentario, construye un objeto de la pseudoclase Comment, la cual es hija de Node, y cuando encuentra un párrafo creará un objeto Element que es también hijo de Node. Esta jerarquía de pseudoclases se ilustra a continuación.

Herencia de pseudoclases del XML DOM y HTML DOM

Cada subclase agrega propiedades a la pseudoclase Node. Por ejemplo, la pseudoclase Element define las siguientes propiedades útiles:

Propiedades de Element en el XML DOM
Propiedad Descripción
Element.tagName String con el nombre del elemento, por ejemplo: "body", "p", etc.
Element.attributes[] Accede a los atributos del elemento en el orden en que fueron definidos.
Element.getAttribute(name) Retorna el valor del atributo name como un string, o null si el elemento no tiene este atributo.
Element.setAttribute(name, value) Asigna el string value al atributo con nombre name del elemento. Si el atributo no existe, lo crea.
Element.removeAttribute(name) Elimina el atributo con nombre name del elemento. No produce ningún error si el atributo no existe.
Element.getAttributeNode(name) Retorna un objeto de tipo Attr que permite por ejemplo saber si el atributo fue definido o se está usando su valor por defecto.
Element.id El valor del atributo id="value", si fue definido para este elemento.
Element.className El valor del atributo class="value", si fue definido para este elemento.
Element.children[] Un arreglo que contiene únicamente los elementos hijos de este elemento, en lugar de todos los nodos como ocurre con el arreglo childNodes heredado de Node. Quizá childElements hubiese sido un nombre más apropiado.
Element.childElementCount La cantidad de elementos hijos. Equivale a children.length.
Element.innerHTML Un string que con el marcado HTML o XML que representa el contenido del elemento. Si se asigna otro string, será "parseado" por el navegador, el resultado reemplazará a los nodos hijos del elemento, y el efecto será inmediatamente visible en el documento.
Element.outerHTML Un string que con el marcado HTML o XML que representa el elemento mismo más su contenido. Si se asigna otro string, será "parseado" por el navegador, el resultado reemplazará al elemento y sus nodos hijos. El efecto será inmediatamente visible en el documento.
Element.style Un objeto CSSStyleDeclaration con los estilos definidos en el documento para este elemento. Se puede modificar dinámicamente. No contiene los estilos computados.

El objeto Attr representa un atributo de un elemento particular, aunque casi nunca se utiliza, ya que el objeto Element tiene propiedades y métodos para manipular sus atributos. Quizá su propiedad más útil es Attr.specified que permite determinar si el valor del atributo fue explícitamente escrito en el documento, sino se está tomando el valor por omisión.

Los navegadores realmente implementan dos modelos de objetos del documento (DOM), uno para XML y otro especializado para HTML. Dado que JavaScript utiliza XML para transferir datos entre el servidor y el cliente, requiere poder manipular estos documentos a través de una jerarquía de objetos. El DOM que se ha presentado hasta el momento se llama XML DOM, compuesto de objetos bastante genéricos como lo es también XML.

Sin embargo, el navegador provee un conjunto de pseudoclases especializadas para HTML, que componen lo que se llama HTML DOM y que heredan de las pseudoclases presentes en el XML DOM. Algunas de estas pseudoclases se presentan en verde en la figura {ver arriba}. El principal distintivo del HTML DOM es que define la pseudoclase HTMLElement para todos los elementos que sólo tienen los atributos comunes (em, span, dt, etc.) y una pseudoclase HTMLXxxElement para cada elemento Xxx que tiene atributos propios, como HTMLDocument, body (HTMLBodyElement), p (HTMLParagraphElement), ul (HTMLUListElement), etc.

Las pseudoclases HTMLXxxElement definen propiedades homónimas a los atributos (X)HTML que son de lectura y escritura. Por ejemplo HTMLImageElement define el string src que puede cambiarse dinámicamente con JavaScript. Los nombres son homónimos a XHTML, en minúsculas, excepto el atributo class="" que en JavaScript se renombra className debido a que class es una palabra reservada. El tipo de datos de cada atributo es el adecuado, por ejemplo, HTMLImageElement.src es un string, HTMLImageElement.width es un entero y HTMLBodyElement.onload es una función de JavaScript.

El autor puede utilizar estos atributos definidos por las pseudoclases HTMLXxxElement directamente con el operador punto o el operador corchetes, si está seguro de que el objeto es del tipo xxx adecuado. También puede emplear los métodos getAttribute(name) y setAttributeValue(name, value) heredados de Element en el XML DOM; que a diferencia de los atributos de HTMLXxxElement, sólo retornan y reciben valores string.

Ejercicio. Reimplemente el ejercicio 5.25 usando la jerarquía de Node, en lugar de usar una jerarquía de clases propias. Provea una función que al ser invocada obtiene una referencia al elemento body y construye un documento aleatorio.

Localizar elementos en el documento

De todos los tipos de nodos el elemento es incuestionablemente el más importante para el autor, tanto que el objeto Document del XML DOM y HTMLDocument del HTML DOM, proveen varios métodos útiles para localizar elementos por algún criterio. Estos se resumen en la siguiente tabla.

Métodos de HTMLDocument para localizar elementos
Método Descripción
document.getElementById(id) Localiza el único elemento que tiene el identificador id en su atributo homónimo. Retorna el objeto HTMLElement que tiene este identificador o null si ningún elemento en el documento está identificado con el string id.
document.getElementsByTagName(tagName) Selecciona todos los elementos en el documento que fueron creados con la etiqueta tagName, y los retorna en un arreglo que tiene el mismo orden en que aparecen en el documento. Por ejemplo, document.getElementsByTagName('table') retorna un arreglo con todas las tablas del documento. La búsqueda se hace sin importar mayúsculas y minúsculas por compatibilidad con HTML, y si se envía un asterisco '*' en tagName retorna todos los elementos del documento.
document.getElementsByClassName(classValue) El atributo class="c1 c2 ... cN" indica que el elemento pertenece a una o más clases separadas por espacios (y no por comas). Una clase es una agrupación de elementos que comparten características comunes; por ejemplo, una clase ejercicio podría usarse para distinguir todos los párrafos que representen ejercicios en un libro (<p class="ejercicio">...</p>) y aplicarles estilos diferenciados. En JavaScript se puede tener un arreglo con todos los elementos del documento que pertenecen a una o a varias clases con el método document.getElementsByClassName(), que recibe un string con la lista de clases separadas por espacios sin importar el orden.
document.querySelectorAll(selector) CSS tiene una notación estándar para seleccionar elementos a los cuales aplicarles reglas de estilo; por ejemplo, el selector ".ejemplo" recupera todos los elementos que tienen el atributo class="ejemplo" y "#cap01 a[rel='external']" selecciona a todos los enlaces que tienen el atributo <a rel="external"> y que son descendientes del elemento identificado con id="cap01". En JavaScript el método document.querySelectorAll(selector) retorna un arreglo con todos los elementos que satisfacen el selector enviado por parámetro.
document.querySelector(selector) Hace lo mismo que querySelectorAll(), pero sólo regresa el primer elemento que satisface el selector en lugar de una lista de elementos.
document.getElementByName(name) El atributo name sirve para identificar varios objetos relacionados, especialmente una familia de botones de radio (radio buttons). Ya que varios elementos pueden tener el mismo nombre, este método retorna un arreglo de elementos que comparten dicho nombre.

La pseudoclase Element también define los métodos Element.getElementsByTagName(tagName) y Element.getElementsByClassName(classValue), que hacen lo mismo que sus contrapartes de Document, con la diferencia de que en lugar de buscar en todo el documento, buscan únicamente en el subárbol del elemento al cual el método es invocado.

Por conveniencia la pseudoclase HTMLDocument define las propiedades document.head, document.body y document.documentElement, que hacen referencia a sus respectivos elementos, como es de esperar:

document.head == document.getElementsByTagName('head')[0];
document.body == document.getElementsByTagName('body')[0];
document.documentElement == document.getElementsByTagName('html')[0];

Manejo de eventos

El manejo de eventos estático consiste en que el autor asocie una función o código JavaScript a un atributo mientras escribe el documento. Por ejemplo:

<body onload="fixExternalLinks(); insertIndex();">

Esto es una mala práctica, ya que el documento (X)HTML sólo debe especificar contenido y no estilo o comportamiento. Así que la asignación debería hacerse con JavaScript, lo cual es muy simple, por ejemplo:

document.body.onload = function() { fixExternalLinks(); insertIndex(); }

Aunque la asignación anterior se hizo con JavaScript y no en el documento (X)HTML, el esquema anterior tiene varios problemas. Supóngase que la asignación anterior se hizo por un archivo1.js que es parte de una biblioteca 1. Un programador que estuviese creando una biblioteca 2 en JavaScript que es independiente de la anterior, probablemente necesitará invocar una función suya cuando el documento (X)HTML se ha cargado. En nuestro ejemplo esto implicaría tener que agregar una tercera función al evento onload de body ¿Cómo hacer para agregar esta tercera función y mantener las otras funciones que pueda ya tener asociadas?. Es deseable disponer de algún mecamismo que diga simplemente "agregue este manejador al evento onload" sin importar cuántos eventos tiene ya asociados. De esto se encarga el método addEventListener().

El método element.addEventListener(event, function, phase) hace que la función function sea invocada cuando el elemento element produce el evento event. El nombre del evento en el primer parámetro es un string sin el prefijo 'on', por ejemplo "mousedown". El segundo parámetro es una función que debe recibir sólo un parámetro: un objeto de tipo Event y debe retornar nada. El tercer parámetro es un booleano, con true indica que el evento puede ser atendido por uno de sus ancestros, mientras que con false se le da primero oportunidad al elemento antes de que a sus ancestros (el por defecto). Esto se explicará más adelante. El siguiente ejemplo muestra un archivo .js que al ser incluido por un documento (X)HTML, invoca dos funciones cuando éste termina de cargarse.

<!-- onload_event_dom2.html -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Eventos del DOM 2</title>
    <script src="onload_event_dom2.js"></script>
</head>
<body>
    <h1>Manejo de eventos del DOM level 2</h1>
</body>
</html>
// onload_event_dom2.js
function showInfo()
{
    window.alert('Esta función JavaScript fue llamada sin que el documento hiciere' +
        ' nada especial, mas que incluir este archivo .js');
}

function addInfo()
{
    var text = 'Este párrafo es producto de un segundo manejador de eventos.';
    var paragraph = document.createElement('p');
    paragraph.appendChild( document.createTextNode(text) );
    document.body.appendChild( paragraph );
}

window.addEventListener('load', showInfo, false);
window.addEventListener('load', addInfo, false);
Un archivo .js que al ser incluido por un documento, agrega dos manejadores de eventos. Correr este ejemplo.

El método Element.addEventListener() es parte de lo que se conoce como DOM Level 2, el establece que cuando un evento ocurre en un elemento, éste puede antenderlo o alguno de sus elementos padres, es decir, el evento se propaga por la jerarquía de elementos y puede ser atendido llama en una de las siguientes tres fases:

  • Capturing phase. El evento recorre desde el documento hasta el elemento esperando ser atendido.
  • At target phase. El elemento que generó el evento tiene oportunidad de atenderlo.
  • Bubbling phase. el evento retorna desde el elemento hacia el documento.

El tercer parámetro del método element.addEventListener() controla la fase en la que será atendido del evento. Si es true se atenderá en la fase de captura, si es false en la fase objetivo o fase de ascenso (bubbling).

Otra ventaja de usar addEventListener() en lugar de asignar manejadores de eventos directamente a los atributos intrínsecos (DOM Level 0), es que una misma función puede menejar eventos generados en distintos lugares del documento. Por ejemplo, si quiere hacer algo cuando el cursor del ratón pase sobre cualquier párrafo del documento, en el DOM Level 0 tendría que recorrer todos los elementos p y asignarles la función manejadora; mientras que en el DOM Level 2, simplemente registra la función en el elemento document.body con addEventListener().

Tanto en DOM Level 0 como DOM Level 1, la función que se asigna al atributo intrínseco o que se pasa en el segundo parámetro de element.addEventListener() se convierte en una propiedad del elemento al cual se hizo la asignación o invocación. Es decir, esa función se convierte en un método del elemento. Cuando éste se invoca por el navegador, se hará como una invocación a un método normal del elemento, así la palabra reservada this hará referencia a dicho elemento, por lo que se puede usar dentro del código del manejador. El siguiente ejemplo muestra una mínima implementación de un juego de estallar una bolsa de burbujas. Nótese cómo la función popBubble es asignada a cada imagen de burbuja al evento onclick, lo que la convierte en un método, el cual utiliza el parámetro this para cambiar el origen de la imagen presionada por una burbuja estallada.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Estallar burbujas</title>
    <style type="text/css">
        body { margin: 2cm; }
        #wrap { width: 600px; height: 216px; margin: 0 auto; border: 5px solid #64859e;
            background: #a4c5de; position: relative; }
        #wrap img { position: absolute; border: 0; }
    </style>
    <script type="text/javascript" src="bubble_wrap_min.js"></script>
</head>

<body>
    <div id="wrap"></div>
</body>
</html>
// bubble_wrap_min.js
// Fills the game area with bubble images
function loadGame()
{
    // Get the game area
    var wrap = document.getElementById('wrap');

    // Assume the bubble image dimensions and area dimensions
    var imgWidth = 75;
    var imgHeight = 72;
    var rows = 3;
    var cols = 8;

    // Fill the game area creating img elements with onclick attribute set
    for ( var i = 0; i < rows; ++i )
    {
        for ( var j = 0; j < cols; ++j )
        {
            // Create the image as a new node
            var img = document.createElement('img');
            img.src = 'air.jpg';

            // Position the image, CSS must specify 'position:absolute' for img
            img.style.top = (i * imgHeight) + 'px';
            img.style.left = (j * imgWidth) + 'px';

            // When user clicks this image, replace it for a popped image
            img.onclick = popBubble;

            // Insert the new image as child of the game area
            wrap.appendChild(img);
        }
    }
}

// Called when a image is clicked
function popBubble()
{
    // Replace the current image for a popped one
    this.src = 'pop.jpg';

    // This image cannot be popped again
    this.onclick = null;

    // Play an auditive feedback
    var snd = new Audio('pop.mp3');
    snd.play();
}

// Initializes the game when the document is loaded
window.addEventListener('load', loadGame, false);
Un ejemplo del juego de estallar una bolsa de burbujas. Correr este ejemplo: versión minimalista, o juego completo.

La función que se envía en el segundo parámetro de element.addEventListener() recibe un objeto Event por parámetro. En el ejemplo del juego de la bolsa de burbujas, el método popBubble() ignora este parámetro. El objeto Event tiene varias propiedades útiles, entre las que sobresalen currentTarget que es una referencia al objeto que generó el evento (equivalente a this); el método stopPropagation() que al ser invocado evita que otros nodos sigan procesando el evento; y preventDefault() que cancela el efecto que tendría normalmente el evento en el navegador, como por ejemplo seguir un enlace si el elemento fuera un hipervínculo. En algunos eventos el navegador envía un objeto MouseEvent que contiene información sobre el puntero del ratón, como su posición (clientX, clientY), el botón usado (button), si hubo teclas modificadoras presionadas en el teclado y otros.

Ejercicio. Escriba un programa JavaScript en un archivo .js que al ser incluido en un documento, busca todas las tablas de la clase class="sortable" y convierte sus encabezados en elementos que reaccionan al clic del ratón. Cuando se presiona un encabezado de columna, se ordenan las filas alfabéticamente por los valores de esa columna.

Ejercicio. Modifique su solución del ejercicio anterior para que el ordenamiento ascendente y descendente se alternativo. Es decir, cuando se presiona el encabezado de una columna se ordena ascendentemente, pero si se vuelve a presionar el encabezado de dicha columna, se ordenan los datos de la tabla en forma descendente. Una tercera vez en orden ascendente, y así sucesivamente.

Ejercicio. Escriba un programa JavaScript en un archivo .js que al ser incluido en un documento, busca un elemento identificado con id="word_index" y crea en él un índice de materias, obtenido de las definiciones (elemento dfn) dispersas por el documento. Cada entrada en su índice de materias debe estar compuesto de enlaces, de tal forma que al hacer click en una de ellas, lleva a la definición en el documento. Puede tomar el libro del curso para sus pruebas.

El servidor web

Un servidor web es un programa que responde peticiones HTTP en algún puerto TCP, normalmente el 80. Existe en la actualidad varias implementaciones populares, entre las que sobresalen Apache HTTP Server, Microsoft Internet Information Services (IIS), y nginx. Este laboratorio pretende que el estudiante instale, configure y utilice uno de estos servidores para construir un pequeño sitio web dinámico utilizando FastCGI. En este texto se utilizará nginx.

Ejercicio. Repase la sección Arquitectura web del material de apoyo del curso, en especial los temas "El servidor web" y "El protocolo HTTP".

Instalación y ejecución del servidor web

La instalación de un servidor web es un proceso muy dependiente de la implementación escogida y de las necesidades del autor. En el caso de los servidores web libres, el método ideal es obtener el código fuente, instalarlar sus pre-requisitos, configurarlo, compilarlo, e instalarlo. Este procedimiento tipicamente se documenta con detalle en el sitio del servidor web. Por otra parte, tanto los servidores web libres como propietarios distribuyen binarios que son relativamente fáciles de instalar, pero los módulos que traen son los escogidos por quien generó el ejecutable.

Ejercicio. Instale nginx en su máquina local. Para propósitos de este laboratorio una versión binaria será suficiente. Si trabaja con Linux, utilice el administrador de paquetes de su distribución, por ejemplo, en Debian/Ubuntu bastará con el comando:

sudo apt-get install nginx

Si utiliza Windows, descargue una versión estable del sitio oficial de nginx. Descomprímala en cualquier lugar de su disco duro.

Una vez instalado el servidor web es necesario ponerlo en marcha. En el caso de nginx basta con invocar su ejecutable. El usuario notará que al hacerlo el programa termina inmediatamente dando la sensación de que nada ocurrió, sin embargo, nginx habrá iniciado al menos dos procesos homónimos en segundo plano (background), los cuales estarán esperando solicitudes HTTP en el puerto 80. Esto se puede probar con un navegador que acceda a la dirección http://localhost.

Ejercicio. Ponga en marcha su servidor web. En el caso de Windows corra con permisos de administración el ejecutable que descargó. Si no tiene permisos de administración, edite el archivo conf/nginx.conf y cambie la directiva listen 80 a un puerto superior a 1024, por ejemplo listen 8080; luego corra el ejecutable normalmente. En el caso de Linux puede ocurrir que su administrador de paquetes ya lo haya puesto en ejecución. De lo contrario invóquelo con permisos de administración:

sudo nginx

Si el ejecutable nginx no está en el $PATH, deberá localizar su ruta completa con el administrador de paquetes, por ejemplo en Debian se obtiene con dpkg -L nginx-full. Sin embargo, los administradores de paquetes asumen que el servidor debe iniciarse cada vez que arranca la computadora, por lo que estará en algún "init level", y como cualquier otro demonio se puede arrancar con:

sudo /etc/init.d/nginx start

Finalmente pruebe con un navegador que su servidor web responda en http://localhost.

Desde el punto de vista de la interacción con el usuario hay dos tipos de programas. Los programas en primer plano (foreground) cuando son ejecutados mantienen un diálogo con el usuario. La mayoría de personas creen que todos los programas son así. Sin embargo, en ese mismo instante el sistema operativo estará ejecutando un número, probablemente mayor, de programas en segundo plano (background) que no esperan una interacción directa con el usuario, como por ejemplo, el núcleo del sistema operativo, servicios de acceso a la red, demonios que ejecutan tareas programadas, servidores de correo, FTP, etc. El servidor web cabe en esta segunda categoría.

Cuando nginx es ejecutado arranca al menos dos procesos. El primero se llama nginx master process y debe ser ejecutado con permisos de administración, ya que por política de Unix, sólo procesos iniciados por el superusuario pueden abrir sockets en cualquier puerto, mientras que los iniciados por usuarios normales, sólo pueden escuchar en puertos superiores a 1024. El segundo y demás procesos reciben el nombre de nginx working processes, son procesos iniciados por nginx a nombre de un usuario cualquiera (especificado en la configuración de nginx) que atienden un cliente web en cualquier otro puerto, con el fin de liberar el puerto 80 para que esté disponible y poder aceptar más conexiones.

Si se invoca el ejecutable de nginx sin parámetros, tratará de iniciar el servidor web. Si se invoca por segunda vez sin parámetros, fallará ya que el puerto 80 estará en uso. Si se invoca con el parámetro nginx -h brindará ayuda. La opción nginx -V permite ver la versión y los módulos con que fue compilado el ejecutable. La opción nginx -s sirve para controlar al servidor en ejecución. Otras opciones se discutirán más adelante.

Ejercicio. Utilizando parámetros del ejecutable nginx, detenga la ejecución del servidor y pruebe su efecto en el navegador; determine la versión que tiene instalada en el sistema y los módulos que puede habilitar. Compare contra la lista de módulos disponibles si existe alguno que necesite o esté interesado(a). Si el módulo no está en la lista de instalados, deberá compilar nginx desde el código fuente.

Configuración del servidor web

Una vez instalado el servidor web, el autor querrá configurarlo para hacer funcionar su sitio. Tareas como indicarle al servidor web dónde están los documentos que conforman el sitio, si debe o no habilitar PHP, y cuánto es el número máximo de conexiones (visitantes) que debe permitir simultáneamente, son las que se establecen en la configuración.

La mayoría de servidores web se configuran con directivas en archivos de texto. Desdichadamente cada uno tiene sus propias convenciones, en lugar de existir un estándar. En el caso de nginx, el archivo principal de configuración se llama nginx.conf y su ubicación depende de los parámetros con que se compiló el código fuente, o de las convenciones de la distribución de Linux. Por ejemplo, en Ubuntu puede ubicarse bajo la carpeta /etc/nginx/. La nomenclatura de las directivas de configuración de nginx puede visualizarse con el siguiente pseudocódigo:

# Esto es un comentario
nombre_directiva valores;

# Un modulo se configura con un bloque de directivas
nombre_modulo
{
   directiva2 valor;
   directiva3 valor1 valor2;
   # ...
}

Las directivas constan de un término que significa alguna característica para nginx, seguido por uno o más valores. El término y los valores se separan por espacios en blanco. Como es de esperar, nginx está dividido en muchos módulos: core, webdav, fastcgi, mail, etc., y cada uno tiene directivas para configurarlos. Las directivas de cada módulo se escriben en el archivo de configuración agrupadas dentro de llaves { }, lo que forma un bloque de directivas (directive block), y son precedidos por el nombre del módulo. Esta práctica mantiene el orden e incrementa la legibilidad. El siguiente listado muestra un fragmento de un archivo de configuración de nginx.

user www-data;
worker_processes 4;
pid /var/run/nginx.pid;

events
{
   worker_connections 768;
   # multi_accept on;
}

http
{
   # Basic settings
   include /etc/nginx/mime.types;
   default_type application/octet-stream;

   # Logs
   access_log /var/log/nginx/access.log;
   error_log "/var/log/nginx/error.log";

   # Virtual host configs
   include /etc/nginx/conf.d/*.conf;
   include /etc/nginx/sites-enabled/*;
}

Del listado anterior se puede leer lo siguiente. La directiva en la línea 7 sólo afecta al módulo de eventos (Events Module) de nginx. Las directivas que no están dentro de llaves (líneas 1 a 3) se dice que están en el bloque principal (main block) y tienen un efecto global en el servidor web. Algunas directivas esperan valores numéricos como worker_processes en la línea 2, otras cadenas de caracteres como access_log y error_log en las líneas 18 y 19. Las cadenas se pueden escribir literalmente, pero si tienen espacios en blanco, puntos y comas, u otros caracteres especiales, deberán encerrarse entre comillas dobles o simples.

Los módulos de nginx son opcionales, es decir, se pueden instalar o no, y si están instalados se pueden habilitar o no; a excepción de tres módulos: Core module, Events module y Configuration module, los cuales siempre están instalados y siempre habilitados, ya que proveen la funcionalidad mínima de nginx.

La directiva include

Una instalación de un servidor web puede utilizarse para uno o más sitios web. No es aconsejable que la configuración de todos esos sitios se haga en el mismo archivo, puesto que se hará voluminoso y difícil de mantener. En su lugar, se recomienda distribuir la configuración en varios archivos que contengan directivas coherentes, al menos un archivo por cada sitio web que deba servir la instalación de nginx.

La directiva include recibe un nombre de un archivo (el cual debe existir) y le indica a nginx que debe insertar el contenido de dicho archivo en el lugar de la directiva include. La línea 14 del listado anterior muestra un ejemplo. El nombre del archivo puede ser un patrón con los caracteres comodines * y ? (filename globbing), que indica a nginx incluir, no un archivo, sino todos aquellos que cumplan el patrón. Por ejemplo, la línea 22 del listado anterior indica a nginx importar en ese lugar todos los archivos que terminan en *.conf que se encuentran en la carpeta /etc/nginx/conf.d/.

El módulo HTTP Core

El módulo HTTP de nginx es opcional, por lo que puede deshabilitarse, sin embargo, es quizá el más requerido por el autor y el que querrá configurar primero para hacer funcionar su sitio web. Es también el módulo más extenso de nginx y su configuración se hace con tres bloques: http, server y location. Tómese el siguiente ejemplo:

http
{
    gzip on;

    server
    {
        server_name miempresa.com www.miempresa.com;
        listen 80;
        root /home/sites/miempresa.com;

        location /download/
        {
            gzip off;
        }
    }

    server
    {
        server_name portal.miempresa.com;
        root /home/sites/portal.miempresa.com;
        index index.php index.html;
    }

    server
    {
        server_name svn.miempresa.com;
        root /home/svn;
    }
}

Las directivas que se escriban directamente en el bloque http { ... } afectan a todo el módulo HTTP, como la línea 3 del listado anterior, que indica a nginx comprimir los recursos antes de enviarlos al cliente.

Servidores virtuales

Un servidor web puede alojar uno o varios sitios web, los cuales se acceden con diferentes dominios. Por ejemplo, una misma instalación de nginx podría servir la página pública de la empresa (www.miempresa.com), un sitio para empleados, clientes y proveedores de la empresa (portal.empresa.com), y un sitio con repositorios de Subversion (svn.miempresa.com). Es deseable poder configurar cada uno de esos sitios independiente de los demás. Por ejemplo, cada uno debe tener un diferente directorio físico donde se encuentran sus recursos, para los dos primeros podría ser importante tener PHP habilitado, mientras que para el segundo el módulo WebDAV, y así por el estilo.

nginx, y en general todos los servidores web, representan cada sitio web como un servidor virtual, es decir, como un pequeño servidor web que corre bajo el auspicio del servidor web real, con su propia configuración independiente de los demás servidores virtuales. Si sólo se quiere montar un único sitio web en un servidor web, lo normal es crear un servidor virtual para dicho sitio, con el bloque de directivas server { ... } dentro del bloque http { ... }.

El listado anterior define tres servidores virtuales y por ende tres sitios web. La directiva listen le indica a nginx en cuál puerto TCP, o dirección IP, espera conexiones para ese sitio web, de esta forma se puede tener dos sitios web con igual dominio pero diferenciados por el puerto. Si se omite, se asume el puerto 80.

La directiva server_name hace que el servidor virtual responda a uno o más nombres de dominio. Cuando nginx recibe un mensaje de solicitud, revisa el atributo Host: en el encabezado HTTP, y compara su valor contra todos los servidores virtuales para determinar cuál debe responder. En nuestro ejemplo, si nginx recibe el siguiente mensaje HTTP (recuerde que los cambios de línea son importantes):

GET /login.php?remember_user=yes
Host: portal.miempresa.com:80
User-Agent: Opera/11.01 (iOS)


no podrá utilizar el puerto o la dirección IP como criterio para seleccionar el servidor virtual, pero sí el dominio. En este caso, sólo el segundo servidor virtual atiende el dominio portal.miempresa.com en el puerto 80, por lo que nginx cargará el recurso /home/sites/portal.miempresa.com/login.php?remember_user=yes y lo retornará en un mensaje de respuesta HTTP. La directiva server_name acepta comodines, por lo que un servidor virtual podría antender todos los dominios que terminen en o tengan una cadena particular, como:

server_name *.miempresa.com; # or
server_name *miempresa.*; # or
server_name *miempresa*;

La directiva root le indica a nginx dónde se encuentran físicamente los recursos que deben ser servidos a través de un servidor virtual. Es seguido por una ruta, normalmente absoluta, de lo contrario se tomará como relativa al directorio donde está instalado nginx.

La directiva index le indica a nginx cuál recurso o archivo escoger para retornar al cliente cuando se solicita un directorio. Si se especifican varios archivos, se retornará el primero que se encuentre. A este recurso se le suele llamar index page. Por ejemplo, si el servidor web recibe la siguiente solicitud HTTP

GET /admin/
Host: portal.miempresa.com:80
User-Agent: Opera/11.01 (iOS)


nginx intentará encontrar el archivo /home/sites/portal.miempresa.com/admin/index.php, si no lo encuentra, intentará con /home/sites/portal.miempresa.com/admin/index.html, y si tampoco existe, responderá con un código de estado de error, posiblemente 404 NOT FOUND. Si en un servidor virtual no se especifica la directiva index, se asumirá index.html.

El bloque location

A veces dentro de un servidor virtual, es deseable aplicar una configuración diferente a ciertos recursos del sitio. Los bloques location pattern { ... } permiten escoger un subconjunto de recursos que coincidan con el patrón pattern y aplicarles la configuración que aparece dentro de llaves. El patrón es sensitivo a mayúsculas/minúsculas si el sistema de archivos del sistema operativo lo es también.

MIME types

Cuando el cliente solicita un recurso, el servidor web lo localiza o genera, y debe retornarlo en un mensaje de respuesta HTTP, en el cual debe indicarle al cliente de qué tipo de datos es dicho recurso para que lo pueda interpretar adecuadamente, utilizando una notación estándar llamada extensiones multipropósito de correo de internet (MIME, Multipurpose Internet Mail Extensions), ideadas para el intercambio de archivos por Internet. Por ejemplo, text/html indica un documento HTML, image/png una imagen codificada con el formato PNG, y application/xhtml+xml un documento XHTML.

Póngase en los zapatos del servidor web. Dado un archivo que debe retornar al cliente ¿cómo sabe cuál tipo MIME asignarle en el mensaje de respuesta? nginx se guía por la extensión del archivo, y si no la tiene, asume un tipo MIME por defecto. nginx le permite configurar las asociaciones entre extensiones y los tipos MIME utilizando el bloque types { ... }, como en el siguiente ejemplo:

http
{
    types
    {
        text/html                    html htm shtml;
        text/css                     css;
        text/xml                     xml rss;
        application/xhtml+xml        xhtml;
        application/x-javascript     js;
        image/gif                    gif;
        image/jpeg                   jpeg jpg;
        # ...
    }

    default_type application/octet-stream;
}

La directiva default_type le indica a nginx cuál MIME type asumir cuando la extensión no se encuentra en el bloque types { ... } o cuando el recurso simplemente no tiene extensión.

Ejercicio. Compruebe el efecto de servir un documento XHTML con el MIME adecuado (application/xhtml+xml). Escriba en un documento web con extensión .xthml que tenga alguna diferencia con HTML, por ejemplo, una sección CDATA cuyo contenido es código (X)HTML:

<body>
    <h1>MIME application/xhtml+xml</h1>
    <p>Si este <strong>documento XHTML</strong> es servido con el MIME
    <code>application/xhtml+xml</code>, abajo se debe ver <em>el código
    fuente</em> que generó este párrafo y no un texto con formato.</p>

    <pre><![CDATA[
        <p>Si este <strong>documento XHTML</strong> es servido con el MIME
        <code>application/xhtml+xml</code>, abajo se debe ver <em>el código
        fuente</em> que generó este párrafo y no un texto con formato.</p>
    ]]></pre>
</body>
Una sección CDATA no debe interpretarse como código (X)HTML cuando el documento es servido como application/xhtml+xml. Correr este ejemplo.

Ejercicio. Monte su sitio web personal (creado en el proyecto 01) en su instalación local de nginx. Cree un servidor virtual o utilice el que nginx trae por defecto. Ya que su sitio web se compone únicamente de documentos XHTML pero con extensión .html, registre el MIME type application/xhtml+xml a la extensión html en su servidor virtual.

Server Side Includes module

nginx -y en general todos los servidores web- pueden extender su funcionalidad con módulos, escritos por el equipo oficial, terceros, o incluso usted. En esta y siguientes secciones se explorarán algunos de ellos.

El módulo Server Side Includes (SSI) le indica al servidor web que antes de servir un recurso al cliente, analice (parse) dicho recurso en búsqueda de comandos para el servidor web, los cuales tienen la notación:

<!--# command parameter1="value1" parameter2="value2" ... parameterN="valueN" -->

Por ejemplo, al servir el siguiente documento

<html>
    <head>
        <title>Mi empresa</title>
    </head>
    <body>
        <div id="whole">
            <!--# include file="header.html" -->
            <!--# include file="menu.html" -->
            <div id="content">
                <p>Bienvenidos al ...</p>
            </div>
            <!--# include file="footer.html" -->
        </div>
    </body>
</html>

el servidor web localizará todos los comentarios que inicien con <--# y tratará de ejecutar el comando que sigue. El comando include le indica al servidor cargar el archivo que se encuentra en el atributo file="url", o si es un programa, ejecutarlo. El contenido del archivo o el resultado del programa será insertado en el documento servido, reemplazando el comando dentro del comentario.

El autor que quiera utilizar Server Side Includes, deberá habilitar el módulo ssi en la configuración de nginx, e indicarle en cuáles tipos de recursos (MIME types) quiere que el servidor procese esos comandos, de lo contrario se asumirán sólo los text/html. Por ejemplo:

server
{
    server_name miempresa.com www.miempresa.com;
    root /home/sites/miempresa.com;

    ssi on;
    ssi_types text/html application/xhtml+xml application/x-javascript;
}

Aunque SSI es muy eficiente y se puede habilitar para todo un sitio web, es mejor evitarlo para archivos que no lo requieren. Una práctica común es nombrar los archivos que tienen Server Side Includes con la extensión .shtml (contracción de server html), y habilitar el módulo ssi sólo para ellos, por ejemplo:

server
{
    server_name miempresa.com www.miempresa.com;
    root /home/sites/miempresa.com;
    index index.shtml index.php index.html;

    # Habilita Server Side Includes en todos los archivos con extensión .shtml
    location ~* \.shtml$
    {
        ssi on;
    }
}

Ejercicio. Habilite el módulo SSI en su instalación de nginx; y cambie todos los elementos object de su sitio web personal por comandos include de SSI. Modifique los documentos web referidos en esos comandos para quitar el encabezado (head), cuerpo (body) y otros elementos ahora innecesarios.

CGI y FastCGI

La publicación de páginas web estáticas se está convirtiendo en una práctica en desuso. La mayoría de sitios web pretenden hacer tan efectiva la comunicación con el lector para que éste se sienta a gusto y ambas partes se beneficien. En este movimiento no basta sólo con programación del lado del cliente con JavaScript, el servidor web también debe ajustarse a las necesidades del visitante y hacer que las páginas que sirva sean dinámicas. Es decir, la fuente de los recursos con los que responde el servidor web no son archivos estáticos, sino el resultado de ejecutar programas de aplicación escritos en cualquier lenguaje de programación. Es evidente entonces que debe existir un conjunto de convenciones que permitan a una aplicación cualquiera -ocupada de la lógica del negocio- comunicarse con un servidor web cualquiera -ocupado de los asuntos de red-, para que así ambos colaboren y respondan al visitante adecuadamente. Este conjunto de convenciones las establece el estándar Common Gateway Interface (CGI) surgido en 1993.

Cuando el usuario solicita un recurso, el servidor web sabe -de acuerdo a su configuración- si el recurso es estático o dinámico. Si es estático lo carga desde la memoria secundaria y retorna una copia al cliente. Si es dinámico, deberá interactuar con una aplicación y es ahí donde entra en juego CGI. CGI es protocolo que impone la forma en que la información se transfiere entre el servidor web y una aplicación cualquiera (llamada gateway application) y se resume en lo siguiente. El servidor web ejecuta el programa de aplicación pasándole por parámetro o en la línea de entrada un conjunto de variables. La aplicación se carga, realiza su lógica del dominio en función de esas variables, consultando bases de datos o cualquier otro recurso, construye una página web, la imprime en la salida estándar y termina su ejecución. El servidor web captura esa salida y la envía al cliente que solicitó el recurso.

El procotolo CGI tiene varios inconvenientes, en especial para sitios web mundialmente concurridos. Por esto a mediados de los 90 se desarrolló el estándar FastCGI, el cual introduce las siguientes diferencias sobre CGI:

  1. CGI ejecuta un proceso de la aplicación por cada solicitud, lo cual puede agotar la memoria del servidor rápidamente, consume procesador iniciando y cerrando procesos, y además los procesos deben implementar complejos mecanismos de intercomunicación si necesitan trabajar en conjunto. FastCGI sólo arranca un proceso de la aplicación al cual le "pasa" todas las solicitudes de uno o más visitantes. La ejecución de la aplicación no es detenida, por lo que se le llama proceso persistente.
  2. En CGI la aplicación y el servidor web deben correr en la misma máquina física, lo cual puede ser indeseable, en especial si el proceso es pesado y se disponen de más computadoras en la organización. FastCGI impone que el servidor web y la aplicación deben comunicarse a través de TCP/IP, así ambos pueden ejecutarse en computadoras distintas, incluso, en redes distantes y con sistemas operativos diversos.

De lo anterior se infiere que la aplicación, indeferentemente del lenguaje de programación en que esté hecha (C, C++, Java, PHP, Ruby, etc.), debe implementar un servidor TCP/IP esperando conexiones en algún puerto, configurar nginx para que cuando se solicita un recurso con una extensión particular o una ruta particular, se contacte la aplicación en dicho puerto TCP. En lo siguiente este proceso se explicará para PHP.

PHP a través de FastCGI

Los programas para PHP no son ejecutables, es decir, no son compilados. Un archivo de texto con extensión .php es interpretado por el ejecutable de PHP (/usr/bin/php ó php.exe), de la siguiente forma:

php /path/to/myfile.php

Lo anterior claramente no es un servidor TCP/IP esperando conexiones en algún puerto, como exige el protocolo FastCGI. El proyecto PHP-FPM (PHP FastCGI Process Manager) es una alteración de PHP para Unix que ejecuta un demonio normalmente en el puerto 9000, el cual espera solicitudes de ejecutar un archivo .php particular con sus respectivos parámetros. nginx puede comunicarse con PHP-FPM a través de FastCGI, lo cual se considera una implementación muy eficiente, ideada para sitios altamente concurridos.

Para instalar PHP-FPM se puede configurar y compilar su código fuente, o utilizar el administrador de paquetes de su distribución de Linux. Por ejemplo, los siguientes comandos instalan y ponen en ejecución un servidor de PHP-FPM en Debian/Ubuntu:

sudo apt-get install php5-fpm
sudo /etc/init.d/php5-fpm start

Ahora debe decirle a nginx que cada vez que un visitante solicita un archivo .php, debe contactar al servidor de PHP a través de FastCGI en el puerto 9000, para que lo ejecute, y el resultado que se genere de su invocación, enviarlo al visitante. Para nuestros efectos, el archivo .php puede estar en cualquier lugar del sitio web, su único distintivo con los demás archivos del sitio es su extensión .php. La siguiente configuración mínima hace este trabajo. Nota: es probable que las siguientes líneas ya estén presentes en la configuración por defecto, basta con quitarles los comentarios.

server
{
    server_name miempresa.com www.miempresa.com;
    root /home/sites/miempresa.com;
    index index.php index.html;

    # Ejecuta todos los archivos con extensión .php antes de enviarlos al cliente
    location ~* \.php$
    {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_index index.php;
    }
}

El bloque location anterior le indica a nginx que antes de servir un archivo con extensión .php, debe contactar a través de FastCGI al programa que está corriendo en el puerto 9000, e indicarle que ejecute el archivo .php. Esto último se indica a través de parámetros con la directiva fastcgi_param NOMBRE_PARAMETRO valor_parametro. Por ejemplo, la directiva en la línea 11 le indica a nginx enviar a PHP-FPM la ruta absoluta del archivo .php en el parámetro SCRIPT_FILENAME. Los valores iniciados con un símbolo de dólar, como $document_root son variables de nginx, las cuales se reemplazan por sus valores respectivos, en el caso de $document_root por el directorio donde están los recursos del sitio web (/home/sites/miempresa.com en este ejemplo), y $fastcgi_script_name por la ruta relativa dentro del sitio del archivo .php solicitado por el visitante. Otros parámetros son necesarios, el archivo fastcgi_params que trae la instalación de nginx se encarga de ellos y funciona para la mayoría de sitios.

Para Microsoft Windows no existe una implementación de PHP-FPM, por lo que el desarrollador puede emularlo a través de Cygwin, o utilizar algún paquete "todo en uno", como Easy WEMP (acrónimo de Windows, nginx, MySQL y PHP).

Ejercicio. Pruebe que su instalación de nginx-PHP funcione. Cree un archivo info.php con el siguiente contenido en algún lugar de su sitio web:

<?php
    phpinfo();
?>

Solicite el recurso anterior a través de un navegador, por ejemplo, dirigiéndolo a http://localhost/info.php. Si la instalación fue exitosa, verá una extensa página con detalles sobre su configuración de PHP.

Programación del servidor web: PHP

Un autor que quiera construir un sitio web dinámico deberá instalar un servidor web (como nginx), configurarlo con FastCGI, escribir un programa (en C, C++, Java, ...) que sea un servidor FastCGI y además se ocupe de la lógica del sitio. Esta tarea puede ser monumental, incluso para un sitio web sencillo. Este fue el sentimiento que tuvo Rasmus Lerdorf en 1994 cuando quiso publicar una página web personal con un mínimo grado de dinamismo, como el conteo de visitantes. Lerdorf encontró que sería más natural insertar pequeños trozos de código (scripts) dentro de sus páginas web estáticas. Cada vez que un visitante solicitara una de estas páginas, un intérprete ejecutaría estos scripts antes de responderle al cliente. Lerdorf llamó "Personal Home Page tools" a su intérprete y lo dispuso como software libre, el cual evolucionaría a ser el lenguaje de "scripting" más usado en servidores web del mundo, bajo el nombre recursivo de PHP: Hypertext Processor (PHP).

El lenguaje de programación PHP

Aunque se pueden escribir programas PHP independientes en línea de comandos o con interfaz gráfica, su uso más difundido ha sido para crear páginas web dinámicas, es decir, aquellas cuyo contenido, apariencia o comportamiento puede cambiar cada vez que un navegador las solicita. Así PHP está diseñado para mezclarse con el contenido de las páginas web. El php_hello_world muestra un posible "hola mundo" con PHP.

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Hola mundo PHP</title>
</head>

<body>
    <h1>Hola mundo en PHP</h1>
    <?php
        print("<p>Este párrafo proviene de PHP, no del documento XHTML</p>\n");
    ?>
</body>
</html>
Hola mundo en PHP. Correr este ejemplo.

Al correr el ejemplo del php_hello_world ocurre lo siguiente. El servidor web recibe un mensaje solicitando el recurso /path/to/hello_world.php, el cual es un simple archivo de texto. En condiciones normales, el servidor web podría retonarle el archivo al navegador, el cual no sabría qué hacer con él y le preguntaría al usuario un lugar para guardarlo. Sin embargo, el servidor web está configurado para tratar de forma especial a todos los archivos que terminen con extensión .php. Esta configuración indica al servidor web que debe ejecutar los scripts que se encuentran en el archivo .php antes de enviarlo al navegador.

La configuración del servidor web también le indica cómo encontrar el intérprete de PHP. Así el servidor invoca al intérprete pasándole el archivo /path/to/hello_world.php. El intérprete carga el archivo y lo recorre (parse) buscando scripts de PHP, los cuales se encuentran únicamente dentro de la etiqueta especial <?php ... ?>. El intérprete ejecuta este código y reemplaza la etiqueta especial por la salida que generó su ejecución. Una vez que ha terminado de ejecutar todos los scripts en el archivo, el resultado regresa al servidor web quien lo despacha al navegador.

El navegador sólo recibe el resultado de ejecutar los scripts, no el código fuente PHP que generó el resultado. El lector puede comprobarlo corriendo el ejemplo del php_hello_world y revisando en su navegador el código fuente de la página.

Durante muchos años se utilizó un short tag <? ... ?> en lugar de <?php ... ?> para escribir el código PHP. El uso del "short tag" debe evitarse a toda costa, ya que genera conflicto con las instrucciones de procesamiento de XML <?xml ... ?>. De hecho, si se quiere servir documentos XHTML con scripts de PHP, se debe deshabilitar la directiva short_open_tag en la configuración del intérprete de PHP (en el archivo php.ini). El php_web_server_date muestra la fecha y hora del servidor web.

<body>
    <h1>Hora y fecha del servidor</h1>
    <p><?php
        $fecha = date('d \d\e F \d\e Y');
        $hora = date('h:i:s a');
        print("Hoy es " . $fecha . ". Son las " . $hora );
    ?></p>
</body>
La hora del servidor en PHP. Correr este ejemplo.

El php_web_server_date muestra otras características de PHP. En el ejemplo del hola mundo (php_hello_world) el párrafo se generó dentro del script de PHP (línea 13), mientras que en el ejemplo de la hora (php_web_server_date) el script se escribió dentro de un elemento p. Esto muestra que el autor puede intercalar código PHP y (X)HTML a conveniencia. Algunos programadores prefieren abrir una única etiqueta <?php ... ?> y escribir mucho código PHP, otros prefieren escribir código (X)HTML y abrir etiquetas <?php ... ?> sólo para el mínimo código PHP necesario. El segundo método es quizá ligeramente más eficiente y es la convención que se tomará en este texto.

Ejercicio. Reescriba el ejemplo de la hora del servidor (php_web_server_date) utilizando sólo código PHP, es decir, su archivo tendrá sólo una etiqueta real: <?php ... ?>, y las etiquetas XHTML se generan a partir de strings en PHP. La salida de su script debe ser un documento XHTML válido. Nota 1: en los ejemplos de este material se presenta sólo fragmentos de código por razones de legibilidad; si el lector quiere reproducirlos debe agregar el marcado faltante, como la declaración de tipo de documento, el elemento html, y el encabezado del documento web. Nota 2: Las funciones estándar de PHP utilizadas en los listados de este material pueden tener enlaces hacia su documentación oficial.

Las líneas 4 y 5 del php_web_server_date invocan una función estándar de PHP: date(), la cual retorna un string con la fecha u hora en el formato que se le indique por parámetro. PHP tiene una rica biblioteca de funciones cuya documentación es quizá una de las mejores que existe en el mundo.

El string resultado de invocar a date() en la línea 4 del php_web_server_date se almacena en la variable $fecha. PHP exige anteponer el símbolo de dólar ($) a un identificador para distinguirlo como una variable, de la misma forma que ocurre en Perl y en Unix shell scripting, con el objetivo de hacer más eficiente el reconocimiento de las variables a la hora de ejecutar el código. Al igual que en JavaScript, las variables pueden almacenar valores de cualquier tipo de datos, incluso cambiar en el tiempo. Una variable se crea simplemente asignándole un valor.

Ejercicio. Escriba un programa PHP que imprima la cantidad de días que faltan para navidad (25 de diciembre) del año en curso. Si la navidad ya pasó, imprime la cantidad de días que faltan para la navidad del próximo año. Busque en la documentación de PHP una función que le pueda ayudar a construir fechas a partir de sus valores separados (hora, mes, día, etc.). Sugerencia: puede también estudiar los ejemplos provistos en la documentación de la función date().

La línea 6 del php_web_server_date imprime un texto resultado de concatenar unos strings literales con las variables $fecha y $hora utilizando el operador punto (.). Esto crea la primera diferencia con JavaScript, que utiliza el operador de suma (+) para la concatenación. El ejemplo anterior pudo haberse escrito de otra forma muy común:

<body>
    <h1>Hora y fecha del servidor (2)</h1>
    <p><?php
        $fecha = date('d \d\e F \d\e Y');
        $hora = date('h:i:s a');
        print("Hoy es $fecha. Son las $hora");
    ?></p>
</body>
La hora del servidor con interpolación de variables. Correr este ejemplo.

El ejemplo del php_variable_interpolation sólo cambia la línea 6 del php_web_server_date. En la nueva versión se insertaron las variables directamente en el string. A esto se le llama interpolación de variables o sustitución de variables y el intérprete de PHP lo hace únicamente en las cadenas con comillas dobles (como en "$var") y no en cadenas con comillas simples (como en '$var'). Lo mismo aplica para las secuencias de escape como "\n".

Ejercicio. Pruebe el efecto de cambiar las comillas dobles por simples en el ejemplo del php_variable_interpolation.

El desarrollador web debe tener clara esta diferencia, ya que JavaScript trata de forma idéntica a las cadenas literales con comillas dobles o simples, mientras que PHP hace interpolación de variables y secuencias de escape en una de ellas y no en la otra. El lector podría estar pensando en utilizar siempre comillas dobles para evitar sorpresas, sin embargo, debe tener en cuenta que el procesamiento de las cadenas literales con comillas simples es más eficiente que con comillas dobles.

Ambientes de producción y desarrollo

Para escribir un programa en PHP el desarrollador web sigue normalmente la siguiente rutina. Crea un archivo de texto con extensión .php donde escribe su código dentro de etiquetas <?php ... ?>. Lo guarda en alguna carpeta que forma parte de su sitio web. Accede al archivo .php a través de un navegador escribiendo su URL. El servidor invoca al intérprete de PHP y envía el resultado al navegador. El desarrollador puede entonces ver la salida de su programa.

Dependiendo de la configuración de PHP, esta rutina se vuelve en contra del desarrollador o de la empresa cuando el programa PHP tiene errores. Si la configuración dicta que el intérprete de PHP debe informar los errores en la salida estándar, éstos aparecerán en el navegador, lo cual ayudará enormemente al desarrollador a depurar sus programas, pero podría hacer visible información sensible de la empresa a los visitantes, o confundirlos arruinando el contenido y diseño del sitio web.

Si la configuración dicta que el intérprete de PHP debe callar los errores (o a lo sumo enviarlos a una bitácora), el desarrollador difícilmente detectará que los hay, a menos de que el intérprete se niegue a ejecutar el código PHP con errores y no regrese del todo una respuesta al servidor web, el cual optará por enviar un mensaje de respuesta con un código de estado 500 Internal server error al navegador. Esto es mucho más eficiente y evita que el servidor web exponga información sensible a los visitantes, aunque estos se molestarán en todo caso.

La solución aconsejada es tener dos ambientes: uno de producción y otro de desarrollo. El ambiente de producción aloja el sitio web que atiende a los visitantes, se considera estable y los errores en el código PHP no son reportados al navegador. El ambiente de desarrollo es de uso exclusivo de los desarrolladores, en él los errores y advertencias son reportadas al navegador. Estas diferencias se configuran en la sección Error handling and logging del archivo php.ini.

Los ambientes de producción y desarrollo podrían estar en computadoras diferentes, o en la misma bajo servidores virtuales distintos. Es importante que el sitio web completo se encuentre administrado por algún software de control de versiones, como Subversion o Git, lo cual además facilita el mantenimiento del código entre estos ambientes.

Ejercicio. Escriba un archivo .php que tenga uno o más errores de sintaxis, como omitir un punto y coma, o escribir un trozo de texto literal sin comillas. Pruebe qué ocurre al ejecutar su programa en dos ambientes diferentes: uno local y otro en un servicio de alojamiento web gratuito con soporte para PHP. Determine el tipo de manejo de errores que hace cada cuál. Configure el ambiente local para que reporte al navegador errores y advertencias (warnings), si no lo está ya.

Antes de publicar código PHP en el sitio web o en el repositorio de control de versiones, el desarrollador siempre debería chequear sus programas con el intérprete de PHP, lo cual puede hacerse en la línea de comandos de la siguiente forma:

php /path/to/file.php

El desarrollador tampoco podrá verificar un documento .php contra los validadores del Consorcio Web (como Unicorn), pero sí su salida. Es decir, que el desarrollador deberá hacer dos verificaciones, la primera es llamar al intérprete de PHP en línea de comandos para verificar que su código PHP sea correcto, y luego la salida que su programa genera debe verificarla contra los estándares del Consorcio Web. Una tercera verificación podría deberse si se tiene código JavaScript, inspeccionando la bitácora de errores del navegador.

Generalidades del lenguaje PHP

La sintaxis de PHP se basa en C y Perl; y tiene mucha familiaridad con C++ y Java. Esto implica que los comentarios, condicionales, ciclos y operadores son similares. Este material asume que el lector conoce C y JavaScript, y se concentra en los aspectos que hacen a PHP distinto a estos lenguajes. Los comentarios en PHP se pueden escribir en cualquiera de las siguientes tres formas:

// Este es un comentario de una línea con estilo BCPL

# Este es un comentario de una línea con estilo Perl

/* Este comentario
puede abarcar varias
líneas pero no anidarse */

Al igual que JavaScript, PHP diferencia mayúsculas y minúsculas (case sensitive), y la aritmética se hace en punto flotante, es decir, 5 / 2 genera 2.5 y no 2 como ocure en C. A diferencia de JavaScript, PHP sí permite que un string se expanda varias líneas, y en tal caso, los espacios en blanco y cambios de línea pasan a formar parte natural del string, por ejemplo:

$table = /* ... */;
$condition = /* ... */;

$query = "
    SELECT *
    FROM $table
    WHERE $condition
";

equivale a haber escrito el siguiente código en una sola línea, pero que resulta más ilegible:

$query = "\n\tSELECT *\n\tFROM $table\n\tWHERE $condition\n";

Al igual que JavaScript, las variables pueden almacenar valores de diferentes tipos de datos a lo largo de la ejecución del script, y las conversiones de datos se hacen automáticamente de acuerdo al contexto. En PHP el operador de suma (+) se utiliza sólo para valores numéricos y no hace concatenación, lo cual es responsabilidad del operador punto (.). Por ejemplo:

<p>
<?php
    # Una variable que almacena un valor numérico
    $telefono = 50628241202;

    # Obtiene el cuarto digito (en la posicion 3) conviertiendo el número en un string
    # la conversion la hace PHP automaticamente al usar algo en un contexto string
    $tipo_tel = substr($telefono, 3, 1);

    # Comparación de un string con un número: el string se convierte a número
    if ( $tipo_tel != 8 )
        echo "No se puede enviar mensajes de texto al teléfono $telefono.";
?>
</p>
Conversión automática de tipos de datos. Correr este ejemplo.

El lector habrá notado que en algunos ejemplos se ha utilizado print y en otros echo para generar la salida que recibirá el navegador. No son funciones realmente, son constructos del lenguaje y por ende pueden utilizarse con o sin paréntesis. La diferencia radica en que print recibe únicamente un parámetro de tipo string y retorna siempre el entero 1; mientras que echo no tiene un valor de retorno y puede recibir un número arbitrario de parámetros de cualquier tipo separados por comas, los cuales tratará de convertir a string automáticamente. En general, print() es un constructo del lenguaje utilizado para simular una función en un contexto donde se requiere una, mientras que echo es un constructo del lenguaje que no puede utilizarse en el contexto de una función y debe obligatoriamente usarse sin paréntesis si recibe dos o más parámetros. Exceptuando esas extrañas restricciones, en la mayoría de contextos puede utilizarse cualquiera de los dos, y la escogencia la hace el programador de acuerdo a sus gustos personales.

Ejercicio. Escriba un programa PHP que reporte estadísticas sobre el uso de la memoria secundaria en la carpeta donde está alojado su archivo .php (la cual se puede obtener con la expresión dirname(__FILE__)). Imprima en una tabla (X)HTML como la siguiente.

Rubro Tamaño Porcentaje
Espacio utilizado 22.98 GiB 98.63%
Espacio libre 322 MiB 1.37%
Espacio total 23.00 GB 100.00%

Estudie las funciones disk_free_space() y disk_total_space(). Note que la cantidad de bytes debe imprimirse en unidades amigables (kiB, MiB, GiB, etc.) [Puede reutilizar código provisto en los ejemplos de la documentación]. Imprima en rojo el espacio libre si es menor al 5% para alertar al usuario de que sus programas PHP, y por ende, su sitio web, podrían fallar por falta de espacio en disco.

Tipos de datos

Los tipos de datos que PHP soporta son: booleanos, números (enteros y flotantes), strings, arreglos, objetos, recursos (resource) y el valor especial NULL. Son prácticamente los mismos tipos de datos de JavaScript con algunas diferencias. Una variable no tiene tipo de datos, sino que puede almacenar valores de cualquiera de esos tipos de datos. La función gettype($var) retorna un string que describe el tipo de datos del valor almacenado en la variable $var.

Las variables booleanas (boolean) sólo admiten los valores true y false, los cuales se pueden escribir en cualquier combinación de mayúsculas y minúsculas (case-insensitive).

Internamente PHP almacena los números utilizando enteros (int) siempre que sea posible, y cuando el número es muy grande o tiene decimales, se representa internamente como un número flotante (double). El programador no necesita preocuparse por estas diferencias. Sin embargo, ambas representaciones tienen limitaciones en cuanto a la capacidad y precisión, inadecuadas cuando se tiene que hacer cálculos precisos o manejo de dinero. En tales circunstancias el programador puede recurrir a alguna extensión matemática de PHP.

Los strings en PHP no son objetos, por tanto, no tienen propiedades ni métodos como length(); sino que deben ser manipulados en forma similar a C: a través de funciones como strlen(str) y substr(str, start, length). Sin embargo hay una diferencia importante, en C los strings son punteros mientras que en PHP son valores y por tanto, los operadores de comparación (==, ===, <, <=, >, >=) y concatenación (.) trabajan de forma esperada.

En PHP todos los arreglos son realmente arreglos asociativos, también conocidos como mapas (maps), tablas hash o diccionarios; porque almacenan parejas llave => valor. Si las llaves no se especifican, se asumen valores enteros secuenciales. Los arreglos se crean con el constructo array(v1, v2, ..., vN) que recibe una lista de valores separados por comas, e inserta esos valores en orden asociándolos con llaves enteras correspondientes a la posición del elemento. Las llaves también pueden ser strings, y en tal caso se especifican usando la notación key => value. Un arreglo que almacene varias de estas parejas se crea utilizando la notación array(k1 => v1, k2 => v2, ..., kN => vN), como se aprecia en el php_arrays_intro. Al igual que los string, los arreglos no son objetos, por lo que su manipulación se hace a través de funciones como count($arr) y sort($arr). Este tema se estudiará con más detalle adelante.

<h1>Arreglos</h1>
<pre><?php
    // Crear un arreglo secuencial
    $days = array('domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sabado');

    // Recorrer el arreglo
    echo "\$days == \n";
    for ( $i = 0; $i < count($days); ++$i )
        echo '   ', $i, ': ', $days[$i], "\n";

    // Agregar un elemento al arreglo
    echo "\n\$days[] = 'desconocido'\n";
    $days[] = 'desconocido';

    // Estudiar el arreglo
    echo "\n\$days == "; var_dump($days);
?></pre>

<h1>Arreglos asociativos</h1>
<pre><?php
    // Crear un arreglo asociativo user => points
    $gameRecords = array('lvargas' => 64900, 'elpapi' => 58831, 'chema' => 9215);

    // Recorrer el arreglo
    echo "\$gameRecords == \n";
    foreach ( $gameRecords as $user => $points )
        echo '   ', $user, ': ', $points, " puntos\n";

    // Agregar un elemento al arreglo
    echo "\n\$gameRecords['pinocho'] = 64901\n";
    $gameRecords['pinocho'] = 64901;

    // Ordernar el arreglo por valores descendenemente
    arsort($gameRecords);

    // Estudiar el arreglo
    echo "\n\$gameRecords == "; var_dump($gameRecords);
?></pre>
Ejemplo de uso de arreglos secuenciales y asociativos en PHP. Correr este ejemplo.

Los objetos de PHP se parecen mucho a los de Java. Un objeto es una instancia de una clase, la cual puede tener herencia y polimorfismo con otras clases. Los objetos se crean con el operador new y sus miembros se acceden con el operador ->. Este tema se explica más adelante.

El tipo de datos y valor especial null sirve para indicar que una variable no tiene valor. Si se trata de hacer referencia a una variable que no existe, PHP puede o no generar un warning de acuerdo a su configuración, y el valor de reemplazo de la variable no existente es null, el cual se puede escribir en cualquier combinación de mayúsculas y minúsculas.

Cuando se utiliza un valor de un tipo de datos en un contexto donde se espera otro, PHP trata de hacer conversiones automáticamente; lo cual funciona bien en la mayoría de casos para los valores primitivos. El programador puede forzar la conversión de datos utilizando explicit type cast, anteponiendo el tipo de datos entre paréntesis a la expresión que se quiere convertir, como en el ejemplo del php_explicit_type_cast.

$num = 5 / 2;
echo "5 / 2 == $num\n";         // prints 2.5

$num = (int) (5 / 2);
echo "(int) (5 / 2) == $num\n"; // prints 2
Conversión explícita de datos en PHP. Correr este ejemplo.

Operadores

El programador puede escribir los mismos operadores que usa en C o JavaScript sin cambios en PHP. Las diferencias son mínimas, como las que se exponen a continuación.

El operador de concatenación en PHP es el punto (.), así el operador de suma (+) pretende sólo recibir operandos numéricos, de lo contrario, tratará de convertirlos a números.

Al igual que en JavaScript, el operador de igualdad == indica si dos valores son el mismo aún después de hacer conversiones, así la expresión "+12.3" == 12.30 se evaluará como verdadera. El operador de identidad === se evalúa verdadero sólo si sus operandos son iguales sin hacer conversiones.

Los operadores lógicos clásicos && y || trabajan de la misma forma que en otros lenguajes de programación. Sin embargo, PHP introduce los operadores lógicos nombrados and y or (que también pueden escribirse en mayúsculas: AND y OR), que tienen una prioridad menor, por lo que pueden causar conflictos si se combinan operadores clásicos con los nombrados en una misma expresión. Se aconseja al programador utilizar sólo los operadores clásicos a menos de que se entienda bien el efecto del operador nombrado.

Control de flujo

Los condicionales en PHP se indican con tres cláusulas: if/else/elseif, ?: y switch. La cláusula else if se puede escribir con el espacio o sin él (elseif); su efecto es el mismo en ambos casos, como se aprecia en el php_elseif.

<body>
    <h1>Lógica de pares</h1>
    <p>
        <?php
            $promedio = rand() % 100;
            echo "Promedio = $promedio: ";
            if ( $promedio < 30 )
                echo 'Vuelva a explicar';
            elseif ( $promedio < 70 )
                echo 'Discusión de pares';
            else
                echo 'Pase al siguiente tema';
        ?>
    </p>
</body>
La cláusula else if se puede escribir con o sin el espacio en PHP. Correr este ejemplo.

Los ciclos en PHP se realizan con las mismas cláusulas de C: while, do-while y for, las cuales mantienen la misma sintaxis. PHP agrega una estructura de control más: foreach, la cual sirve para iterar arreglos y objetos, como se verá más adelante en sus respectivas secciones.

Arreglos

En PHP los arreglos son contenedores asociativos, es decir, son contenedores de parejas key => value, donde las llaves (key) pueden ser números o strings, y si se omiten, PHP asume valores enteros. Un arreglo se crea simplemente asignándole valores a sus elementos o con el constructo array(). Para crear una pareja key => value y agregarla al arreglo, se emplea la siguiente notación:

$arr[key] = value;

Si se asigna un valor a una llave que ya existe dentro del arreglo, no se inserta de nuevo, si no que se reemplaza su valor anterior. Si se omite la llave en la asignación, PHP busca el último entero asociado en el arreglo y asigna el subsecuente al valor. Esto se ilustra en el php_array_assign.

// Crea un arreglo asignándole un valor
$notas['luis'] = 9.5;
echo "\n\$notas['luis'] = 9.5;\n\$notas = "; print_r($notas);

// Crea otra asociación (key,value) y la agrega al arreglo
$notas['ana'] = 10.0;
echo "\n\$notas['ana'] = 10.0;\n\$notas = "; print_r($notas);

// Llave 'luis' ya existe en arreglo, actualiza valor
$notas['luis'] = 6.5;
echo "\n\$notas['luis'] = 6.5;\n\$notas = "; print_r($notas);

// Asocia un valor a la mayor clave numérica libre en el arreglo
$notas[] = 'ana';
echo "\n\$notas[] = 'ana';\n\$notas = "; print_r($notas);

// Asocia la posición numérica dada con el valor
$notas[3] = 'diana';
echo "\n\$notas[3] = 'diana';\n\$notas = "; print_r($notas);

// Asocia en la posición 1 ó 4?
$notas[] = 'fiona';
echo "\n\$notas[] = 'fiona';\n\$notas = "; print_r($notas);
Insertar y actualizar elementos en un arreglo utilizando el operador corchetes y asignación. Correr este ejemplo.

La segunda forma de crear un arreglo es utilizando el constructo array(k1 => v1, k2 => v2, ..., kN => vN), que recibe las parejas llave=valor utilizando la notación key => value que asemeja una asignación, pero en lugar de ser una asignación directa indica actualizar el valor del índice o llave key. En el constructo array(), si una llave se omite, se asume el próximo índice que sigue al último entero utilizado. El resultado final del php_array_assign se podría escribir utilizando array() como se aprecia en el php_array_construct.

// Crea un arreglo utilizando el constructo array()
$notas = array('luis' => 6.5, 'ana' => 10.0, 'ana', 3 => 'diana', 'fiona');
echo "\n\$notas = "; print_r($notas);

// Se puede asociar nuevos elementos a un arreglo creado con array()
echo "\nAgregando notas para diana y fiona:";
$notas['diana'] = 8.5;
$notas['fiona'] = 5.0;

// Imprime todos los elementos del arreglo utilizando el ciclo foreach
foreach($notas as $key => $value)
    echo "\n    $key: $value";

// Calcula el promedio de la clase
$sum = 0.0;
$count = 0;
foreach($notas as $key => $value)
{
    if ( gettype($value) == 'double' )
    {
        $sum += $value;
        ++$count;
    }
}
printf("\n\nEl promedio de los $count alumnos es %.2f", $sum / $count);
Crear un arreglo utilizando el constructo array() y recorrerlo con el ciclo foreach-as. Correr este ejemplo.

PHP provee el ciclo foreach-as, el cual recorre un arreglo por cada una de sus parejas key => value y permite al programador hacer algo con ellas en cada iteración del ciclo. El orden de recorrido es el mismo en que se encuentran los elementos en el arreglo, y puede alterarse con funciones de ordenamiento.

Las llaves en un arreglo asociativo sólo pueden ser enteras o string, pero el valor puede ser de cualquier tipo de datos, incluso otro arreglo. Esto permite representar matrices o arreglos de más dimensiones.

Funciones

Las funciones en PHP se declaran con la palabra reservada function seguido por la lista de parámetros entre paréntesis, de la misma forma que se hace en JavaScript. El php_factorial_function ilustra la función factorial() y su uso para imprimir los primeros 19 factoriales.

<body>
    <h1>Factoriales</h1>
    <ul>
    <?php
        # Retorna el factorial de $n
        function factorial($n)
        {
            return $n > 0 ? $n * factorial($n - 1) : 1;
        }

        // Imprime los primeros 20 factoriales incluyendo el 0
        for ( $i = 0; $i < 20; ++$i )
            echo "<li>$i! = ", factorial($i), "</li>\n";
    ?>
    </ul>
</body>
Números factoriales con una función recursiva en PHP. Correr este ejemplo.

En JavaScript el manejo de valores o referencias es implícito, es decir, el lenguaje se reserva su manejo y el programador no puede alterarlo. En cambio, en PHP el manejo de valores y referencias es explícito, es decir, el programador debe indicar cuándo quiere hacer una copia de un valor o cuando quiere tener una referencia hacia el valor original. Como es de esperar, las referencias se crean con el operador ampersand (&), como se aprecia en el ejemplo del php_byvalue_byreference.

Reglas de alcance de variables (variable scope)

Una importante diferencia en PHP con otros lenguajes de programación son sus reglas de alcance de variables (variable scope). PHP asume que toda referencia a variable que se haga en el cuerpo de una función, es una referencia a una variable local, a menos de que se indique lo contrario. En el php_variable_scope_error, la función print_factorials() falla en la línea 16 al tratar de acceder a $count, ya que PHP busca una variable $count local, la cual no fue declarada en ese contexto.

<body>
    <h1>Factoriales</h1>
    <ul>
    <?php
        # Variable global
        $count = 25;

        function factorial($n)
        {
            return $n > 0 ? $n * factorial($n - 1) : 1;
        }

        function print_factorials()
        {
            // Error: $count no esta definida localmente
            for ( $i = 0; $i < $count; ++$i )
                echo "<li>$i! = ", factorial($i), "</li>\n";
        }

        print_factorials();
    ?>
    </ul>
</body>
Error tratando de acceder a una variable local no definida. Correr este ejemplo.

Este problema es un error común de programación, debido a PHP rompe la norma de la mayoría de lenguajes con los cuales podría estar familiarizado el desarrollador. Hay varias soluciones. Si es factible, se recomienda pasar por parámetro el valor que requiere la función, así se mantiene la modularidad del código y se reduce la dependencia de variables globales, como se hace en el php_variable_scope_fix1.

<body>
    <h1>Factoriales</h1>
    <ul>
    <?php
        // Variable global
        $count = 25;

        function factorial($n)
        {
            return $n > 0 ? $n * factorial($n - 1) : 1;
        }

        function print_factorials($count)
        {
            // Error: $count no esta definida localmente
            for ( $i = 0; $i < $count; ++$i )
                echo "<li>$i! = ", factorial($i), "</li>\n";
        }

        print_factorials($count);
    ?>
    </ul>
</body>
Pasar variables globales por parámetro a una función. Correr este ejemplo.

Si por alguna razón se necesita trabajar con variables globales, se le puede indicar a la función que la variable $count no está definida en el contexto local si no en el global, lo cual se hace con la palabra reservada global, como se aprecia en la línea 16 del php_variable_scope_fix2. El uso de variables globales se considera una mala práctica de programación.

<body>
    <h1>Factoriales</h1>
    <ul>
    <?php
        // Variable global
        $count = 25;

        function factorial($n)
        {
            return $n > 0 ? $n * factorial($n - 1) : 1;
        }

        function print_factorials()
        {
            // Hace a $count accesible en el contexto local
            global $count;

            // Error: $count no esta definida localmente
            for ( $i = 0; $i < $count; ++$i )
                echo "<li>$i! = ", factorial($i), "</li>\n";
        }

        print_factorials();
    ?>
    </ul>
</body>
Uso de variables globales. Correr este ejemplo.

PHP provee algunas variables globales que son accesibles desde cualquier contexto, es decir, sin tener que declararlas primero con la palabra reservada global. Se les conocen como variables superglobales y son arreglos asociativos con información muy útil para el programa. Se listan en la php_superglobal_variables.

Arreglo Descripción
$GLOBALS Sirve para acceder a las variables globales, en especial si se quiere acceder a una variable global y existe una homónima en el contexto local.
$_SERVER Contiene variables enviadas por el servidor web al intérprete de PHP, como $_SERVER['PHP_SELF'] que tiene la ruta del programa PHP relativa a la raíz del sitio, el nombre del servidor web ($_SERVER['SERVER_NAME']), detalles del navegador que solicitó ejecutar el programa PHP ($_SERVER['HTTP_USER_AGENT']), el IP de la máquina del cliente ($_SERVER['REMOTE_ADDR']) y muchos otros.
$_GET Aloja las variables pasadas al script por parámetro en el URL (método HTTP GET).
$_POST Aloja las variables pasadas al script por parámetro utilizando el método HTTP POST.
$_FILES Aloja información sobre los potenciales archivos que se hayan enviado (uploaded) por el método HTTP POST.
$_COOKIE Un arreglo con variables y valores que el navegador aloja por petición del sitio web.
$_SESSION Almacena variables globales para controlar la sesión con el navegador.
$_REQUEST Un arreglo que contiene la unión de los arreglos $_GET, $_POST y $_COOKIE.
$_ENV Las variables ambiente que dispone el intérprete de JavaScript. Son dependientes del sistema operativo.
Arreglos asociativos superglobales en PHP

Siempre que se va a usar una variable cuyo valor fue provisto por una fuente externa, como un parámetro, debe evitarse que su valor sea ejecutado por el intérprete de JavaScript o genere código (X)HTML/JavaScript, ya que permite a un atacante inyectar código maligno que podría revelar información confidencial de su sitio web u otro tipo de daño. PHP provee varias funciones que convierten código en una representación no ejecutable, como htmlentities() que reemplaza los caracteres especiales (<, >, &) por sus entidades correspondientes (&lt;, &gt;, &amp;).

Ejercicio. Escriba un programa PHP que imprima en tablas (X)HTML el contenido de cada uno de los arreglos superglobales de PHP. Estudie el resultado para encontrar algunos valores que le pueden resultar de utilidad en sus aplicaciones web. Pruebe su programa con algunos parámetros. Si nombra su archivo superglobals.php y lo coloca en la raíz de su sitio web, puede probar con este enlace: http://localhost/superglobals.php?test=hola_mundo.

Objetos

Los objetos en PHP utilizan prácticamente la misma sintaxis de Java, con algunas variaciones de C++. Los objetos son instancias de clases. Una clase se declara con la palabra reservada class seguida por un identificador. Las propiedades (miembros de datos) y métodos de la clase, se deben anteceder con el tipo de acceso que deben tener: private, protected o public. Si se omite, PHP asume public.

Los métodos se declaran como cualquier otra función de PHP, sólo que dentro del cuerpo de la clase. Un método de una clase a diferencia de una función normal, tiene acceso a tres identificadores reservados: $this es una referencia hacia la instancia del objeto al que se le invocó el método, y se utiliza para acceder a sus propiedades y otros métodos. self:: es una referencia hacia la clase misma y sirve para acceder a sus miembros estáticos. parent:: permite acceder a propiedades y métodos de la clase base.

Para instanciar un objeto se utiliza el operador new clase(parametros), enviándole los parámetros que recibe la clase en el constructor. Un constructor es un método con el nombre especial __construct, el cual se encarga de inicializar los miembros de datos del objeto. Aunque se puede utilizar un método con el mismo nombre de la clase como constructor, no se recomienda esta práctica, ya que versiones recientes de PHP lo interpretan como un método normal.

class Player
{
    protected $nickname, $email, $score;

    function __construct($nickname, $email, $score = 0)
    {
        $this->nickname = $nickname;
        $this->email = $email;
        $this->score = $score;
    }

    function increaseScore($by = 1) { $this->score += $by; }
    function getNickname() { return $this->nickname; }
    function getScore() { return $this->score; }
}

$players[] = new Player('pinocho', 'pinocho@cajafishel.com');
$players[] = new Player('chema', 'chema@semeolvido.com');
$players[] = new Player('osqui', 'dumbo@tlc.com');

$bets = rand(30, 100);
echo count($players), " jugadores, $bets apuestas... ";
for ( $i = 0; $i < $bets; ++$i )
{
    $winner = $players[ rand(0, count($players) - 1) ];
    $winner->increaseScore();
}

echo "Resultados:\n";
for ( $i = 0; $i < count($players); ++$i )
    echo '   ', $players[$i]->getNickname(), ': ', $players[$i]->getScore(), "\n";
Ejemplo de clases y objetos en PHP. Correr este ejemplo.

Ejercicio. Escriba un programa PHP que cuente cuántos días feriados "caen" cada día de la semana para el año en curso. Imprima el resultado en una tabla, por ejemplo:.

Feriados de 2011 por día de la semana
Feriados Detalle
Domingo 2 Día del trabajo, Navidad
Lunes 3 Batalla de Rivas, Anexión de Nicoya, Día de la madre
Martes 1 Día de la Virgen de los Ángeles
Miércoles 1 Día de las culturas
Jueves 1 Independencia de Costa Rica
Viernes 0
Sábado 1 Año nuevo

Sugerencia: Represente cada feriado como un objeto, y el conjunto de ellos como un arreglo.

Manejo de archivos

PHP puede crear o acceder a archivos existentes en el servidor web, por ejemplo, para crear bitácoras o archivos de datos que necesita el programa en PHP.

PHP provee una considerable cantidad de funciones para trabajar con archivos y el sistema de archivos del sistema operativo, la mayoría de ellas fuertemente influenciadas por el lenguaje de programación C. El ejemplo del php_log_file_append muestra cómo crear una bitácora en la misma carpeta donde está el archivo .php, la cual registra detalles sobre cada acceso, como la fecha y la dirección IP del cliente que solicitó el recurso.

// Abrir el archivo para agregarle texto al final (append)
$log_file = fopen('log_file.txt', 'a')
    or die('Error: No se pudo abrir log_file.txt');

// Escribir algunos datos sobre el acceso: fecha, IP del cliente, etc
fprintf($log_file, "%s\t%s\t%s\t%s\n"
    , date('ymd:His')
    , $_SERVER['REMOTE_ADDR']
    , $_SERVER['REMOTE_PORT']
    , $_SERVER['HTTP_USER_AGENT']
    ) or die('Error: no se pudo escribir en la bitácora');

// Cerrar el archivo
fclose($log_file);
Crear un archivo de texto en el servidor. Correr este ejemplo.

El procedimiento para trabajar con archivos en PHP es el mismo que en otros lenguajes: abrir el archivo con la función fopen(), leer o escribir bytes en él (fread(), fwrite(), fprintf(), etc.) y cerrar el archivo con la función fclose(). Todas estas funciones necesitan saber en cuál archivo trabajar. PHP utiliza un tipo de variable especial llamada "recurso" (resource) para representar archivos o registros de una base de datos.

La función fopen() retorna un recurso (resource) hacia el archivo cuya ruta está en el primer parámetro. Pero si fopen() falla al abrir el archivo, retorna false. Es una práctica común detener la ejecución del script con la función die() en caso de no poder acceder a un recurso importante (línea 3 del php_log_file_append) ya que permite rápidamente a los desarrolladores identificar y solucionar el problema; sin embargo, esto nunca debe hacerse. Lo adecuado es presentar una página de error amigable al visitante, y quizá enviar un correo electrónico al administrador del sitio web para avisar del problema. Las principales razones porque fopen() falla al abrir un archivo es por falta de permisos, falta de espacio en disco o porque el nombre del archivo es inválido. Estos problemas son responsabilidad del programador y no del visitante.

El php_log_file_read muestra como recorrer la bitácora creada en el php_log_file_append y presentar su contenido en una tabla XHTML. La lectura se hace en un ciclo hasta encontrar el final del archivo (línea 11). En cada iteración del ciclo se obtiene una línea del archivo y se imprime como una fila de la tabla, a excepción de que sea una línea vacía.

// Abrir el archivo para mostrar su contenido
$log_file = fopen('log_file.txt', 'r')
    or die('Error: No se pudo abrir log_file.txt');

echo "<table><thead><tr>"
    , "<th></th><th>Fecha</th><th>IP</th><th>Puerto</th><th>Agente usuario</th>"
    , "</tr></thead><tbody>\n";

// Imprimir cada línea en una fila de una tabla XHTML
$count = 0;
while ( ! feof($log_file) )
{
    $line = fgets($log_file);
    $line = trim($line);
    if ( strlen($line) == 0 ) continue;
    $line = str_replace("\t", '</td><td>', $line);
    echo "<tr><th>", ++$count, "</th><td>$line</td></tr>\n";
}

echo "</tbody></table>\n";
fclose($log_file);
Leer un archivo de texto en PHP. Correr este ejemplo.

Ejercicio. Escriba un script en PHP que cuente el número de visitas que se le han hecho y las imprima como resultado.

¿Qué pasa si dos programas escriben simultáneamente en un mismo archivo? Si no se hace de forma controlada, la respuesta será que el archivo se corrompe. En un ambiente web, una misma página puede ser visitada por uno, varios o miles de usuarios simultáneamente. Cada uno provoca una ejecución distinta del mismo script PHP. Por eso es tan importante evitar que dos procesos traten de escribir el mismo archivo simultáneamente, o que uno escriba mientras otros están leyendo.

PHP provee la función flock() que bloquea un archivo para uso exclusivo (LOCK_EX) o lo desbloquea (LOCK_UN), y funciona de la siguiente forma. Supóngase que un proceso 1 pide bloquear un archivo con flock(), el cual le otorga permiso de escritura. Mientras el proceso 1 está escribiendo, un proceso 2 solicita bloquear el archivo para escritura; flock() pone en espera al proceso 2 hasta que el proceso 1 haya terminado de escribir. Una vez que proceso 1 desbloquee el archivo invocando a flock() con el parámetro LOCK_UL, flock() sacará de la cola de espera al proceso 2 bloqueando el archivo de nuevo.

El uso de flock() produce una serialización de procesos, y por ende, introduce tiempos de espera que podrían ser notorios para el visitante. Para reducir estos tiempos al máximo se recomienda bloquear el archivo inmediatamente antes de hacer la escritura y desbloquearlo inmediatamente después de escribir en él; es decir, se debe evitar bloquear un archivo desde que inicia el script, y desbloquearlo al final de éste. El php_log_file_lock muestra el ejemplo de la bitácora (php_log_file_append) utilizando candados.

// Abrir el archivo para agregarle texto al final (append)
$log_file = fopen('log_file.txt', 'a')
    or die('Error: No se pudo abrir log_file.txt');

// Escribir algunos datos sobre el acceso: fecha, IP del cliente, etc
if ( flock($log_file, LOCK_EX) )
{
    fprintf($log_file, "%s\t%s\t%s\t%s\n"
        , date('ymd:His')
        , $_SERVER['REMOTE_ADDR']
        , $_SERVER['REMOTE_PORT']
        , $_SERVER['HTTP_USER_AGENT']
        ) or die('Error: no se pudo escribir en la bitácora');

    flock($log_file, LOCK_UN);
}
// Cerrar el archivo
fclose($log_file);
Utilizar candados para evitar que dos procesos escriban un mismo archivo simultáneamente. Correr este ejemplo.

Como se podrá notar en la línea 6 del php_log_file_lock, la solicitud de bloquear un archivo debe hacerse en un if, debido a que flock() retorna false en caso de que el mecanismo de candados no sea implementado por el sistema operativo o el sistema de archivos, como ocurre en FAT32 o NFS (Network File System).

Ejercicio. ¿Puede el script del ejercicio anterior ser reutilizado por varias páginas de un sitio web para mostrar el número de visitas que ha tenido cada una por separado?. Si la respuesta es "No", modifique el script para ser capaz de manejar múltiples contadores. Modifique las páginas de las secciones principales (inicio, currículo, aficiones y computación) su sitio web personal para mostrar el número de veces que han sido visitadas (en un pie de página u otro lugar adecuado). Sugerencia: estudie los elementos del arreglo superglobal $_SERVER para obtener la ruta del script PHP y del sitio web; y el constructo require_once() para reutilizar código PHP. Asegúrese de bloquear el archivo de contadores antes de escribir en él.

Bases de datos

Además del uso de archivos, PHP puede conectarse a diversidad de motores de bases de datos (DBMS, Database Management System) para almacenar información del sitio web, como SQLite, MySQL, PostgreSQL, SQL Server y Oracle. De todos ellos cabe resaltar a MySQL, por ser relativamente liviano y apto para la mayoría de sitios web de mediana escala. Para sitios web gigantes de alta concurrencia, el modelo relacional es inapropiado y el desarrollador debería considerar alguna alternativa, como bases de datos orientadas a documentos. En los ejemplos de esta sección se usará MySQL debido a su histórica popularidad junto con PHP.

Para instalar MySQL en Linux, puede utilizar su administrador de paquetes o compilar desde el código fuente. Además debe instalar el driver de MySQL en PHP. Por ejemplo, en Ubuntu puede lograr estas dos tareas emitiendo el comando sudo apt-get install mysql-server php5-mysql y reiniciar el servidor de PHP si lo hay. Para Microsoft Windows existe un instalador o bien puede utilizar un ambiente de desarrollo integrado como EasyPHP que ya lo incluye. Es normal que en el proceso de instalación se solicite la contraseña del administrador (usuario root) de MySQL, de lo contrario, se asume vacía.

Para acceder a las bases de datos, se debe utilizar un lenguaje de programación y el API (Application Programming Interface) que el DBMS provee. Esto es lo que se hará desde PHP. Sin embargo, la mayoría de motores de bases datos, proveen clientes amigables al usuario, en línea de comandos (trate mysql -u root -p en Ubuntu), gráficos como mySQLAdmin o vía web como phpMyAdmin.

El primer paso para utilizar bases de datos, es crear una en el DBMS y los usuarios que tendrán permiso de accederla. Aunque se pueden crear bases de datos desde PHP dándole permisos de administración del DBMS (la cuenta root de MySQL), no es una solución adecuada ya que compromete la seguridad de los datos. Por esto, es común que la persona que administra el servidor de bases datos utilice alguno de los otros clientes del DBMS para crear una base de datos y los usuarios que podrán modificarla, entre los que se incluye un usuario para los scripts de PHP.

Los siguientes comandos ilustran la creación de una base de datos Universidad utilizando el cliente en línea de comandos de MySQL, y la creación del usuario php_user al cual se le otorgan permisos de administración sobre la base de datos Universidad, pero no sobre otras bases de datos ni el DBMS. Al final se prueba que el usuario php_user haya sido creado exitosamente volviendo a entrar al cliente de MySQL con sus credenciales.

$ mysql -u root -p
Password:

mysql> CREATE DATABASE Universidad;
Query OK, 1 row affected (0.00 sec)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| Universidad        |
+--------------------+
3 rows in set (0.01 sec)

mysql> USE Universidad;
Database changed

mysql> GRANT ALL ON Universidad.* TO 'php_user' IDENTIFIED BY 'php_user_password';
Query OK, 0 rows affected (0.00 sec)

mysql> QUIT
Bye


$ mysql -u php_user -p
Password:

mysql> QUIT
Bye

Es probable que su sitio web se componga de muchos archivos .php y varios de ellos necesiten acceder a la base de datos. Para evitar redundar código, es conveniente guardar las credenciales en un archivo reutilizable, por ejemplo db_credentials.php, cuyo contenido puede ser como el mostrado en el php_db_credentials. Es muy importante que estas variables estén dentro de la etiqueta <?php...?>, de lo contrario cualquier visitante que trate de acceder a él, verá información sensible. Y si por alguna razón el servidor de PHP dejara de funcionar, el servidor web podría enviarle el archivo db_credentials.php íntegro al navegador para que lo almacene en algún lugar del disco. Por esto, de ser posible el archivo con credenciales debería estar en alguna carpeta del sistema de archivos que no es parte del sitio web, y debería tener permisos de lectura y escritura el administrador del sistema operativo (root en Unix) y permiso de lectura para el usuario del servidor web (algo como www-data) únicamente.

<?php
$db_host = 'localhost';
$db_name = 'Universidad';
$db_user = 'php_user';
$db_pass = 'php_user_password';
?>
Archivo reutilizable con las credenciales para acceder a la base de datos. Al correr este ejemplo debería obtener una página vacía.

Para comunicarse con el DBMS, PHP provee un conjunto de funciones que varían de un DBMS a otro, o la clase genérica PDO (PHP Data Objects) que provee una misma interfaz para acceder a varios DBMS.

Funciones para acceder a MySQL

PHP provee un conjunto de funciones para acceder a MySQL, las cuales inician con el prefijo mysql_. El procedimiento para acceder a la base de datos desde PHP es típicamente el siguiente:

  1. Se establece una conexión con el servidor de bases de datos (DBMS): mysql_connect().
  2. Se escoge la base de datos con la que se va a trabajar: mysql_select_db().
  3. Se emiten consultas (para obtener o actualizar información): mysql_query().
  4. Si los hay, se recorren los registros resultado de la consulta con mysql_fetch_row() o con mysql_fetch_assoc() , se hace algún procesamiento con ellos, y se construye alguna salida útil para el visitante.
  5. Si los hay, se liberan los registros obtenidos de la base de datos: mysql_free_result().
  6. Se cierra la conexión con el DBMS: mysql_close().

El php_db_create_schema muestra un ejemplo parcial de cómo crear las tablas en una base de datos vacía desde PHP. La línea 3 hace que, en esa posición, PHP incruste literalmente el contenido del archivo db_credentials.php mostrado en el php_db_credentials, y luego lo interprete; lo cual ocasiona que las variables $db_host, $db_name, $db_user y $db_pass estén definidas como variables globales para el resto del programa.

// Este PHP se llama solo 1 vez para crear el esquema de la base de datos.
// Cargar las credenciales de la base de datos
require_once('db_credentials.php');

// Conectarse al servidor de base de datos (DBMS)
$db_connection = mysql_connect($db_host, $db_user, $db_pass)
    or die("No se pudo conectar al DBMS: " . mysql_error() );

// De todas las bases de datos que hay en el DBMS, escoger la de Universidad
// Esto equivale a escribir 'USE Universidad;' en un cliente de MySQL
mysql_select_db($db_name)
    or die("No se pudo seleccionar la base de datos: " . mysql_error() );

// Truco: si se pasa el parametro drop_tables=true, se recrea la base de datos
$drop_tables = isset($_GET['drop_tables']) && $_GET['drop_tables'] == 'true';

// Borrar las tablas es util por si el webmaster quiere empezar el sitio de cero
if ( $drop_tables )
{
    // Obtener la lista de tablas existentes para eliminarlas
    $result = mysql_query("SHOW TABLES FROM $db_name")
        or die("No se pudo obtener la lista de tablas: " . mysql_error() );

    // Por cada tabla encontrada en la base de datos, eliminarla
    while ($row = mysql_fetch_row($result))
    {
        // mysql_fetch_row() retorna un arreglo con un valor por cada atributo
        $table_name = $row[0];

        mysql_query("DROP TABLE $table_name") or die( mysql_error() );
        echo "<li>Tabla $table_name: <span class=\"warning\">Eliminada</span>.</li>\n";
    }

    // Ya los registros con los nombres de las tablas no seran usados mas, liberarlos
    mysql_free_result($result);
}

// Crear las tablas que no existen
// Crear cada tabla de la base de datos Universidad
$query =
    'CREATE TABLE IF NOT EXISTS Estudiante (
        Carne CHAR(6),
        Nombre VARCHAR(50) NOT NULL,
        Correo VARCHAR(100) UNIQUE,
        Promedio REAL DEFAULT 0.0,
        CONSTRAINT PRIMARY KEY (Carne),
        INDEX(Nombre(45))
    );';
mysql_query($query) or die("No se pudo crear la tabla Estudiante: " . mysql_error());
echo "Tabla Estudiante: creada exitosamente.<br/>\n";

$query =
    'CREATE TABLE IF NOT EXISTS Profesor (
        Cedula INT(9),
        Nombre VARCHAR(50) NOT NULL,
        Correo VARCHAR(100) NOT NULL UNIQUE,
        CONSTRAINT PRIMARY KEY (Cedula),
        INDEX(Nombre(45))
    );';
mysql_query($query) or die("No se pudo crear la tabla Profesor: " . mysql_error());
echo "Tabla Profesor: creada exitosamente.<br/>\n";

// Es buena practica cerrar la conexion tan pronto como se deja de usar
mysql_close($db_connection);
Creación de tablas en una base de datos MySQL vacía. Obtener el ejemplo completo.

La línea 6 del php_db_create_schema invoca la función mysql_connect($host, $user, $pass) para intentar establecer una conexión con el servidor de base de datos MySQL que está en la computadora local (puesto que el valor de $db_host es 'localhost'), con el usuario y contraseña que se crearon para uso exclusivo de los scripts de PHP. Si mysql_connect() logra establecer la conexión, retorna un recurso de PHP (resource) que representa a la conexión y puede usarse subsecuentemente. Si no se puede establecer la conexión, mysql_connect() retorna false y PHP verifica la segunda condición del or en la línea 7, que detiene la ejecución del script con mensaje de error para el navegador. Como se dijo antes, este mensaje es útil para el desarrollador, pero poco amigable para el visitante.

Si la ejecución alcanza la línea 11 del php_db_create_schema, indica que la conexión con el DBMS fue establecida exitosamente. El DBMS puede tener muchas bases de datos a su cargo, y es necesario indicarle con cual de todas ellas se quiere trabajar con la función mysql_select_db($db_name).

A partir de la línea 40 en adelante, se empiezan a crear las tablas de la base de datos Universidad, pero si ya existen, se mantienen inalteradas. Sin embargo, pueda que por alguna razón el administrador del sitio web de la Universidad (webmaster) quiera limpiar la base de datos y empezar de cero. Para efectos ilustrativos, si se ha provisto el parámetro create_schema.php?drop_tables=true en el URL (de esto se hablará luego) se destruirán todas las tablas existentes de la base de datos sin confirmación alguna, lo cual no es un buen diseño, pero se hace aquí con propósitos ilustrativos.

En caso de que el administrador solicite recrear las tablas, el método más eficiente es eliminarlas de golpe (DROP TABLE) y volverlas a crear vacías. Para poder borrarlas se necesita conocer el nombre de cada una de ellas. El script podría tener estos nombres en un arreglo, o bien, solicitarlos al DBMS mediante una consulta SHOW TABLES de MySQL. Cada cual tiene sus ventajas y desventajas. Aquí se seguirá la segunda.

La consulta SHOW TABLES opera de la misma forma que una consulta SELECT de SQL. La consulta es ejecutada con la función mysql_query($sql_query), la cual retorna un recurso que representa el resultado de la consulta, a veces llamado "record set". En la línea 21 del php_db_create_schema, este recurso se almacena en una variable $result.

Es importante hacer notar que $result almacena un recurso, no los datos que se obtuvieron de la consulta. En el modelo relacional, los datos resultado de una consulta son una tabla temporal, y aunque algunos DBMS permiten acceder a todos ellos como si fuesen una matriz en memoria aleatoria (RAM, Random Access Memory), es más eficiente recorrerlos una fila a la vez. Cada invocación a la función mysql_fetch_row($query_result) trae de la base de datos la próxima fila resultado de una consulta, y la retorna en un arreglo que permite acceder a cada valor por un índice que equivale al número de la columna. Para el caso de SHOW TABLES sólo se retorna una columna con el nombre de cada tabla presente en la base de datos. La línea 28 del php_db_create_schema muestra cómo acceder a este valor. Si se quiere acceder al valor por el nombre de la columna en lugar de un índice, utilícese la función mysql_fetch_assoc($query_result), que puede tener el efecto de hacer al código más portable.

Una vez que se ha obtenido el nombre de la tabla, se emite otra consulta DROP TABLE para eliminarla de la base de datos (línea 30). Nótese que la invocación a mysql_fetch_row() se hace en un ciclo, ya que es muy probable que existan varias tablas en la base de datos. Cuando se ha terminado de procesar la última de ellas, mysql_fetch_row() retornará false en lugar de un arreglo de valores.

La línea 35 del php_db_create_schema invoca la función mysql_free_result($query_result) para liberar las estructuras de datos en memoria que alojan el resultado de la consulta obtenido en la línea 21 con mysql_query(). Si se omite esta invocación, el intérprete de PHP lo hará automáticamente cuando el script termine su ejecución.

En síntesis, el código de las líneas 18 a 36 del php_db_create_schema se ejecuta sólo cuando se ha solicitado eliminar las tablas existentes de la base de datos, obteniendo el nombre de cada una de ellas y eliminándolas con DROP TABLE. Una vez que se han eliminado, el programa sigue su ejecución normal en la línea 40, que emite otras consultas (CREATE TABLE) con la misma función mysql_query(), pero debido a la naturaleza de la instrucción CREATE TABLE de SQL, no se generan registros de datos como resultado, sino los valores true y false indicando respectivamente si la creación de la tabla fue exitosa o no. Como puede inferirse, la función mysql_query() se utiliza para ejecutar cualquier tipo de consulta SQL: crear tablas, insertar valores, actualizar valores, eliminar valores y obtener datos.

Para mysql_query() la consulta es un simple string que pasa directamente al DBMS. La sintaxis de dicha consulta es completamente dependiente del DBMS en uso. El desarrollador web debe consultar la documentación oficial de su DBMS para comprobar su validez u otras opciones útiles. En los ejemplos de este documento se ha utilizado MySQL cuya sintaxis se puede consultar en línea.

Finalmente la línea 64 del php_db_create_schema desconecta al intérprete de PHP del servidor de bases de datos con la función mysql_close(), la cual libera recursos y permite que el DBMS pueda aceptar otras conexiones; aspecto importante cuando se atienden miles de visitantes simultáneamente en un sitio web.

Ejercicio. Modifique el contador de visitas de varias páginas de su sitio web hecho en el ejercicio anterior, para almacenar los contadores en una base de datos. Estudie la documentación de su DBMS para utilizar transacciones cada vez que se actualiza un contador.

Objetos para acceder a la base de datos

Formularios web

A diferencia de otros medios tradicionales, como la radio y televisión; el web permite una comunicación interactiva, donde el visitante puede expresarse y retroalimentar a los autores. El mecanismo de interacción más primitivo es activar un elemento, haciendo click sobre él con el puntero del ratón o navegando con el teclado. Sin embargo, es el formulario web el mecanismo que permite al visitante comunicar información textual, a través de campos de texto, seleccionando valores en una lista emergente, adjuntando un archivo de su computadora, y otros controles que guardan mucha similitud a los que dispone la interfaz de una aplicación de escritorio.

Las aplicaciones web actuales recurren al formulario web como el escenario donde la interacción con el usuario tiene lugar. Tómese de ejemplo un cliente web de correo (como GMail). Los campos para el destinatario, las copias, y el título del mensaje, se implementan con campos de texto (text fields); el cuerpo del mensaje se escribe en un control de área de texto (text area); los botones permiten formatear el texto del mensaje, adjuntar archivos, y uno en especial, el botón de enviar (submit) termina la edición del mensaje y envía copias a los destinatarios.

En la mayoría de casos, la interacción con un formulario web tiene repercusiones en el lado del sevidor. Por ejemplo en el caso del cliente de correo, además de enviar el mensaje a los destinatarios, se guardará una copia en el buzón de salida. Es decir, algún texto se concatenará a un archivo, o un registro se agregará a la base de datos del servidor de correo. Es por esto que la implementación de un formulario web requiere trabajo en ambas partes: en el lado del cliente, el navegador despliega el formulario y asiste al informante en el ingreso de información mediante JavaScript; mientras que en el lado del servidor se reciben los datos, se realiza la validación de los mismos y se aplica el efecto para el cual se ideó el formulario (enviar un correo, almacenar datos, buscar información, etc.).

El formulario web es un concepto que existe desde los inicios de la web, y se escribe con el elemento form como se ejemplifica en las líneas 12 a 16 del php_login_form_get. La primera vez que se carga este programa, ninguna de las variables $username y $password tendrán un valor, por lo que el control alcanza el else y despliega el formulario web de las líneas 12 a 16. Nota: El operador <<< se conoce como Heredoc, y es seguido por un identificador y un cambio de línea. Todos los caracteres que se encuentren entre ese cambio de línea y la próxima ocurrencia del identificador forman un string en el cual se hace interpolación de variables. Es útil para encerrar texto que contiene comillas dobles sin tener que utilizar caracteres de escape (\). La única restricción es que el identificador de cierre sólo puede estar precedido por un cambio de línea como se ve en la línea 17.

<body><?php
    $username = isset($_GET['username']) ? $_GET['username'] : '';
    $password = isset($_GET['password']) ? $_GET['password'] : '';
    if ( $username != '' && $password != '' )
    {
        echo "<p>Bienvenido(a) <strong>$username</strong> a nuestro sitio seguro. ";
        echo "(No eres <a href=\"login1.php\">$username</a>?).</p>";
    }
    else
    {
        echo <<<_EOT
        <form method="get" action="login1.php">
            <p><label>Usuario: <input type="text" name="username"/></label></p>
            <p><label>Contraseña: <input type="password" name="password"/></label></p>
            <p><input type="submit" value="Enviar"/></p>
        </form>
_EOT;
    }
?></body>
Un formulario web minimalista. Correr este ejemplo.

Un formulario web (form) se compone de varios campos de entrada (input, select y textarea) en los que el visitante ingresa o escoge información. En el caso del php_login_form_get el formulario consta de un campo de texto con nombre username (línea 13), un campo de texto para ingresar contraseñas llamado password (línea 14) y el botón de "submit" (línea 15). El usuario llena estos campos y cuando está listo, presiona el botón de enviar (submit). Esto causa que el navegador recopile el valor de todos los campos del formulario como parejas nombre_campo=valor y las envíe a algún programa en el servidor web para que las procese. El URL de este programa se especifica en el atributo action del elemento form (línea 12), que en el caso del php_login_form_get es el programa mismo.

¿Cómo hace el programa indicado en el atributo action para recuperar los datos ingresados por el visitante? El protocolo HTTP establece dos métodos estándar para transferir los datos de un formulario: GET y POST, el cual se escoge con el atributo method del elemento form. En el php_login_form_get se utilizó el método GET (línea 12).

El método GET

El método GET indica que el navegador debe enviar las parejas nombre_campo=valor al servidor en el URL, separadas del programa (indicado por el valor del atributo action del elemento form) por un signo de interrogación, y cada pareja separada de otra por un ampersand (&), de la forma siguiente:

action?field1=value1&field2=value1&...&fieldN=valueN

Al texto que aparece después del carácter signo de pregunta en el URL anterior (marcado en negritas) se le conoce como query string, y es visible al visitante. Esto se puede probar al correr el programa del php_login_form_get, escribir un par de valores en los campos de texto y examinar la barra de direcciones del navegador tras presionar el botón de enviar. Incluso, el visitante puede cambiar los valores en la barra de direcciones y ejecutar de nuevo el programa con ellos; o bien guardar el URL en sus favoritos.

PHP facilita el trabajo al programador, y cada vez que se llama un script, el intérprete de PHP "parsea" el query string y extrae cada valor que en él encuentre, y los agrega a un arreglo asociativo superglobal llamado $_GET, de tal forma que el programador puede acceder a los valores con la expresión:

$field_value = $_GET['field_name'];

donde field_name corresponde al valor del atributo name del campo input, select o textarea definido en el formulario web. En lo siguiente se explicará paso a paso la lógica del php_login_form_get.

La primera vez que el visitante accede al programa login1.php normalmente no provee un query string, y por ende las variables $username y $password adquirirán cadenas vacías en las líneas 2 y 3; ya que la función isset() retorna true sólo para variables definidas, lo cual no ocurre con las entradas $_GET['username'] y $_GET['password'] en el arreglo asociativo $_GET. De esta forma, la condición del if en la línea 4 se evalúa como false y hace que el intérprete de PHP imprima el formulario (líneas 11 a 16), el cual es enviado al navegador.

El navegador presenta el formulario vacío, el cual consta de los siguientes controles: un campo de texto normal con nombre username (línea 13), un campo de texto especial para escribir contraseñas llamado password (línea 14), y el botón de enviar (línea 15). Supóngase que el visitante escribe 'chema' en el campo username, 'semeolvido' en el campo password y presiona el botón Enviar. Para el navegador este botón es especial, y cuando se activa busca el formulario al cual pertenece (línea 12), y examinando su atributo action obtiene el programa al que se le debe enviar los valores ingresados por el usuario (login1.php) y a través de cuál método (get). El navegador recopila los nombres y los valores de cada campo, arma el query string y lo concatena al URL del programa, lo cual genera el siguiente URL:

http://www.ejemplo.com/path/login1.php?username=chema&password=semeolvido

Seguidamente, el navegador pone este URL en la barra de direcciones y envía el siguiente mensaje de solicitud HTTP al servidor (los cambios de línea son significativos):

GET /path/login1.php?username=chema&password=semeolvido HTTP/1.1
Host: www.ejemplo.com:80
User-Agent: Chrome/13

Como se puede ver, los valores de los campos viajan en la línea de solicitud (request line) del mensaje de solicitud HTTP, precedidos por el método de solicitud GET del estándar HTTP. Al recibir este mensaje, el servidor web localiza dentro de su sitio el recurso /path/login1.php y de acuerdo a su configuración, la extensión .php le indica que debe invocar al intérprete de PHP enviándole por parámetro el mensaje de solicitud completo, más otra información.

El intérprete de PHP recibe la información del servidor web, y la distribuye en los arreglos superglobales $_SERVER, $_ENV, etc. "Parsea" el query string; llena el arreglo asociativo $_GET e inicia la ejecución del login1.php del php_login_form_get, pero esta vez habrá una diferencia importante: las entradas $_GET['username'] y $_GET['password'] están definidas en el arreglo asociativo $_GET, por lo que las variables $username y $password adquirirán los valores 'chema' y 'semeolvido' respectivamente en las líneas 2 y 3. La condición del if esta vez se evaluará como verdadera y se imprimirá el texto de bienvenida, el cual es regresado como resultado al servidor web, quien lo despachará al cliente.

Ejercicio. Modifique el ejercicio 1.7 del laboratorio 10 para que muestre un campo de texto donde el visitante puede ingresar el año del cual quiere ver los días feriados. Si no se especifica un año, su programa debe mostrar el conteo de días feriados del año en curso.

El método POST

En el ejemplo del php_login_form_get se envía al servidor una contraseña por el método GET, lo cual la hace visible al usuario en el URL, quien además puede modificarla. El método POST realiza el mismo trabajo del método GET de transferir valores de un formulario al servidor web, con la diferencia de que éstos se envían en el cuerpo del mensaje de solicitud HTTP en lugar de la línea de solicitud, lo cual los hace invisibles al usuario, impidiendo que los puedan modificar o agregar a los marcadores del navegador. Los cambios para hacer el php_login_form_get funcionar con el método POST son pocos, como se resaltan en el php_login_form_post (líneas 2, 3 y 12).

<body><?php
    $username = isset($_POST['username']) ? $_POST['username'] : '';
    $password = isset($_POST['password']) ? $_POST['password'] : '';
    if ( $username != '' && $password != '' )
    {
        echo "<p>Bienvenido(a) <strong>$username</strong> a nuestro sitio seguro. ";
        echo "(No eres <a href=\"login1.php\">$username</a>?).</p>";
    }
    else
    {
        echo <<<_EOT
        <form method="post" action="login1.php">
            <p><label>Usuario: <input type="text" name="username"/></label></p>
            <p><label>Contraseña: <input type="password" name="password"/></label></p>
            <p><input type="submit" value="Enviar"/></p>
        </form>
_EOT;
    }
?></body>
Un formulario de autenticación utilizando el método POST. Correr este ejemplo.

Cuando el intérprete de PHP ejecuta un script, toma las parejas campo=valor del query string y las agrega al arreglo asociativo $_GET, y las parejas que el navegador envía en el cuerpo del mensaje HTTP, las agrega al arreglo asociativo $_POST. Como es de notar, ambos métodos no son excluyentes. El programador debe simplemente tener el cuidado de utilizar el arreglo asociativo correspondiente al método escogido en el atributo method del elemento form.

A modo de ilustración supóngase que el visitante ingresa los mismos valores: 'chema' en el campo username, 'semeolvido' en el campo password y presiona el botón Enviar del php_login_form_post. Esta vez el navegador detecta que el método de envío de datos es POST, y ensambla el siguiente mensaje de solicitud HTTP:

POST /path/login1.php HTTP/1.1
Host: www.ejemplo.com:80
User-Agent: Chrome/13
Content-Type: application/x-www-form-urlencoded
Content-Length: 38

username=chema&password=semeolvido

En la mayoría de situaciones el método POST es preferido sobre el método GET, no sólo porque asegura a la aplicación web mayor control sobre los datos ingresados, sino por una razón más. La cantidad de bytes que se puede escribir en un URL es bastante limitada, mientras que en el cuerpo del mensaje HTTP puede crecer arbitrariamente. Esto es especialmente necesario cuando se tienen grandes formularios o donde el usuario puede escribir extensas cantidades de texto en sus campos.

Seguridad y validación de datos

Los ejemplos del php_login_form_get y php_login_form_post no deben ponerse en funcionamiento en un sistema en producción, debido a que tienen una gran vulnerabilidad: los valores que provienen de los arreglos asociativos $_GET y $_POST son provistos por el visitante, y nada impide que éste pueda enviar código (X)HTML, JavaScript, PHP o SQL con malas intenciones, por ejemplo, para descubrir contraseñas o información sensible. A esta práctica se le llama inyección de código y aunque es bastante fácil evitar sus efectos nocivos, sigue siendo un tipo de ataque común ya que es fácil para el programador olvidar defender su código.

Siempre que el programador va a tomar un valor de los arreglos $_GET y $_POST, debe "esterilizarlo" (sanitize) primero; lo cual se logra neutralizando el efecto de los caracteres especiales en cada lenguaje. PHP provee varias funciones para esterilizar código, las cuales se listan en la php_sanitize_functions.

Función Descripción
stripslashes($str) Retorna un string resultado de eliminar los backslahes (\) de $str.
htmlentities($str) Retorna un string resultado de reemplazar los caracteres especiales en (X)HTML (<, >, &, ', ") por sus respectivas entidades (&lt;, &gt;, &amp;, &apos;, &quot;).
strip_tags($str) Elimina etiquetas (X)HTML que hayan en $str y retorna el resultado en una nueva cadena.
mysql_real_escape_string($str) Inserta backslahes (\) a los caracteres que tienen significado especial en SQL, como cambios de línea y comillas.
Funciones para esterilizar código en PHP

El php_sanitize muestra un archivo con funciones de conveniencia que llaman a las listadas en la php_sanitize_functions, las cuales se utilizan en las líneas 2, 3 y 4 del php_login_form_post_sanitize para evitar inyección de código maligno.

<?php
function sanitize($str)
{
    return strip_tags(htmlentities(stripslashes($str)));
}

function sanitize_mysql($str)
{
    return sanitize(mysql_real_escape_string($str));
}

function sanitize_trim($str) { return trim(sanitize($str)); }
function sanitize_mysql_trim($str) { return trim(sanitize_mysql($str)); }
?>
Funciones de conveniencia para esterilizar código (X)HTML, PHP y SQL.
<body><?php
    require_once('sanitize.php');
    $username = isset($_POST['username']) ? sanitize_trim($_POST['username']) : '';
    $password = isset($_POST['password']) ? sanitize_trim($_POST['password']) : '';
    if ( $username != '' && $password != '' )
    {
        echo "<p>Bienvenido(a) <strong>$username</strong> a nuestro sitio seguro. ";
        echo "(No eres <a href=\"login1.php\">$username</a>?).</p>";
    }
    else
    {
        echo <<<_EOT
        <form method="post" action="login1.php">
            <p><label>Usuario: <input type="text" name="username"/></label></p>
            <p><label>Contraseña: <input type="password" name="password"/></label></p>
            <p><input type="submit" value="Enviar"/></p>
        </form>
_EOT;
    }
?></body>
Esterilización de código. Correr este ejemplo.

Ejercicio. Modifique su solución del ejercicio anterior para impedir efectos secundarios ante un ataque de inyección de código.

La validación de datos provistos por el informante es una tarea más semántica, como por ejemplo, verificar que los números se encuentren dentro de un rango apropiado, los textos no sean muy cortos o largos, o estén en algún lenguaje natural. En el php_login_form_post_sanitize simplemente se ha discriminado cadenas vacías o constituidas únicamente de espacios en blanco (línea 5).

Ejercicio. Haga que si se provee un año que su programa no soporta (por ejemplo, -234, 659071 ó 'asdfg'), presente un mensaje indicando el rango de años válidos para su aplicación, además del formulario para ingresar otro año (pero no los resultados para el año actual, ya que podría confundir al visitante).

Controles en el formulario

El estándar (X)HTML permite varios tipos de controles para ingresar datos, los cuales se listan en la xhtml_form_control_types. Normalmente cuando se escribe un formulario, el autor incluye algún texto cercano a cada campo que ayude al informante a saber qué tipo de información debe proveer en él, al cual se le llama rótulo o etiqueta (label). Si tanto el texto como el campo se escriben dentro de un elemento label, el navegador los asociará semánticamente, de tal forma que cuando el usuario hace click en el rótulo, el navegador transferirá el efecto al campo asociado. Este es el comportamiento natural que el usuario espera, en especial para checkboxes y radio buttons. Los ejemplos de la xhtml_form_control_types incluyen rótulos para ilustrar esta práctica.

Nombre Ejemplo
Checkbox

<label><input type="checkbox" name="recordarme" value="si" checked="checked"/>Recordarme</label>
Radio buttons

Sexo:

<p>Sexo:
    <label><input type="radio" name="sexo" value="1" checked="checked"/>Hombre</label>
    <label><input type="radio" name="sexo" value="2"/>Mujer</label>
</p>
Combo box

<label>Nivel académico aprobado:
    <select name="nivel_academico">
        <option value="0">Ninguno</option>
        <option value="1">Kindergarten</option>
        <option value="2">Primaria</option>
        <option value="3">Secundaria académica</option>
        <option value="4">Secundaria técnica</option>
        <option value="5">Universitaria</option>
    </select>
</label>
Multiple list

<label>Marque los lenguajes que conoce:<br/>
    <select name="aficiones" size="6" multiple="multiple">
        <option value="1">Ensamblador</option>
        <option value="2">C</option>
        <option value="3">C++</option>
        <option value="4">Objective-C</option>
        <option value="5">Java</option>
        <option value="6">C#</option>
        <option value="7">PHP</option>
        <option value="8">JavaScript</option>
        <option value="9">Ruby</option>
        <option value="10">SQL</option>
        <option value="11">Lisp</option>
        <option value="12">Prolog</option>
    </select>
</label>
Button

<input type="button" value="Validar"/>
Submit button

<input type="submit" value="Buscar"/>
Reset button

<input type="reset" value="Limpiar"/>
Text field

<label>Buscar:
    <input type="text" name="buscar" maxlength="255" size="50" value="Escriba su consulta aquí"/>
</label>
Password

<label>Contraseña: <input type="password" name="contrasena" maxlength="12" size="12"/></label>
Text area

<label>
    Describa el problema: <br/>
    <textarea name="problema" cols="60" rows="6">Escriba los pasos para reproducir el problema</textarea>
</label>
                
File select

<input type="file" name="avatar"/>
Hidden field
<input type="hidden" name="userid" value="chema"/>
Tipos de controles disponibles en un formulario web

A excepción de las listas (select) y las áreas de texto (textarea), la mayoría de controles en (X)HTML se escriben con el elemento input. Su único atributo obligatorio es type, que indica el tipo de control que se quiere: checkbox, radiobutton, .

Todos los controles comparten el atributo name, cuyo valor es un identificador que no necesariamente debe ser único en el documento. Cuando un formulario es aceptado (al presionar el botón "submit"), el navegador ensambla las parejas campo=valor en el query string a partir de los nombres de los controles y sus respectivos valores. Si un control no tiene nombre, el navegador simplemente lo ignora. Los demás atributos son dependientes del tipo de control, como se explica en los siguientes párrafos.

Las casillas de verificación (checkboxes) permiten al usuario indicar un valor booleano al marcar o no un rectángulo. Se escriben con la notación <input type="checkbox" name="checkbox_name" value="yes" checked="checked"/>. Su estado inicial es desmarcado, a menos de que se provea el atributo checked="checked". Cuando se acepta el formulario, si la casilla de verificación está desmarcada, el navegador simplemente no la incluye en el query string. Si la casilla de verificación está marcada, se incluye una pareja checkbox_name=on en el query string. El valor "on" lo asume el navegador, pero se puede reemplazar por un valor más significativo indicándolo en el atributo value="valor".

Los botones de radio (radio buttons) permiten al informante escoger una única opción entre varias disponibles. El autor debe proveer un elemento <input type="radio" name="nombre_grupo" value="valor" checked="checked"/> por cada opción. Si varios botones de radio comparten el mismo nombre, conforman un grupo de botones de radio y sólo uno de ellos puede estar marcado a la vez. Inicialmente todos los botones de radio están desmarcados a menos de que uno tenga el atributo checked="checked". Cuando se acepta el formulario (se presiona el botón Submit), si ningún botón de radio en el grupo está seleccionado, el navegador omite el grupo por completo en el query string; si el botón seleccionado no tiene un valor en el atributo value, el navegador envía el valor "on" para el grupo, lo cual carece de utilidad para el desarrollador; por eso es importante proveer un valor adecuado en el atributo value de cada botón de radio, y en tal caso, al aceptar el formulario el navegador agrega una pareja nombre_grupo=valor, donde valor es el valor del atributo value del botón seleccionado por el usuario.

Los botones (button) se especifican con <input type="button" value="label del botón"/>. No tienen una acción asociada a menos de que se les establezca una con JavaScript. Su rótulo se puede cambiar con el atributo value. El botón de enviar (submit button) se escribe <input type="submit" value="label del botón"/> ejecuta la acción especificada en el atributo action del formulario (elemento form). Existe una variación del botón enviar que permite reemplazarlo por una imagen, por ejemplo: <input type="image" src="enviar.svg" alt="Enviar datos"/>. El botón de limpiar (reset button) se escribe <input type="reset" value="label del botón"/> y cuando se presiona indica al navegador que regrese todos los controles del formulario a sus valores originales.

Los campos de texto (text field) son quizá el tipo de control más usado en el web. Permiten introducir una línea de texto, cuya longitud está limitada por el valor del atributo maxlength. El atributo size indica la cantidad de caracteres que tendrá el ancho visible del control, sin embargo, es mejor ajustar el ancho mediante hojas de estilo (CSS). Si se escribe un texto en el atributo value, se tomará como el valor inicial del campo. En caso de que se quiera delimitar el campo para que permita únicamente números o valores en cierto formato, se debe emplear JavaScript.

Una variación del campo de texto son los campos de contraseña (password field) que despliegan asteriscos u otro carácter especial para encubrir los reales, con el fin de introducir información sensible que podría ser vista por una persona ajena al informante. Tiene otras características como deshabilitar funciones del portapapeles y disparar el sistema de recuerdo de contraseñas del navegador.

El selector de archivo (file select) permite al visitante adjuntar un archivo cualquiera de su computadora local al formulario. Se escribe con el elemento <input type="file" name="nombre_selector"/>. Una vez enviado al servidor, el nombre del archivo y su contenido se pueden obtener en PHP a través del arreglo superglobal $_FILES.

El elemento <select name="nombre_lista" size="elementos_visibles" multiple="multiple" value="default_value">...</select> sirve para construir listas desplegables, listas simples y listas múltiples. Una lista desplegable (combo box) presenta sólo el elemento seleccionado y en caso de que se active el control, una lista emergente muestre todas las opciones. Las listas desplegables se forman con el atributo size = 1, y aunque permiten selección múltiple, no es un comportamiento natural. Si el valor del atributo size es 2 o más, el navegador presentará un control de lista simple; y si el atributo multiple="multiple" está presente, el navegador presentará una lista múltiple donde el usuario podrá seleccionar varios elementos con el ratón mientras mantiene la tecla Ctrl o Command presionada.

Los elementos de la lista se escriben con elementos <option value="valor">Texto</option>. Por defecto el primero ellos estará seleccionado en caso de que la lista sea desplegable (combo box). Si se quiere preseleccionar otro valor, debe especificarse con el atributo value del elemento select. Se pueden crear grupos de opciones con el elemento optgroup, como se ilustra en el xhtml_hierarchical_list_control.

<label>Cantón de nacimiento:<br/>
    <select name="canton" size="10">
        <optgroup label="San José">
            <option value="101">San José</option>
            <option value="102">Escazú</option>
            ...
            <option value="120">León Cortés</option>
        </optgroup>
        <optgroup label="Alajuela">
            <option value="201">Alajuela</option>
            <option value="202">San Ramón</option>
            ...
            <option value="215">Guatuso</option>
        </optgroup>
        ...
    </select>
</label>
Grupos de opciones en un control de lista.

Los campos ocultos (hidden field) son útiles para almacenar valores provenientes del servidor web que no es necesario mostrar al usuario; pero que pueden ser leídos por código JavaScript, y también por código PHP cuando el formulario vuelva a ser enviado. Se suelen transferir identificadores de usuario o sesión mediante estos campos; los cuales no se deben asumir seguros, ya que sus valores se pueden descubrir simplemente mirando el código fuente de la página web en el navegador.

El autor puede ajustar la apariencia de todos los controles del formulario mediante hojas de estilos CSS. Es una práctica común emplear una tabla dentro del formulario para dar una apariencia ordenada. En tal caso habrá que separar los rótulos de los controles. Por dicha el elemento label tiene el atributo for="id_del_control", que permite conectar el rótulo con el control mediante el id del control, indiferentemente de dónde ése se encuentre dentro del documento web.

Ejercicio. Modifique su programa de días feriados para permitir al visitante escribir un rango de años, por ejemplo 2010 a 2012. Al enviar la consulta, su programa despliega los días feriados para cada año en el rango (2010, 2011 y 2012 en nuestro ejemplo). Utilice los controles que guste.

Ejercicio. Su implementación actual del programa de días feriados funciona de la siguiente forma. La primera vez que el visitante carga la página, se presenta un formulario vacío. En él escribe un rango de años y envía la consulta. El sistema le responde con el resultado de la consulta y el formulario vacío de nuevo para permitirle emitir una nueva consulta. Haga que el formulario en la página resultado conserve en sus campos los valores que el visitante escribió en la consulta previa.

Ejercicio. Haga que su sitio lleve cuenta del número de veces que se consulta los días feriados de cada año. Despliegue este contador en sus resultados, por ejemplo:

Feriados de 2011 por día de la semana (consultado 18 veces)
Feriados Detalle
Domingo 2 Día del trabajo, Navidad
Lunes 3 Batalla de Rivas, Anexión de Nicoya, Día de la madre
Martes 1 Día de la Virgen de los Ángeles
Miércoles 1 Día de las culturas
Jueves 1 Independencia de Costa Rica
Viernes 0
Sábado 1 Año nuevo

Nota: El rojo es sólo para hacer notar la diferencia. En su solución el contador puede aparecer del color natural del texto.

Validación de datos con JavaScript

Cuando se implementa un formulario web es apremiante hacer una doble validación de datos, tanto en el servidor web como en el navegador. La validación de datos en el lado del cliente es instantánea. Permite al informante tener asistencia y retroalimentación antes de enviar los datos, sin tener que esperar y consumir recursos para que el servidor web se pronuncie. El autor podría estar tentado a pensar que validando los datos en el navegador, éstos estarán limpios y listos para ser procesados o almacenados en la base datos, por lo que es innecesaria una segunda validación con PHP. Sin embargo, la validación en el lado del servidor es ineludible. El visitante podría deshabilitar JavaScript en su navegador, o modificar su código fuente, tanto (X)HTML como JavaScript y poner a prueba su servidor de ingeniosas formas, en especial si la información que está en juego es sensible o de alto interés (como dinero).

En general la validación con JavaScript consiste en obtener el valor de cada control del formulario y revisar que sea adecuado. Existen varias formas de acceder al valor de un control. Quiźa la más sencilla sea utilizando el valor del atributo name. Cuando el navegador carga el documento, crea un objeto tipo arreglo document.forms[] con cada formulario que en él encuentre. Además si se provee un nombre al formulario de la forma <form name="form_name"...> creará un objeto document.form_name, y los controles de dicho formulario se pueden acceder de la forma document.form_name.field_name, en especial es de interés su valor con la propiedad document.form_name.field_name.value.

La validación se puede realizar en distintos momentos: cuando el usuario cambia el valor de un control (onchange) o cuando intenta enviar el formulario. En el ejemplo del js_web_form_validation se hace de la segunda forma, interceptando el evento onsubmit del formulario (línea 2). Nótese que se emplea la palabra reservada return al invocar la función validadora. Si esta función retorna true, el navegador continúa el envío de datos al servidor; pero si retorna false, el manejo del evento se interrumpe, evitando que el formulario sea enviado al servidor.

<body>
    <form name="login" method="post" action="login3.php" onsubmit="return validate()">
        <table>
            <tr>
                <th><label for="username">Usuario:</label></th>
                <td><input type="text" name="username" id="username"/></td>
            </tr>
            <tr>
                <th><label for="password">Contraseña:</label></th>
                <td><input type="password" name="password" id="password"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="Enviar"/>
                <input type="reset" value="Limpiar"/></td>
            </tr>
        </table>
    </form>
    <script type="text/javascript">
    <!--
        function validate()
        {
            var result = '';
            result += validateUsername(document.login.username.value);
            result += validatePassword(document.login.password.value);

            if ( result == '' ) return true;

            alert(result);
            return false;
        }

        function validateUsername(value)
        {
            // Quitar espacios en blanco (trim)
            value = value.replace(/^\s+|\s+$/g, '');

            // El nombre es obligatorio
            if ( value == '' ) return 'Especifique el nombre de usuario\\n';

            // Impedir caracteres especiales
            if ( /[ !"\#\$%&'\(\)\*\+\,\-\.\/\:;<=>\?@\[\]\^\`\{\|\}\~\\\\]/.test(value) )
                return 'No utilice caracteres especiales en el nombre\\n';

            return '';
        }

        function validatePassword(value)
        {
            // Quitar espacios en blanco (trim)
            value = value.replace(/^\s+|\s+$/g, '');

            // La contraseña es obligatoria
            if ( value.length < 8 ) return 'La contraseña debe ser al menos de 8 caracteres\\n';

            // Debe tener mayusculas, minusculas y numeros
            if ( ! /[a-z]/.test(value) || ! /[A-Z]/.test(value) || ! /[0-9]/.test(value) )
                return 'La contraseña debe tener mayúsculas, minúsculas y números';

            return '';
        }
    -->
    </script>
</body>
Validación de dos campos de un formulario utilizando JavaScript. Correr este ejemplo.

La validación se realiza probando condiciones contra los datos provistos por el usuario. El código del js_web_form_validation emplea expresiones regulares para hacer más sencilla la programación. Una expresión regular en JavaScript se escribe entre dos caracteres slash (/ /) que no están seguidos (de lo contrario formarían un comentario). Internamente JavaScript crea un objeto RegExp que provee el método test(str), el cual recibe una cadena de caracteres, y si logra encontrar la expresión regular dentro de ella, retorna true. Por su parte, el método str.replace(/exp/g, text) busca todos los textos de str que cumplen la expresión regulgar exp y los reemplaza por text. El estudio de la nomenclatura de las expresiones regulares se deja como ejercicio para el lector.

Ejercicio. Utilizando JavaScript deshabilite el botón de enviar en su formulario de consulta de días feriados, hasta que el usuario provea un año o un rango de años válidos.

Ejercicio. Utilizando JavaScript provea retroalimentación visual en tiempo real al visitante. Resalte en rojo el valor de un campo cuando se escribe en él un año fuera de rango. Inmediatamente después de que el visitante haya corregido el año, debe regresar al color normal del campo, por ejemplo, negro.

Bibliografía

  1. [Gars02] GARSHOL, Lars Marius. Definitive XML application development Prentice Hall PTR, United States of America, 2002
  2. [Gold99] GOLDFARB, CHARLES Y PRESCOD, PAUL. Manual de XML. Prentice Hall, Madrid, España, 1999.
  3. [Holm02] HOLMAN, Ken. Definitive XSLT and XPath Prentice may PTR, United States of America, 2002.
  4. [Marc00] MARCHAL, Benoît. XML by Example. QUE, United States of America, 2000.
  5. [Walm02] WALMSLEY, Priscilla. Definitive XML Schema. Prentice Hall PTR, New Jersey, 2002.