Hey, Scripting Guy!Últimas palabras célebres

Los Scripting Guys de Microsoft

El famoso filósofo griego Sócrates (quien murió en el año 399 a. C., poco antes de que naciera el redactor de scripting) es quizás más conocido por haber dicho: "Una vida sin examen no merece ser vivida". Mucha gente conoce esta frase. Sin embargo, resulta significativo que los Scripting Guys descubrieran recientemente que Sócrates nunca había dicho eso de que "una vida sin examen no merece ser vivida". Resulta que fue una mala traducción hecha por un escribiente medieval hace siglos. En lugar de eso, lo que Sócrates realmente dijo fue que: "un archivo XML sin examen no merece la pena tenerlo".

¡Este dicho sí que tiene realmente sentido! En los últimos tiempo, XML se está haciendo cada vez más popular; una búsqueda rápida de uno de los equipos de prueba de los Scripting Guys reveló que son más de 500 archivos XML los que usan diversos sistemas o aplicaciones.

Sin mencionar la gran cantidad de datos importantes que ahora se almacenan en formato XML. Y eso está bien. A menos que, por supuesto, esos datos dejen de examinarse. Y, por desgracia, así sucede a menudo, no por otra cosa que el hecho de que muchas personas no tienen ni idea de cómo se examina un archivo XML. En particular, no tienen la menor idea sobre cómo realizar consultas y buscar ese archivo XML.

Nota Por si no está al corriente de quiénes son los filósofos griegos: Sócrates era un gran defensor del pasear mientras hablaba de cosas, pero no lo fue, en cambio, del andar mientras se iban haciendo cosas. De hecho, su mujer, Xanthippe, se refería a él como un "holgazán inútil". Ya habrá visto por qué los Scripting Guys sienten debilidad por Sócrates.

Por supuesto, alguno de los que están leyendo esto quizás esté pensando, "Un momento: ¿no hablaron ya los Scripting Guys acerca de la búsqueda de archivos XML en una columna anterior ("Persecución de coches… y XML" en technet.microsoft.com/magazine/cc162506)? Si es así, ¿por qué vuelven a insistir en este tema ahora? ¿Son tan perezosos que pretenden escribir la misma columna una y otra vez?"

Créalo o no, somos aún más perezosos que eso. Pero no es este el motivo principal de que estemos insistiendo en este tema. En nuestra columna anterior hablamos sobre el hecho de trabajar con un archivo XML que estuviese estructurado como el código que se muestra en la figura 1, en la que cada uno de los valores de las propiedades representa un solo nodo en el archivo.

Figura 1 Archivo XML sólo con nodos

<?xml version='1.0'?> 
  <INVENTORY> 
    <COMPUTER>
      <os>Windows XP</os>
      <department>Human Resources
      </department>
      <name>atl-ws-001</name>
    </COMPUTER> 
    <COMPUTER>
      <os>Windows XP</os>
      <department>Finance</department>
      <name>atl-ws-002</name>
    </COMPUTER> 
    <COMPUTER>
      <os>Windows Vista</os>
      <department>Finance</department>
      <name>atl-ws-003</name>
    </COMPUTER> 
  </INVENTORY>

Eso está bien, salvo porque no es la única manera en que los archivos XML pueden estructurarse, tal y como la gente ya se apresuró a señalar. En el ejemplo anterior, cada equipo del archivo tiene su propio nodo; en cambio, cada uno de esos nodos tiene varios nodos secundarios (sistema operativo, departamento y nombre). Sin embargo, también es posible construir un archivo XML en el que los nodos individuales no tengan nodos secundarios; en lugar de eso, los valores de propiedad adicionales se configuran como atributos. Como en el archivo que se muestra en la figura 2.

Figura 2 Archivo XML con atributos

<?xml version='1.0'?> 
  <HARDWARE> 
      <COMPUTER os="Windows XP" department="Human Resources">atl-ws-001</COMPUTER> 
      <COMPUTER os="Windows XP" department="Finance">atl-ws-002</COMPUTER> 
      <COMPUTER os="Windows Server 2003" department="IT">atl-fs-003</COMPUTER> 
      <COMPUTER os="Windows Vista" department="IT">atl-ws-004</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Human Resources">atl-ws-005</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Finance">atl-ws-006</COMPUTER> 
      <COMPUTER os="Windows XP" department="Sales">atl-ws-007</COMPUTER> 
      <COMPUTER os="Windows Server 2008" department="IT">atl-fs-008</COMPUTER> 
      <COMPUTER os="Windows XP" department="Human Resources">atl-ws-009</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Sales">atl-ws-010</COMPUTER> 
  </HARDWARE>

¿Supone eso un problema? Pues sí. El script que le mostramos hace algún tiempo, en un número publicado hace ya bastante tiempo, no funciona con un archivo XML estructurado como el que se muestra en la figura 2. Simplemente no va a funcionar. Y aquí está básicamente el problema, ya que muchos de los usuarios necesitan poder leer este tipo de archivo XML.

Todo lo que debían hacer era preguntar. Bueno, eso, y también esperar un año y medio antes de que finalmente nos pusiéramos a abordar el tema.

Antes de continuar, observemos más de cerca nuestro archivo XML. En este caso, tenemos un archivo con un nodo principal denominado HARDWARE y cada equipo individual existe como un nodo secundario de HARDWARE. Además, cada uno de estos nodos tiene un par de atributos: sistema operativo (usado para almacenar el nombre del sistema operativo instalado en el equipo) y departamento (usado para almacenar el nombre del departamento que posee el equipo).

Supongamos, por ejemplo, que queremos obtener una lista de todos los equipos que ejecutan Windows XP. ¿Podemos hacerlo usando un script? Por supuesto que podemos:

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("HARDWARE.xml")

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER[@os='Windows XP']")

For Each objNode in colNodes
  Wscript.Echo objNode.Text 
Next

Veamos lo que tenemos aquí. Para empezar, creamos una instancia del objeto de Microsoft.XMLDOM; tal y como el mismo nombre implica, éste es el objeto que nos permite trabajar con archivos XML. Una vez que hemos creado el objeto, establecemos la propiedad Async en Falso. Ello permite que el script sepa que queremos cargar el documento sincrónicamente en lugar de hacerlo asincrónicamente. ¿Por qué le importa al script hacerlo de esta forma? Para ser sinceros, probablemente no le importa; después de todo, los scripts (como los Scripting Guys) son objetos inanimados.

Sin embargo, a usted sí debe importarle. Si cargásemos el documento de manera asincrónica, el script continuaría ejecutándose incluso si el documento no se hubiese cargado completamente. Eso no es bueno: imagínese los problemas que podrían generarse si intentara realizar una tarea en un documento que todavía no existe. La carga de un archivo XML de manera sincrónica garantiza que el archivo se cargará completamente antes de que nuestro script siga ejecutándose.

¡Qué coincidencia más asombrosa! Nos encontramos en el punto en el que cargamos nuestro archivo XML. Eso es algo que hacemos llamando al método Load y abriendo el archivo C:\Scripts\HARDWARE.xml, lo cual nos lleva a esta línea de código:

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER[@os='Windows XP']")

Lo que hacemos a continuación es usar el método SelectNodes para realizar consultas en el archivo XML y comunicarle al script qué nodos XML queremos recuperar. Observe la sintaxis, que es, ciertamente, poco habitual.

Para empezar, debemos especificar la ruta de acceso dentro del archivo XML. Nuestro archivo XML tiene un nodo superior denominado HARDWARE seguido de una serie de nodos de segundo nivel denominados EQUIPO. Cada uno de estos nodos de segundo nivel representa un solo registro en nuestro archivo de datos XML. Por eso nuestra consulta de selectNodes empieza de esta forma:

//HARDWARE/COMPUTER

Llegados a este punto, nos encontramos con esta construcción:

[@os='Windows XP']

Esta parte de la consulta es parecida a una cláusula WHERE en una consulta SQL estándar. Con SQL podríamos usar una consulta de la base de datos similar a ésta para recuperar una lista de todos los equipos que ejecutan el sistema operativo de Windows XP:

SELECT Name FROM Hardware 
    WHERE OS = 'Windows XP'

Con el método SelectNodes hacemos algo parecido: pedimos recibir una lista de todos los equipos en los que el atributo del sistema operativo (@os) es igual a Windows XP. Y para indicar que se trata de una cláusula WHERE, encerramos la cláusula entera entre corchetes.

Nota Este tipo de consulta se conoce como una consulta XPath. Para obtener más información acerca de XPath, vaya a msdn.microsoft.com/library/ms256115.aspx.

Como ya dijimos antes, es un poco raro, pero funciona. ¿Qué pasaría si quisiéramos obtener una lista de todos los equipos que pertenecen al departamento de finanzas? No supondría ningún problema; nuestra llamada a selectNodes se parecería a esto:

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER" & _
   "[@department='Finance']")

De nuevo, nuestra cláusula WHERE está encerrada entre corchetes, comenzamos por el nombre del atributo (el departamento) con el signo de la arroba (@) y, a continuación, indicamos el valor que nos interesa.

Sencillo, ¿eh?

Después de haber llamado al método selectNodes, podemos obtener los nombres de los equipos simplemente recorriendo con un bucle la recopilación de valores y devolviendo la propiedad Text, tal y como se muestra a continuación:

For Each objNode in colNodes
  Wscript.Echo objNode.Text 
Next

¿Y qué tipo de información podemos esperar obtener? Para ser sinceros, esperamos recibir información como ésta:

atl-ws-001
atl-ws-002
atl-ws-007
atl-ws-009

En otras palabras, nos devuelve los nombres de todos los equipos que siguen ejecutando Windows XP.

Por cierto, no sólo es posible enviar informes con el nombre de los equipos. Como el nombre de los equipos es el valor "predeterminado" para cada nodo, eso es simplemente lo que obtenemos al hacer referencia a la propiedad Text.

Otra alternativa consiste en poder especificar exactamente qué valores de atributos deseamos que se nos devuelvan. Por ejemplo, echemos una ojeada a este bucle For each modificado:

For Each objNode in colNodes
    Wscript.Echo objNode.Text 
    Wscript.Echo objNode.Attributes. _
      getNamedItem("department").Text
    Wscript.Echo
Next

Como puede ver, en este bucle todavía obtenemos el valor de la propiedad Text; sin embargo, en esta línea de código también hemos agregado:

Wscript.Echo objNode.Attributes. _
  getNamedItem("department").Text

En este caso, usamos el método get-NamedItem para recuperar el valor del atributo de departamento y, a continuación, obtenemos la propiedad Text para ese atributo. De esta forma es como podemos especificar qué valores de atributo (y cuáles no) obtenemos en la pantalla. (Observe también que hemos agregado un comando Wscript.Eco para dejar una línea en blanco entre registros.) Al ejecutar este script, deberíamos recibir lo siguiente:

atl-ws-001
Human Resources

atl-ws-002
Finance

atl-ws-007
Sales

atl-ws-009
Human Resources

Y sí, pueden escribirse consultas más complejas si se desea. Por ejemplo, supongamos que desea tener una lista de todos los equipos que ejecutan Windows XP y Windows Vista. En SQL lo haría escribiendo una consulta OR. ¿Lo adivina? Se hace lo mismo exactamente al consultar un archivo XML:

Set colNodes=xmlDoc.selectNodes _
    ("//HARDWARE/COMPUTER" & _
     "[@os='Windows XP' or " & _
     "@os='Windows Vista']")

¿Ve lo que hemos hecho aquí? Dentro de un único conjunto de corchetes, proporcionamos dos criterios: @os='Windows XP' o @os='Windows Vista'. Eso es todo lo hay que hacer. Y nos va a generar un informe parecido al de la figura 3.

Figura 3 Resultado de la consulta OR

atl-ws-001
Human Resources

atl-ws-002
Finance

atl-ws-004
IT

atl-ws-005
Human Resources

atl-ws-006
Finance

atl-ws-007
Sales

atl-ws-009
Human Resources

atl-ws-010
Sales

Si vuelve a comprobar el archivo XML, verá que los equipos que ejecutan Windows XP o Windows Vista quedan incluidos en los resultados; los equipos que ejecutan Windows Server 2003 o Windows Server 2008 no se han incluido en los resultados. Ni deben.

¿Alguna pregunta? ¿Le gustaría escribir alguna consulta más restringida, una que facilite sólo los equipos que ejecutan Windows XP y que pertenezcan al departamento de finanzas? Como probablemente habrá adivinado, es fácil; todo lo que hay que hacer es escribir una consulta AND como ésta:

Set colNodes=xmlDoc.selectNodes _
    ("//HARDWARE/COMPUTER" & _
     "[@os='Windows XP' and " & _
     "@department='Finance']")

Al hacerlo recibirá el siguiente conjunto de datos:

atl-ws-002
Finance

¿Por qué hay sólo un elemento en nuestro informe? Exacto: Porque el departamento de finanzas sólo tiene un equipo que ejecuta Windows XP.

Esperamos haberle ayudado a tratar con este tipo específico de archivo XML. Si resulta que hay algún otro tipo de archivo XML pululando por ahí, buena suerte con él.

Ya que hemos empezado la columna de este mes con un dicho famoso, pensamos que estaría bien terminarla de una forma parecida. Así que nos gustaría concluir con unas palabras del emperador Guillermo II, palabras que los Scripting Guys recordamos con cariño: "Por la presente renuncio, a todos los efectos, al trono de Prusia y al trono imperial alemán vinculado a éste".

Dr. Scripto's Scripting Perplexer

El desafío mensual que pone a prueba no sólo sus habilidades para resolver rompecabezas, también sus habilidades para el scripting.

Octubre de 2008: Crucigrama de VBScript

Para resolver este enigma, simplemente rellene cada fila con el nombre de una función de VBScript. Una vez que haya acabado, descifre las letras de los cuadrados azules para encontrar otro nombre de función.

fig10.gif

RESPUESTA:

Dr. Scripto's Scripting Perplexer

Respuesta: Octubre de 2008: Crucigrama de VBScript

puzzle_answer.gif

Los Scripting Guys, Greg Stemp y Jean Ross, trabajan para Microsoft.